Download modules with the Play Core Library

Download modules with the Play Core Library

With Google Play’s Dynamic Delivery, your app can download dynamic feature modules on demand to devices running Android 5.0 (API level 21) and higher. Your app simply needs to call APIs in the Play Core Library to download and install those modules as required, and the Google Play Store pushes only the code and resources needed for that module to the device. You can also use this API to download on demand modules for your Android Instant Apps.
To learn how to first add dynamic feature modules to your project and configure them to be available on demand, read Create a dynamic feature module.
Additionally, after you’ve read through this guide, see the API in action by trying the Play Core API sample app.


Include the Play Core Library in your project

Before you can start using the Play Core Library, you need to first import it into your app module as a Gradle dependency, as shown below:
// In your app’s build.gradle file:
...
dependencies {
    // This dependency is downloaded from the Google’s Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation 'com.google.android.play:core:1.4.0'
    ...
}

Request an on demand module

When your app needs to use a dynamic feature module, it can request one while it's in the foreground through the SplitInstallManager class. When making a request, your app needs to specify the name of the module as defined by the split element in the target module’s manifest. When you create a dynamic feature module using Android Studio, the build system uses the Module name you provide to inject this property into the module's manifest at compile time. For more information, read about the Dynamic feature module manifests.
For example, consider an app that has an on demand module to capture and send picture messages using the device’s camera, and this on demand module specifies split="pictureMessages" in its manifest. The following sample uses SplitInstallManager to request the pictureMessages module (along with an additional module for some promotional filters):

// Creates an instance of SplitInstallManager.
val splitInstallManager = SplitInstallManagerFactory.create(context)
// Creates a request to install a module.
val request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build()

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener { sessionId -> ... }
    .addOnFailureListener { exception ->  ... }

When your app requests an on demand module, the Play Core Library employs a “fire-and-forget” strategy. That is, it sends the request to download the module to the platform, but it does not monitor whether the installation succeeded. To move the user journey forward after installation or gracefully handle errors, make sure you monitor the request state.
Note: It's okay to request a dynamic feature module that’s already installed on the device. The API instantly considers the request as completed if it detects the module is already installed. Additionally, after a module is installed, Google Play keeps it updated automatically. That is, when you upload a new version of your app bundle, the platform updates all installed APKs that belong to your app. For more information, read Manage app updates.
To have immediate access to the module's code and resources, your app needs to enable SplitCompat. Note that SplitCompat is not required for Android Instant Apps— they always have immediate access to feature modules.

Defer installation of on demand modules

If you do not need your app to immediately download and install an on demand module, you can defer installation for when the app is in the background. For example, if you want to preload some promotional material for a later launch of your app.
You can specify a module to be download later using the deferredInstall() method, as shown below. And, unlike SplitInstallManager.startInstall(), your app does not need to be in the foreground to initiate a request for a deferred installation.

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(listOf("promotionalFilters"))

Requests for deferred installs are best-effort and you cannot track their progress. So, before trying to access a module you have specified for deferred installation, you should check that the module has been installed. If you need the module to be available immediately, instead use SplitInstallManager.startInstall() to request it, as shown in the previous section.

Monitor the request state

To be able to update a progress bar, fire an intent after installation, or gracefully handle a request error, you need to listen for state updates from the asynchronous SplitInstallManager.startInstall() task. Before you can start receiving updates for your install request, register a listener and get the session ID for the request, as shown below.

// Initializes a variable to later track the session ID for a given request.
var mySessionId = 0
// Creates a listener for request status updates.
val listener = SplitInstallStateUpdatedListener { state ->
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
}
// Registers the listener.
splitInstallManager.registerListener(listener)
...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener { sessionId -> mySessionId = sessionId }
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener { exception ->
        // Handle request errors.
    }
// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener)

Handle request errors

You should gracefully handle failures downloading or installing a module using addOnFailureListener(), as shown below:

splitInstallManager
    .startInstall(request)
    .addOnFailureListener { exception ->
        when ((exception as SplitInstallException).errorCode) {
            SplitInstallErrorCode.NETWORK_ERROR -> {
                // Display a message that requests the user to establish a
                // network connection.
            }
            SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> checkForActiveDownloads()
            ...
        }
    }
fun checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .sessionStates
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                // Check for active sessions.
                for (state in task.result) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        }
}

The table below describes the error states your app may need to handle:
Error code Description Suggested action
ACTIVE_SESSIONS_LIMIT_EXCEEDED The request is rejected because there is at least one existing request that is currently downloading. Check if there are any requests that are still downloading, as shown in the sample above.
MODULE_UNAVAILABLE Google Play is unable to find the requested module based on the current installed version of the app, device, and user’s Google Play account. If the user does not have access to the module, notify them.
INVALID_REQUEST Google Play received the request, but the request is not valid. Verify that the information included in the request is complete and accurate.
SESSION_NOT_FOUND A session for a given session ID was not found. If you’re trying to monitor the state of a request by its session ID, make sure that the session ID is correct.
API_NOT_AVAILABLE The Play Core Library is not supported on the current device. That is, the device is not able to download and install features on demand. For devices running Android 4.4 (API level 20) or lower, you should include dynamic feature modules at install time using the dist:fusing manifest property. To learn more, read about the Dynamic feature module manifest.
ACCESS_DENIED The app is unable to register the request because of insufficient permissions. This typically occurs when the app is in the background. Attempt the request when the app returns to the foreground.
NETWORK_ERROR The request failed because of a network error. Prompt the user to either establish a network connection or change to a different network.
INCOMPATIBLE_WITH_EXISTING_SESSION The request contains one or more modules that have already been requested but have not yet been installed. Either create a new request that does not include modules that your app has already requested, or wait for all currently requested modules to finish installing before retrying the request. Keep in mind, requesting a module that has already been installed does not resolve in an error.
SERVICE_DIED The service responsible for handling the request has died. Retry the request. This error code is be exposed as an update to your SplitInstallStateUpdatedListener with status FAILED and session ID -1.
If a user requests downloading an on demand module and an error occurs, consider displaying a dialog that provides two options for the user: Try again (which attempts the request again) and Cancel (which abandons the request). For additional support, you should also provide Help link that directs users to the Google Play Help center.

Handle state updates

After you register a listener and record the session ID for your request, use StateUpdatedListener.onStateUpdate() to handle state changes, as shown below.

override fun onStateUpdate(state : SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIES) {
       // Retry the request.
       return
    }
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.DOWNLOADING -> {
              val totalBytes = state.totalBytesToDownload()
              val progress = state.bytesDownloaded()
              // Update progress bar.
            }
            SplitInstallSessionStatus.INSTALLED -> {

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
            }
        }
    }
}

The possible states for your install request are described in the table below.
Request state Description Suggested action
PENDING The request has been accepted and the download should start soon. Initialize UI components, such as a progress bar, to provide the user feedback on the download.
REQUIRES_USER_CONFIRMATION The download requires user confirmation. This is most likely due to the size of the download being larger than 10 MB. Prompt the user to accept the download request. To learn more, go to the section about how to obtain user confirmation.
DOWNLOADING Download is in progress. If you provide a progress bar for the download, use the SplitInstallSessionState.bytesDownloaded() and SplitInstallSessionState.totalBytesToDownload() methods to update the UI (see the code sample above this table).
DOWNLOADED The device has downloaded the module but installation has no yet begun. Apps should enable SplitCompat to have immediate access to downloaded modules and avoid seeing this state. Otherwise, the download transitions to INSTALLED, and your app access to its code and resources, only at some point after the app enters the background.
INSTALLING The device is currently installing the module. Update the progress bar. This state is typically short.
INSTALLED The module is installed on the device. Access code and resource in the module to continue the user journey. If the module is for an Android Instant App running on Android 8.0 (API level 26) or higher, you need to use splitInstallHelper to update app components with the new module.
FAILED The request failed before the module was installed on the device. Prompt the user to either retry the request or cancel it.
CANCELING The device is in the process of cancelling the request. To learn more, go to the section about how to cancel an install request.
CANCELED The request has been cancelled.

Obtain user confirmation

In some cases, Google Play may require user confirmation before satisfying a download request. For example, if a request requires a large download and the device is using mobile data. In such cases, the status for the request reports REQUIRES_USER_CONFIRMATION, and your app needs to obtain user confirmation before the device is able to download and install the modules in the request. To obtain confirmation, your app should prompt the user as follows:

override fun onSessionStateUpdate(state: SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a dialog for the user to either “Download”
        // or “Cancel” the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          /* activity = */ this,
          // You use this request code to later retrieve the user's decision.
          /* requestCode = */ MY_REQUEST_CODE)
    }
    ...
 }

The status for the request is updated depending on the user response:
  • If the user selects “Download”, the request status changes to PENDING and the download proceeds.
  • If the user selects “Cancel”, the request status changes to CANCELED.
  • If the user does not make a selection before the dialog is destroyed, the request status remains as REQUIRES_USER_CONFIRMATION. Your app can prompt the user again to complete the request.
To receive a callback with the user's response, use onActivityResult(), as shown below.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  if (requestCode == MY_REQUEST_CODE) {
    // Handle the user's decision. For example, if the user selects "Cancel",
    // you may want to disable certain functionality that depends on the module.
  }
}

Cancel an install request

If your app needs to cancel a request before it is installed, it can invoke the cancelInstall() method using the request’s session ID, as shown below.

SplitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId)

Immediately access modules

Your app needs to enable the SplitCompat Library in order to immediately access code and resources from a downloaded module—that is, before an app restart. In addition, you need to enable SplitCompat for any activity that your app loads from a feature module.
You should note, however, the platform experiences the following restrictions to accessing contents of a module prior to an app restart:
  • The platform can not apply any new manifest entries introduced by the module.
  • The platform can not access the module’s resources for system UI components, such as notifications. If you need to use such resources immediately, consider including those resource in the base module of your app.
You can enable SplitCompat for your app using one of the methods described below.

Declare SplitCompatApplication in the manifest

The simplest way to enable SplitCompat is to declare SplitCompatApplication as the Application subclass in your app’s manifest, as shown below:
<application
    ...
    android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
</application>
After the app is installed on a device, you can access code and resources from downloaded dynamic feature modules automatically.

Invoke SplitCompat at runtime

You can also enable SplitCompat in specific activities or services at runtime. Enabling SplitCompat this way is required to launch activities included in feature modules immediately after module installation. To do this, override attachBaseContext as seen below.
If you have a custom Application class, have it instead extend SplitCompatApplication in order to enable SplitCompat for your app, as shown below:

class MyApplication : SplitCompatApplication() {
    ...
}

SplitCompatApplication simply overrides ContextWrapper.attachBaseContext() to include SplitCompat.install(Context applicationContext). If you don’t want your Application class to extend SplitCompatApplication, you can override the attachBaseContext() method manually, as follows:

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this)
}

If your on demand module is compatible with both instant apps and installed apps, you can invoke SplitCompat conditionally, as follows:

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this)
    }
}

Access code and resources from installed modules

In most cases, after a request for an on demand module reports as INSTALLED, you can start using its code and resources as if it were a part of the base APK.
However, accessing code and resources from a new module requires a refreshed app Context. A context that your app creates before installing a module (for example, one that's already stored in a variable) does not contain the content of the new module. But a fresh context does—this can be obtained, for example, using createPackageContext. If you access an installed module's content by firing an intent to launch a component, that component's context also contains the content of the new module, provided that the target component enables SplitCompat.

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                val newContext = context.createPackageContext(context.packageName, 0)
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                val am = newContext.assets
            }
        }
    }
}

Android Instant Apps on Android 8.0 and higher

When requesting an on demand module for an Android Instant App, an app restart isn’t required, regardless of the version of Android the device is using. However, on Android 8.0 (API level 26) and higher, when such a request reports as INSTALLED, you need to update the app with the context of the new module through a call to SplitInstallHelper.updateAppInfo(Context context). Otherwise, the app is not yet aware of the module’s code and resources. After updating the app’s metadata, you should load the module’s contents during the next main thread event by invoking a new Handler, as shown below:

override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                // You need to perform the following only for Android Instant Apps
                // running on Android 8.0 (API level 26) and higher.
                if (BuildCompat.isAtLeastO()) {
                    // Updates the app’s context with the code and resources of the
                    // installed module.
                    SplitInstallHelper.updateAppInfo(context)
                    Handler().post {
                        // Loads contents from the module using AssetManager
                        val am = context.assets
                        ...
                    }
                }
            }
        }
    }
}

Load C/C++ libraries

If you want to load C/C++ libraries from a module that the device has already downloaded, use SplitInstallHelper.loadLibrary(Context context, String libName), as shown below:

override fun onStateUpdate(state: SplitInstallSessionState) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.INSTALLED -> {
                // Updates the app’s context as soon as a module is installed.
                val newContext = context.createPackageContext(context.packageName, 0)
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, “my-cpp-lib”)
                ...
            }
        }
    }
}

Manage installed modules

To check which dynamic feature modules are currently installed on the device, you can call SplitInstallManager.getInstalledModules(), which returns a Set<String> of the names of the installed modules, as shown below.



val installedModules: Set<String> = splitInstallManager.installedModules

Uninstall modules

You can request the device to uninstall modules by invoking SplitInstallManager.deferredUninstall(List<String> moduleNames), as shown below.

// Specifies two dynamic feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(listOf("pictureMessages", "promotionalFilters"))

Module uninstalls do not occur immediately. That is, the device uninstalls them in the background as needed to save storage space. You can confirm that the device has deleted a module by invoking SplitInstallManager.getInstalledModules() and inspecting the result, as described in the previous section.

Download additional language resources

Through Dynamic Delivery, devices download only the code and resources they require to run your app. So, for language resources, a user’s device downloads only your app’s language resources that match the one or more languages currently selected in the device’s settings.
If you want your app to have access to additional language resources—for example, to implement an in-app language picker, you can use the Play Core Library to download them on demand. The process is similar to that of downloading a dynamic feature module, as shown below.

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply()
...
// Creates a request to download and install additional language resources.
val request = SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build()
// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request)

The request is handled as if it were a request for a dynamic feature module. That is, you can monitor the request state like you normally would.
If your app doesn't require the additional language resources immediately, you can defer the installation for when the app is in the background, as shown below.

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Access downloaded language resources

To gain immediate access to downloaded language resources, your app needs to run the SplitCompat.install() method within the attachBaseContext() method of each activity that requires access to those resources, as shown below.

override fun attachBaseContext(base: Context) {
  super.attachBaseContext(base)
  SplitCompat.install(this)
}

For each activity you want to use language resources your app has downloaded, update the base context and set a new locale through its Configuration:

override fun attachBaseContext(base: Context) {
  val configuration = Configuration()
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
  val context = base.createConfigurationContext(configuration)
  super.attachBaseContext(context)
  SplitCompat.install(this)
}

In order for these changes to take effect, you have to recreate your activity after the new language is installed and ready to use. You can use the Activity#recreate() method.

when (state.status()) {
  SplitInstallSessionStatus.INSTALLED -> {
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate()
  }
  ...
}

Uninstall additional language resources

Similar to dynamic feature modules, you can uninstall additional resources at any time. Before requesting an uninstall, you may want to first determine which languages are currently installed, as follows.

val installedLanguages: Set<String> = splitInstallManager.installedLanguages

You can then decide which languages to uninstall using the deferredLanguageUninstall() method, as shown below.

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Additional resources

To learn more about using the Play Core Library, try the following resources.

Samples

  • PlayCore API sample, a sample app that demonstrates usage of the PlayCore API to request and download dynamic features.

Videos

Tidak ada komentar