使用本指南中的步骤从 Java 代码访问应用的资源包。
构建 Kotlin 和 Java 版本
按照以下步骤将 Play 资源交付集成到项目的 Android 应用包中。您无需使用 Android Studio 执行这些步骤。
将项目
build.gradle
文件中的 Android Gradle 插件版本更新到4.0.0
或更高版本。在项目的顶级目录中,为资源包创建一个目录。此目录名称用作资源包名称。资源包名称必须以字母开头,并且只能包含字母、数字和下划线。
在资源包目录中,创建一个
build.gradle
文件并添加以下代码。确保指定资源包的名称,并且只指定一种交付类型Groovy
// In the asset pack's build.gradle file: plugins { id 'com.android.asset-pack' } assetPack { packName = "asset-pack-name" // Directory name for the asset pack dynamicDelivery { deliveryType = "[ install-time | fast-follow | on-demand ]" } }
Kotlin
// In the asset pack's build.gradle.kts file: plugins { id("com.android.asset-pack") } assetPack { packName.set("asset-pack-name") // Directory name for the asset pack dynamicDelivery { deliveryType.set("[ install-time | fast-follow | on-demand ]") } }
在项目的 app
build.gradle
文件中,添加项目中每个资源包的名称,如下所示Groovy
// In the app build.gradle file: android { ... assetPacks = [":asset-pack-name", ":asset-pack2-name"] }
Kotlin
// In the app build.gradle.kts file: android { ... assetPacks += listOf(":asset-pack-name", ":asset-pack2-name") }
在项目的
settings.gradle
文件中,包含项目中的所有资源包,如下所示Groovy
// In the settings.gradle file: include ':app' include ':asset-pack-name' include ':asset-pack2-name'
Kotlin
// In the settings.gradle.kts file: include(":app") include(":asset-pack-name") include(":asset-pack2-name")
在资源包目录中,创建以下子目录:
src/main/assets
。将资源放在
src/main/assets
目录中。您也可以在此处创建子目录。应用的目录结构现在应如下所示build.gradle
settings.gradle
app/
资源包名称/build.gradle
资源包名称/src/main/assets/您的资源目录
使用 Gradle 构建 Android 应用包。在生成的应用包中,根级目录现在包含以下内容
资源包名称/manifest/AndroidManifest.xml
:配置资源包的标识符和交付模式资源包名称/assets/您的资源目录
:包含作为资源包一部分交付的所有资源的目录
Gradle 会为每个资源包生成清单,并为您输出
assets/
目录。(可选) 如果你计划使用快速跟进和按需交付,请包含 Play Asset Delivery 库
Groovy
implementation "com.google.android.play:asset-delivery:2.2.2" // For Kotlin use asset-delivery-ktx implementation "com.google.android.play:asset-delivery-ktx:2.2.2"
Kotlin
implementation("com.google.android.play:asset-delivery:2.2.2") // For Kotlin use core-ktx implementation("com.google.android.play:asset-delivery-ktx:2.2.2")
(可选) 配置你的应用包以支持不同的 纹理压缩格式。
集成 Play Asset Delivery API
Play Asset Delivery Java API 提供了 AssetPackManager
类用于请求资源包、管理下载和访问资源。请确保首先 将 Play Asset Delivery 库添加到你的项目中。
你需要根据你希望访问的资源包的交付类型来实现此 API。以下流程图显示了这些步骤。
安装时交付
配置为 install-time
的资源包在应用启动时即可立即使用。使用 Java AssetManager API 访问以这种模式提供的资源
Kotlin
import android.content.res.AssetManager ... val context: Context = createPackageContext("com.example.app", 0) val assetManager: AssetManager = context.assets val stream: InputStream = assetManager.open("asset-name")
Java
import android.content.res.AssetManager; ... Context context = createPackageContext("com.example.app", 0); AssetManager assetManager = context.getAssets(); InputStream is = assetManager.open("asset-name");
快速跟进和按需交付
以下部分展示了如何在下载资源包之前获取有关资源包的信息,如何调用 API 启动下载,以及如何访问已下载的包。这些部分适用于 fast-follow
和 on-demand
资源包。
检查状态
每个资源包都存储在应用内部存储中的单独文件夹中。使用 getPackLocation()
方法确定资源包的根文件夹。此方法返回以下值
返回值 | 状态 |
---|---|
一个有效的 AssetPackLocation 对象 |
资源包根文件夹已准备好通过 assetsPath() 立即访问 |
null |
未知资源包或资源不可用 |
获取有关资源包的下载信息
应用需要在获取资源包之前公开下载的大小。使用 requestPackStates()
或 getPackStates()
方法确定下载的大小以及包是否正在下载。
Kotlin
suspend fun requestPackStates(packNames: List<String>): AssetPackStates
Java
Task<AssetPackStates> getPackStates(List<String> packNames)
requestPackStates()
是一个返回 AssetPackStates
对象的挂起函数,而 getPackStates()
是一个返回 Task<AssetPackStates>
的异步方法。packStates()
方法返回 Map<String, AssetPackState>
。此映射包含每个请求的资源包的状态,以其名称为键
Kotlin
AssetPackStates#packStates(): Map<String, AssetPackState>
Java
Map<String, AssetPackState> AssetPackStates#packStates()
最终请求如下所示
Kotlin
const val assetPackName = "assetPackName" coroutineScope.launch { try { val assetPackStates: AssetPackStates = manager.requestPackStates(listOf(assetPackName)) val assetPackState: AssetPackState = assetPackStates.packStates()[assetPackName] } catch (e: RuntimeExecutionException) { Log.d("MainActivity", e.message) } }
Java
final String assetPackName = "myasset"; assetPackManager .getPackStates(Collections.singletonList(assetPackName)) .addOnCompleteListener(new OnCompleteListener<AssetPackStates>() { @Override public void onComplete(Task<AssetPackStates> task) { AssetPackStates assetPackStates; try { assetPackStates = task.getResult(); AssetPackState assetPackState = assetPackStates.packStates().get(assetPackName); } catch (RuntimeExecutionException e) { Log.d("MainActivity", e.getMessage()); return; })
以下 AssetPackState
方法提供了资源包的大小、迄今为止已下载的数量(如果请求)以及已传输到应用的数量
要获取资源包的状态,请使用 status()
方法,该方法将状态作为整数返回,该整数对应于 AssetPackStatus
类中的常量字段。尚未安装的资源包的状态为 AssetPackStatus.NOT_INSTALLED
。
如果请求失败,请使用 errorCode()
方法,其返回值对应于 AssetPackErrorCode
类中的常量字段。
安装
使用 requestFetch()
或 fetch()
方法首次下载资源包或调用更新资源包以完成
Kotlin
suspend fun AssetPackManager.requestFetch(packs: List<String>): AssetPackStates
Java
Task<AssetPackStates> fetch(List<String> packNames)
此方法返回一个 AssetPackStates
对象,其中包含包列表及其初始下载状态和大小。如果通过 requestFetch()
或 fetch()
请求的资源包已在下载,则返回下载状态,并且不会启动其他下载。
监控下载状态
你应该实现一个 AssetPackStateUpdatedListener
来跟踪资源包的安装进度。状态更新按包细分,以支持跟踪各个资源包的状态。你可以在所有其他下载完成之前开始使用可用的资源包。
Kotlin
fun registerListener(listener: AssetPackStateUpdatedListener) fun unregisterListener(listener: AssetPackStateUpdatedListener)
Java
void registerListener(AssetPackStateUpdatedListener listener) void unregisterListener(AssetPackStateUpdatedListener listener)
大型下载
如果下载大于 200 MB 且用户未连接 Wi-Fi,则在用户明确同意使用移动数据连接继续下载之前,下载不会开始。类似地,如果下载很大且用户断开 Wi-Fi 连接,则下载将暂停,并且需要明确同意才能使用移动数据连接继续。已暂停的包的状态为 WAITING_FOR_WIFI
。要触发 UI 流程以提示用户确认,请使用 showConfirmationDialog()
方法。
请注意,如果应用未调用此方法,则下载将暂停,并且仅当用户重新连接到 Wi-Fi 连接时才会自动恢复。
所需的用户信息
如果包具有 REQUIRES_USER_CONFIRMATION
状态,则在用户接受使用 showConfirmationDialog()
显示的对话框之前,下载不会继续。当 Play 未识别应用时,例如应用被旁加载时,可能会出现此状态。请注意,在这种情况下调用 showConfirmationDialog()
将导致应用更新。更新后,你需要再次请求资源。
以下是一个侦听器实现示例
Kotlin
private val activityResultLauncher = registerForActivityResult( ActivityResultContracts.StartIntentSenderForResult() ) { result -> if (result.resultCode == RESULT_OK) { Log.d(TAG, "Confirmation dialog has been accepted.") } else if (result.resultCode == RESULT_CANCELED) { Log.d(TAG, "Confirmation dialog has been denied by the user.") } } assetPackManager.registerListener { assetPackState -> when(assetPackState.status()) { AssetPackStatus.PENDING -> { Log.i(TAG, "Pending") } AssetPackStatus.DOWNLOADING -> { val downloaded = assetPackState.bytesDownloaded() val totalSize = assetPackState.totalBytesToDownload() val percent = 100.0 * downloaded / totalSize Log.i(TAG, "PercentDone=" + String.format("%.2f", percent)) } AssetPackStatus.TRANSFERRING -> { // 100% downloaded and assets are being transferred. // Notify user to wait until transfer is complete. } AssetPackStatus.COMPLETED -> { // Asset pack is ready to use. Start the game. } AssetPackStatus.FAILED -> { // Request failed. Notify user. Log.e(TAG, assetPackState.errorCode()) } AssetPackStatus.CANCELED -> { // Request canceled. Notify user. } AssetPackStatus.WAITING_FOR_WIFI, AssetPackStatus.REQUIRES_USER_CONFIRMATION -> { if (!confirmationDialogShown) { assetPackManager.showConfirmationDialog(activityResultLauncher); confirmationDialogShown = true } } AssetPackStatus.NOT_INSTALLED -> { // Asset pack is not downloaded yet. } AssetPackStatus.UNKNOWN -> { Log.wtf(TAG, "Asset pack status unknown") } } }
Java
assetPackStateUpdateListener = new AssetPackStateUpdateListener() { private final ActivityResultLauncher<IntentSenderRequest> activityResultLauncher = registerForActivityResult( new ActivityResultContracts.StartIntentSenderForResult(), new ActivityResultCallback<ActivityResult>() { @Override public void onActivityResult(ActivityResult result) { if (result.getResultCode() == RESULT_OK) { Log.d(TAG, "Confirmation dialog has been accepted."); } else if (result.getResultCode() == RESULT_CANCELED) { Log.d(TAG, "Confirmation dialog has been denied by the user."); } } }); @Override public void onStateUpdate(AssetPackState assetPackState) { switch (assetPackState.status()) { case AssetPackStatus.PENDING: Log.i(TAG, "Pending"); break; case AssetPackStatus.DOWNLOADING: long downloaded = assetPackState.bytesDownloaded(); long totalSize = assetPackState.totalBytesToDownload(); double percent = 100.0 * downloaded / totalSize; Log.i(TAG, "PercentDone=" + String.format("%.2f", percent)); break; case AssetPackStatus.TRANSFERRING: // 100% downloaded and assets are being transferred. // Notify user to wait until transfer is complete. break; case AssetPackStatus.COMPLETED: // Asset pack is ready to use. Start the game. break; case AssetPackStatus.FAILED: // Request failed. Notify user. Log.e(TAG, assetPackState.errorCode()); break; case AssetPackStatus.CANCELED: // Request canceled. Notify user. break; case AssetPackStatus.WAITING_FOR_WIFI: case AssetPackStatus.REQUIRES_USER_CONFIRMATION: if (!confirmationDialogShown) { assetPackManager.showConfirmationDialog(activityResultLauncher); confirmationDialogShown = true; } break; case AssetPackStatus.NOT_INSTALLED: // Asset pack is not downloaded yet. break; case AssetPackStatus.UNKNOWN: Log.wtf(TAG, "Asset pack status unknown") break; } } }
或者,你可以使用 getPackStates()
方法获取当前下载的状态。AssetPackStates
包含下载进度、下载状态和任何失败错误代码。
访问资源包
下载请求达到 COMPLETED
状态后,你可以使用文件系统调用访问资源包。使用 getPackLocation()
方法获取资源包的根文件夹。
资源存储在资源包根目录中的 assets
目录中。你可以使用便捷方法 assetsPath()
获取 assets
目录的路径。使用以下方法获取特定资源的路径
Kotlin
private fun getAbsoluteAssetPath(assetPack: String, relativeAssetPath: String): String? { val assetPackPath: AssetPackLocation = assetPackManager.getPackLocation(assetPack) // asset pack is not ready ?: return null val assetsFolderPath = assetPackPath.assetsPath() // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets") return FilenameUtils.concat(assetsFolderPath, relativeAssetPath) }
Java
private String getAbsoluteAssetPath(String assetPack, String relativeAssetPath) { AssetPackLocation assetPackPath = assetPackManager.getPackLocation(assetPack); if (assetPackPath == null) { // asset pack is not ready return null; } String assetsFolderPath = assetPackPath.assetsPath(); // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets"); String assetPath = FilenameUtils.concat(assetsFolderPath, relativeAssetPath); return assetPath; }
其他 Play Asset Delivery API 方法
以下是一些你可能希望在你的应用中使用的其他 API 方法。
取消请求
使用 cancel()
取消活动的资源包请求。请注意,此请求是尽力而为的操作。
删除资源包
使用 requestRemovePack()
或 removePack()
安排删除资源包。
获取多个资源包的位置
使用 getPackLocations()
批量查询多个资源包的状态,它返回资源包及其位置的映射。getPackLocations()
返回的映射包含当前已下载并处于最新状态的每个包的条目。
下一步
在本地和 Google Play 上测试 Play Asset Delivery。