Use the steps in this guide to access your app's asset packs from your Java code.
Build for Kotlin and Java
Use the following steps to build Play Asset Delivery into your project's Android App Bundle. You don't need to use Android Studio to perform these steps.
Update the version of the Android Gradle plugin in your project's
build.gradle
file to4.0.0
or later.In the top-level directory of your project, create a directory for the asset pack. This directory name is used as the asset pack name. Asset pack names must start with a letter and can only contain letters, numbers, and underscores.
In the asset pack directory, create a
build.gradle
file and add the following code. Make sure to specify the name of the asset pack and only one delivery type: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 ]") } }
In the project's app
build.gradle
file, add the name of every asset pack in your project as shown below: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") }
In the project's
settings.gradle
file, include all asset packs in your project as shown below: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")
In the asset pack directory, create the following subdirectory:
src/main/assets
.Place assets in the
src/main/assets
directory. You can create subdirectories in here as well. The directory structure for your app should now look like the following:build.gradle
settings.gradle
app/
asset-pack-name/build.gradle
asset-pack-name/src/main/assets/your-asset-directories
Build the Android App Bundle with Gradle. In the generated app bundle, the root-level directory now includes the following:
asset-pack-name/manifest/AndroidManifest.xml
: Configures the asset pack's identifier and delivery modeasset-pack-name/assets/your-asset-directories
: Directory that contains all assets delivered as part of the asset pack
Gradle generates the manifest for each asset pack and outputs the
assets/
directory for you.(Optional) Include the Play Asset Delivery Library if you plan to use fast-follow and on-demand delivery
Groovy
implementation "com.google.android.play:asset-delivery:2.3.0" // For Kotlin use asset-delivery-ktx implementation "com.google.android.play:asset-delivery-ktx:2.3.0"
Kotlin
implementation("com.google.android.play:asset-delivery:2.3.0") // For Kotlin use core-ktx implementation("com.google.android.play:asset-delivery-ktx:2.3.0")
(Optional) Configure your app bundle to support different texture compression formats.
Integrate with the Play Asset Delivery API
The Play Asset Delivery Java API
provides the
AssetPackManager
class for requesting asset packs, managing downloads, and accessing the assets. Make sure to Add the Play Asset Delivery Library into your project first.
You implement this API according to the delivery type of the asset pack you wish to access. These steps are shown in the following flowchart.
Install-time delivery
Asset packs configured as install-time
are immediately available at app
launch. Use the Java
AssetManager API to access assets
served in this mode:
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");
Fast-follow and on-demand delivery
The following sections show how to get information about asset packs before
downloading them, how to call the API to start the download, and then how to
access the downloaded packs. These sections apply to fast-follow
and
on-demand
asset packs.
Check status
Each asset pack is stored in a separate folder in the app's internal storage.
Use the
getPackLocation()
method to determine the root folder of an asset pack. This method returns the
following values:
Return value | Status |
---|---|
A valid AssetPackLocation object |
Asset pack root folder is ready for immediate access at assetsPath() |
null |
Unknown asset pack or assets are not available |
Get download information about asset packs
Apps are required to disclose the size of the download before fetching the asset
pack. Use the
requestPackStates()
or the getPackStates()
method to determine the size of the download and whether the pack is already
downloading.
Kotlin
suspend fun requestPackStates(packNames: List<String>): AssetPackStates
Java
Task<AssetPackStates> getPackStates(List<String> packNames)
requestPackStates()
is a suspend function returning an
AssetPackStates
object while the getPackStates()
is an asynchronous method that returns a Task<AssetPackStates>
. The
packStates()
method of an AssetPackStates
object returns a Map<String,
AssetPackState>
. This map contains the state of each requested asset
pack, keyed by its name:
Kotlin
AssetPackStates#packStates(): Map<String, AssetPackState>
Java
Map<String, AssetPackState> AssetPackStates#packStates()
The final request is shown by the following:
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; })
The following
AssetPackState
methods provide the size of the asset pack, the downloaded amount so far (if
requested), and the amount already transferred to the app:
To get the status of an asset pack, use the
status()
method, which returns the status as an integer that corresponds to a constant
field in the
AssetPackStatus
class. An asset pack that's not installed yet has the status
AssetPackStatus.NOT_INSTALLED
.
If a request fails, use the
errorCode()
method, whose return value corresponds to a constant field in the
AssetPackErrorCode
class.
Install
Use the requestFetch()
or
fetch()
method to download an asset pack for the first time or call for the update of an
asset pack to complete:
Kotlin
suspend fun AssetPackManager.requestFetch(packs: List<String>): AssetPackStates
Java
Task<AssetPackStates> fetch(List<String> packNames)
This method returns an
AssetPackStates
object containing a list of packs and their initial download states and sizes.
If an asset pack requested via requestFetch()
or fetch()
is already downloading, the download
status is returned and no additional download is started.
Monitor download states
You should implement an
AssetPackStateUpdatedListener
to track the installation progress of asset
packs. The status updates are broken down per pack to support tracking the
status of individual asset packs. You can start using available asset packs
before all other downloads for your request have completed.
Kotlin
fun registerListener(listener: AssetPackStateUpdatedListener) fun unregisterListener(listener: AssetPackStateUpdatedListener)
Java
void registerListener(AssetPackStateUpdatedListener listener) void unregisterListener(AssetPackStateUpdatedListener listener)
Large downloads
If the download is larger than 200 MB and the user is not on Wi-Fi, the download
does not start until the user explicitly gives their consent to proceed with the
download using a mobile data connection. Similarly, if the download is large and
the user loses Wi-Fi, the download is paused and explicit consent is required to
proceed using a mobile data connection. A paused pack has state
WAITING_FOR_WIFI
. To trigger the UI flow to prompt the user for consent, use
the showConfirmationDialog()
method.
Note that if the app does not call this method, the download is paused and will resume automatically only when the user is back on a Wi-Fi connection.
Required user confirmation
If a pack has the REQUIRES_USER_CONFIRMATION
status, the download won't
proceed until the user accepts the dialog that is shown with
showConfirmationDialog()
.
This status can occur when the app is not recognized by Play—for example, if
the app was side-loaded.
Note that calling
showConfirmationDialog()
in this case will cause the app to be updated. After the update, you will need
to request the assets again.
The following is an example implementation of a listener:
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; } } }
Alternatively, you can use the
getPackStates()
method to get the status of current downloads.
AssetPackStates
contains the download progress, download status, and any failure error codes.
Access asset packs
You can access an asset pack using file system calls after the download request
reaches the
COMPLETED
state. Use the
getPackLocation()
method to get the root folder of the asset pack.
Assets are stored in the assets
directory within the asset pack root
directory. You can get the path to the assets
directory by using the
convenience method
assetsPath()
.
Use the following method to get the path to a specific asset:
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; }
Other Play Asset Delivery API methods
The following are some additional API methods you may want to use in your app.
Cancel request
Use
cancel()
to cancel an active asset pack request. Note that this request is a best-effort
operation.
Remove an asset pack
Use
requestRemovePack()
or
removePack()
to schedule the removal of an asset pack.
Get locations of multiple asset packs
Use
getPackLocations()
to query the status of multiple asset packs in bulk, which returns a map of
asset packs and their locations. The map returned by getPackLocations()
contains an entry for each pack that is currently downloaded and up-to-date.
Next step
Test Play Asset Delivery locally and from Google Play.