Los módulos de funciones te permiten separar determinados recursos y funciones del módulo básico de tu app y, luego, incluirlos en el paquete de aplicación. Mediante Play Feature Delivery, los usuarios pueden, por ejemplo, descargar e instalar esos componentes a pedido después de haber instalado el APK base de tu app.
Por ejemplo, piensa en una app de mensajería de texto que incluye funcionalidad para capturar y enviar mensajes con imágenes, pero solo un pequeño porcentaje de usuarios envía mensajes con imágenes. Tal vez tenga sentido incluir los mensajes con imágenes en un módulo de funciones descargable. De esa manera, la descarga inicial de la app es más chica para todos los usuarios, y solo aquellos que envían mensajes con imágenes deben descargar ese componente adicional.
Ten en cuenta que este tipo de modularización requiere más esfuerzo, y posiblemente tengas que refactorizar el código existente de tu app, de modo que deberás considerar con cuidado cuáles de las funciones de la app sería mejor ofrecer a pedido. Para comprender mejor los casos de uso óptimos y las pautas para las funciones a pedido, consulta las prácticas recomendadas de UX para la entrega de funciones a pedido.
Si deseas modularizar gradualmente las funciones de la app con el paso del tiempo, sin habilitar opciones de entrega avanzadas, como la entrega a pedido, configura la entrega durante la instalación.
En esta página, encontrarás ayuda para agregar un módulo de funciones a tu proyecto de app y configurarlo para la entrega a pedido. Antes de comenzar, asegúrate de usar Android Studio 3.5 o una versión posterior y el complemento de Gradle para Android versión 3.5.0 o posterior.
Cómo configurar un nuevo módulo para entrega a pedido
La forma más fácil de crear un nuevo módulo de funciones es con Android Studio 3.5 o una versión posterior. Dado que los módulos de funciones tienen una dependencia inherente del módulo de la app de base, solo puedes agregarlos a proyectos de apps existentes.
Para agregar un módulo de funciones a tu proyecto de app con Android Studio, haz lo siguiente:
- Si aún no lo hiciste, abre tu proyecto de app en el IDE.
- Selecciona File > New > New Module en la barra de menú.
- En el cuadro de diálogo Create New Module, elige Dynamic Feature Module y haz clic en Next.
- En la sección Configure your new module, haz lo siguiente:
- En el menú desplegable Base application module, selecciona el módulo básico de tu app.
- En Module name, especifica el nombre del módulo. El IDE usa ese nombre para identificar el módulo como un subproyecto de Gradle en tu archivo de configuración de Gradle. Cuando compilas tu paquete de aplicación, Gradle usa el último elemento del nombre del subproyecto para insertar el atributo
<manifest split>
en el manifiesto del módulo de funciones. - En Package name, especifica el nombre de paquete del módulo. De forma predeterminada, Android Studio sugiere un nombre de paquete que combina el nombre del paquete raíz del módulo básico y el nombre del módulo que especificaste en el paso anterior.
- En Minimum API level, elige el nivel mínimo de API que admitirá el módulo. Este valor debe coincidir con el del módulo básico.
- Haz clic en Next.
En la sección Module Download Options, haz lo siguiente:
Especifica el título del módulo en un máximo de 50 caracteres. La plataforma usa ese título para identificar el módulo ante los usuarios cuando, por ejemplo, confirma si el usuario desea descargar el módulo. Por esta razón, el módulo básico de la app debe incluir el título del módulo como un recurso de cadenas, que se puede traducir. Cuando se crea el módulo con Android Studio, el IDE agrega el recurso de cadenas al módulo básico y, luego, inserta la siguiente entrada en el manifiesto del módulo de funciones:
<dist:module ... dist:title="@string/feature_title"> </dist:module>
En el menú desplegable en Install-time inclusion, selecciona Do not include module at install-time. Android Studio introduce lo siguiente en el manifiesto del módulo para reflejar tu elección:
<dist:module ... > <dist:delivery> <dist:on-demand/> </dist:delivery> </dist:module>
Marca la casilla junto a Fusing si deseas que ese módulo esté disponible para los dispositivos que ejecutan Android 4.4 (nivel de API 20) y versiones anteriores, y se lo incluya en APK múltiples. Eso significa que puedes habilitar el comportamiento a pedido para este módulo e inhabilitar la fusión para omitirlo en dispositivos que no admiten la descarga e instalación de APK divididos. Android Studio introduce lo siguiente en el manifiesto del módulo para reflejar tu elección:
<dist:module ...> <dist:fusing dist:include="true | false" /> </dist:module>
Haz clic en Finish.
Cuando Android Studio termine de crear tu módulo, abre el panel Project y revisa el contenido (selecciona View > Tool Windows > Project en la barra de menú). El código, los recursos y la organización predeterminados deben ser similares a los del módulo de app estándar.
Luego, deberás implementar la función de instalación a pedido mediante la biblioteca de Play Feature Delivery.
Cómo incluir la Biblioteca de Play Feature Delivery en tu proyecto
Antes de comenzar, debes agregar la biblioteca de Play Feature Delivery a tu proyecto.
Cómo solicitar un módulo a pedido
Si tu app necesita usar un módulo de funciones, puede solicitar uno mientras está en primer plano mediante la clase SplitInstallManager
. Cuando tu app envía una solicitud, debe especificar el nombre del módulo como lo define el elemento split
en el manifiesto del módulo objetivo. Cuando creas un módulo de funciones con Android Studio, el sistema de compilación usa el nombre del módulo que proporcionas para incorporar esta propiedad en el manifiesto del módulo durante el tiempo de compilación.
Para obtener más información, lee sobre los manifiestos del módulo de funciones.
Por ejemplo, imagina una app que tiene un módulo a pedido para capturar y enviar mensajes con imágenes a través de la cámara del dispositivo y este módulo a pedido tiene split="pictureMessages"
especificado en su manifiesto. En el siguiente ejemplo, se usa SplitInstallManager
para solicitar el módulo de pictureMessages
(además del módulo adicional para algunos filtros promocionales):
Kotlin
// 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 -> ... }
Java
// Creates an instance of SplitInstallManager. SplitInstallManager splitInstallManager = SplitInstallManagerFactory.create(context); // Creates a request to install a module. SplitInstallRequest 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 -> { ... });
Cuando tu app solicita un módulo a pedido, la biblioteca de Play Feature Delivery utiliza una estrategia de "activar y olvidar". Es decir, envía la solicitud para descargar el módulo a la plataforma, pero no supervisa si la instalación se realizó correctamente. Para avanzar en el recorrido del usuario después de la instalación o administrar errores correctamente, asegúrate de supervisar el estado de la solicitud.
Nota: Es posible solicitar un módulo de funciones que ya está instalado en el dispositivo. La API considera inmediatamente que se completó la solicitud si detecta que el módulo ya está instalado. Además, después de instalar un módulo, Google Play lo actualiza automáticamente. Es decir, cuando subes una versión nueva de tu paquete de aplicación, la plataforma actualiza todos los APK instalados que pertenecen a tu app. Si deseas obtener más información, consulta Cómo administrar actualizaciones de apps.
Para obtener acceso al código y a los recursos del módulo, debes habilitar SplitCompat para tu app. Ten en cuenta que SplitCompat no es necesario para las Apps instantáneas Android.
Cómo posponer la instalación de módulos a pedido
Si no necesitas que tu app descargue e instale inmediatamente un módulo a pedido, puedes postergar la instalación para cuando la app esté en segundo plano. Por ejemplo, si quieres precargar material promocional para un lanzamiento posterior de tu app.
Puedes especificar que un módulo se descargue más adelante mediante el método deferredInstall()
, como se muestra a continuación. Además, a diferencia de SplitInstallManager.startInstall()
, tu app no necesita estar en primer plano para iniciar la solicitud de una instalación postergada.
Kotlin
// 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"))
Java
// 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(Arrays.asList("promotionalFilters"));
Las solicitudes de instalaciones postergadas se incluyen en la categoría de mejor esfuerzo y no puedes realizar un seguimiento de su progreso. Por lo tanto, antes de intentar acceder a un módulo que especificaste para la instalación diferida, debes verificar que el módulo se haya instalado. Si necesitas que el módulo esté disponible inmediatamente, usa SplitInstallManager.startInstall()
en su lugar para solicitarlo, como se muestra en la sección anterior.
Cómo supervisar el estado de la solicitud
Para poder actualizar una barra de progreso, activar un intent después de la instalación o manejar correctamente un error de solicitud, debes detectar actualizaciones de estado desde la tarea SplitInstallManager.startInstall()
asíncrona.
Antes de comenzar a recibir actualizaciones para tu solicitud de instalación, registra un objeto de escucha y obtén el ID de sesión de la solicitud, como se muestra a continuación.
Kotlin
// 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)
Java
// Initializes a variable to later track the session ID for a given request. int mySessionId = 0; // Creates a listener for request status updates. SplitInstallStateUpdatedListener listener = 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);
Cómo manejar los errores de solicitud
Ten en cuenta que la instalación de módulos de funciones a pedido puede fallar en algún momento, del mismo modo que la instalación de la app no siempre se realiza de forma correcta. La falla en la instalación puede deberse a problemas de poco almacenamiento en el dispositivo, conectividad de red o al usuario que no accedió a Google Play Store. Si quieres obtener sugerencias para controlar correctamente estas situaciones desde la perspectiva del usuario, consulta nuestros Lineamientos de UX para las entregas a pedido.
En cuanto al código, deberías controlar las fallas para descargar o instalar un módulo con addOnFailureListener()
, como se muestra a continuación:
Kotlin
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. } } } } }
Java
splitInstallManager .startInstall(request) .addOnFailureListener(exception -> { switch (((SplitInstallException) exception).getErrorCode()) { case SplitInstallErrorCode.NETWORK_ERROR: // Display a message that requests the user to establish a // network connection. break; case SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED: checkForActiveDownloads(); ... }); void checkForActiveDownloads() { splitInstallManager // Returns a SplitInstallSessionState object for each active session as a List. .getSessionStates() .addOnCompleteListener( task -> { if (task.isSuccessful()) { // Check for active sessions. for (SplitInstallSessionState state : task.getResult()) { if (state.status() == SplitInstallSessionStatus.DOWNLOADING) { // Cancel the request, or request a deferred installation. } } } }); }
En la siguiente tabla, se describen los estados de error que es posible que tu app deba manejar:
Código de error | Descripción | Acción sugerida |
---|---|---|
ACTIVE_SESSIONS_LIMIT_EXCEEDED | Se rechaza la solicitud porque, actualmente, se está descargando, al menos, una solicitud existente. | Verifica si todavía se están descargando solicitudes, como se muestra en el ejemplo anterior. |
MODULE_UNAVAILABLE | Google Play no encuentra el módulo que se solicitó en función de la versión instalada actualmente de la app, el dispositivo y la cuenta de Google Play del usuario. | Si el usuario no tiene acceso al módulo, debes notificarlo al respecto. |
INVALID_REQUEST | Google Play recibió la solicitud, pero esta no es válida. | Verifica que la información incluida en la solicitud esté completa y sea precisa. |
SESSION_NOT_FOUND | No se encontró ninguna sesión para un ID de sesión determinado. | Si intentas supervisar el estado de una solicitud a partir de su ID de sesión, asegúrate de que este sea correcto. |
API_NOT_AVAILABLE | El dispositivo actual no admite la biblioteca de Play Feature Delivery. Es decir, el dispositivo no puede descargar ni instalar funciones a pedido. | Para los dispositivos con Android 4.4 (nivel de API 20) o versiones anteriores, debes incluir módulos de funciones en el tiempo de instalación. Para ello, usa la propiedad de manifiesto dist:fusing . Si deseas obtener más información, lee sobre el manifiesto del módulo de funciones.
|
NETWORK_ERROR | Falló la solicitud debido a un error de red. | Pídele al usuario que establezca una conexión de red o que se conecte a una diferente. |
ACCESS_DENIED | La app no puede registrar la solicitud debido a que los permisos no son suficientes. | Por lo general, esto sucede cuando la app está en segundo plano. Intenta ejecutar la solicitud cuando la app vuelva al primer plano. |
INCOMPATIBLE_WITH_EXISTING_SESSION | La solicitud contiene uno o más módulos que ya se solicitaron, pero que todavía no se instalaron. | Crea una solicitud nueva que no incluya módulos que tu app ya solicitó o espera a que todos los módulos solicitados actualmente terminen de instalarse antes de reintentarlo. Ten en cuenta que solicitar un módulo que ya se instaló no soluciona un error. |
SERVICE_DIED | El servicio responsable de manejar la solicitud no está disponible. | Vuelve a intentar ejecutar la solicitud.
Tu |
INSUFFICIENT_STORAGE | El dispositivo no tiene suficiente almacenamiento gratuito para instalar el módulo de funciones. | Notifica al usuario que no tiene almacenamiento suficiente para instalar esta función. |
SPLITCOMPAT_VERIFICATION_ERROR, SPLITCOMPAT_EMULATION_ERROR, SPLITCOMPAT_COPY_ERROR | SplitCompat no pudo cargar el módulo de funciones. | Estos errores deberían resolverse automáticamente después del próximo reinicio de la app. |
PLAY_STORE_NOT_FOUND | La app de Play Store no está instalada en el dispositivo. | Informa al usuario que se necesita la app de Play Store para descargar esta función. |
APP_NOT_OWNED | Google Play no instaló la app y no se puede descargar la función. Este error solo se puede producir para las instalaciones diferidas. | Si quieres que el usuario adquiera la app en Google Play, usa startInstall() , que puede obtener la confirmación de usuario necesaria. |
INTERNAL_ERROR | Se produjo un error interno en Play Store. | Vuelve a intentar ejecutar la solicitud. |
Si un usuario solicita descargar un módulo a pedido y se produce un error, puedes mostrar un diálogo en el que se ofrezcan dos opciones para el usuario: Try again (para volver a realizar la solicitud) y Cancel (para abandonar la solicitud). Para obtener asistencia adicional, también debes proporcionar un vínculo de ayuda que dirija a los usuarios al Centro de ayuda de Google Play.
Cómo manejar las actualizaciones de estado
Después de registrar un objeto de escucha y grabar el ID de sesión de tu solicitud, usa StateUpdatedListener.onStateUpdate()
a fin de manejar los cambios de estado, como se muestra a continuación.
Kotlin
override fun onStateUpdate(state : SplitInstallSessionState) { if (state.status() == SplitInstallSessionStatus.FAILED && state.errorCode() == SplitInstallErrorCode.SERVICE_DIED) { // 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. } } } }
Java
@Override public void onStateUpdate(SplitInstallSessionState state) { if (state.status() == SplitInstallSessionStatus.FAILED && state.errorCode() == SplitInstallErrorCode.SERVICE_DIES) { // Retry the request. return; } if (state.sessionId() == mySessionId) { switch (state.status()) { case SplitInstallSessionStatus.DOWNLOADING: int totalBytes = state.totalBytesToDownload(); int progress = state.bytesDownloaded(); // Update progress bar. break; case 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. } } }
Los posibles estados de tu solicitud de instalación se describen en la siguiente tabla.
Estado de solicitud | Descripción | Acción sugerida |
---|---|---|
PENDIENTE | Se aceptó la solicitud y la descarga debería comenzar pronto. | Inicializa los componentes de IU, como una barra de progreso, para brindar los comentarios del usuario sobre la descarga. |
REQUIRES_USER_CONFIRMATION | La descarga requiere la confirmación del usuario. Generalmente, este estado ocurre si la app no se instaló a través de Google Play. | Pídele al usuario que confirme la descarga de la función mediante GooglebPlay. Si deseas conocer más detalles, ve a la sección sobre cómo obtener información del usuario. |
DOWNLOADING | La descarga está en curso. | Si agregas una barra de progreso para la descarga, usa los métodos SplitInstallSessionState.bytesDownloaded() y SplitInstallSessionState.totalBytesToDownload() a fin de actualizar la IU (consulta la muestra de código ubicada arriba de esta tabla). |
DOWNLOADED | El dispositivo descargó el módulo, pero la instalación todavía no comenzó. | Las apps deberían habilitar SplitCompat para tener acceso a los módulos descargados y evitar ver este estado. Esto es necesario para acceder al código y a los recursos del módulo de funciones. |
INSTALANDO | El dispositivo está instalando el módulo. | Actualiza la barra de progreso. Por lo general, este estado es corto. |
INSTALLED | Se instaló el módulo en el dispositivo. | Accede al código y a los recursos en el módulo para continuar el recorrido del usuario. Si el módulo es para una App instantánea Android que ejecuta Android 8.0 (nivel de API 26) o versiones posteriores, usa |
ERROR | Falló la solicitud antes de que se pudiera instalar el módulo en el dispositivo. | Pídele al usuario que vuelva a enviar la solicitud o que la cancele. |
CANCELING | El dispositivo está en proceso de cancelar la solicitud. | Para obtener más información, ve a la sección sobre cómo cancelar una solicitud de instalación. |
CANCELED | Se canceló la solicitud. |
Cómo obtener la confirmación del usuario
En algunos casos, es posible que Google Play requiera la confirmación del usuario antes de completar una solicitud de descarga. Por ejemplo, si Google Play no instaló tu app o si intentas realizar una descarga grande usando datos móviles. En estos casos, el estado de la solicitud informa REQUIRES_USER_CONFIRMATION
, y tu app necesita obtener la confirmación del usuario para que el dispositivo pueda descargar y también instalar los módulos en la solicitud. Para obtener la confirmación, tu app debe pedirle al usuario lo siguiente:
Kotlin
override fun onSessionStateUpdate(state: SplitInstallSessionState) { if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) { // Displays a confirmation for the user to confirm the request. splitInstallManager.startConfirmationDialogForResult( state, // an activity result launcher registered via registerForActivityResult activityResultLauncher) } ... }
Java
@Override void onSessionStateUpdate(SplitInstallSessionState state) { if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) { // Displays a confirmation for the user to confirm the request. splitInstallManager.startConfirmationDialogForResult( state, // an activity result launcher registered via registerForActivityResult activityResultLauncher); } ... }
Puedes registrar un selector de resultados de actividad con el contrato integrado ActivityResultContracts.StartIntentSenderForResult
. Consulta las APIs de Activity Result.
El estado de la solicitud se actualiza según la respuesta del usuario:
- Si el usuario acepta la confirmación, el estado de la solicitud cambia a
PENDING
y la descarga continúa. - Si el usuario rechaza la confirmación, el estado de la solicitud cambia a
CANCELED
. - Si el usuario no hace una selección antes de que se destruya el diálogo, el estado de la solicitud permanece como
REQUIRES_USER_CONFIRMATION
. Tu app puede volver a pedirle al usuario que complete la solicitud.
Para recibir una devolución de llamada con la respuesta del usuario, puedes anular el elemento ActivityResultCallback como se muestra a continuación.
Kotlin
registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult -> { // Handle the user's decision. For example, if the user selects "Cancel", // you may want to disable certain functionality that depends on the module. } }
Java
registerForActivityResult( new ActivityResultContracts.StartIntentSenderForResult(), new ActivityResultCallback<ActivityResult>() { @Override public void onActivityResult(ActivityResult result) { // Handle the user's decision. For example, if the user selects "Cancel", // you may want to disable certain functionality that depends on the module. } });
Cómo cancelar una solicitud de instalación
Si tu app necesita cancelar una solicitud antes de que se instale, puede invocar el método cancelInstall()
mediante el ID de sesión de la solicitud, como se muestra a continuación.
Kotlin
splitInstallManager // Cancels the request for the given session ID. .cancelInstall(mySessionId)
Java
splitInstallManager // Cancels the request for the given session ID. .cancelInstall(mySessionId);
Cómo acceder a los módulos
Para acceder al código y los recursos de un módulo descargado después de la descarga, tu app debe habilitar la biblioteca de SplitCompat para tu app y cada actividad en los módulos de funciones que descarga tu app.
Sin embargo, ten en cuenta que la plataforma cuenta con las siguientes restricciones para acceder al contenido de un módulo durante un tiempo (días, en algunos casos) después de descargar el módulo:
- La plataforma no puede aplicar ninguna entrada nueva del manifiesto que ingrese el módulo.
- La plataforma no puede acceder a los recursos del módulo para los componentes de la IU del sistema, como las notificaciones. Si necesitas usar esos recursos inmediatamente, puedes incluirlos en el módulo de base de tu app.
Cómo habilitar SplitCompat
Si quieres que tu app acceda al código y los recursos de un módulo descargado, debes habilitar SplitCompat a través de solo uno de los métodos que se describen en las siguientes secciones.
Después de habilitar SplitCompat en tu app, también debes habilitar SplitCompat para cada actividad en los módulos de funciones a los que quieres que tu app tenga acceso.
Cómo declarar SplitCompatApplication en el manifiesto
La manera más simple de habilitar SplitCompat consiste en declarar SplitCompatApplication
como la subclase de Application
en el manifiesto de tu app, como se muestra a continuación:
<application
...
android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
</application>
Después de instalar la app en un dispositivo, puedes acceder automáticamente al código y los recursos de los módulos de funciones descargados.
Cómo invocar SplitCompat durante el tiempo de ejecución
También puedes habilitar SplitCompat en actividades o servicios específicos durante el tiempo de ejecución.
Es necesario habilitar SplitCompat de esta manera para iniciar las actividades incluidas en los módulos de funciones. Para ello, anula attachBaseContext
como se muestra a continuación.
Si tienes una clase Application personalizada, haz que extienda SplitCompatApplication
a fin de habilitar SplitCompat para tu app, como se muestra a continuación:
Kotlin
class MyApplication : SplitCompatApplication() { ... }
Java
public class MyApplication extends SplitCompatApplication { ... }
SplitCompatApplication
simplemente anula ContextWrapper.attachBaseContext()
para incluir SplitCompat.install(Context applicationContext)
. Si no deseas que tu clase Application
extienda SplitCompatApplication
, puedes anular el método attachBaseContext()
de forma manual, como se muestra a continuación:
Kotlin
override fun attachBaseContext(base: Context) { super.attachBaseContext(base) // Emulates installation of future on demand modules using SplitCompat. SplitCompat.install(this) }
Java
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // Emulates installation of future on demand modules using SplitCompat. SplitCompat.install(this); }
Si tu módulo a pedido es compatible con las apps instantáneas y las apps instaladas, puedes invocar SplitCompat de manera condicional, como se muestra a continuación:
Kotlin
override fun attachBaseContext(base: Context) { super.attachBaseContext(base) if (!InstantApps.isInstantApp(this)) { SplitCompat.install(this) } }
Java
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); if (!InstantApps.isInstantApp(this)) { SplitCompat.install(this); } }
Cómo habilitar SplitCompat para las actividades del módulo
Después de habilitar SplitCompat para tu app de base, debes habilitar SplitCompat en cada actividad que tu app descargue en un módulo de funciones. Para ello, usa el método SplitCompat.installActivity()
de la siguiente manera:
Kotlin
override fun attachBaseContext(base: Context) { super.attachBaseContext(base) // Emulates installation of on demand modules using SplitCompat. SplitCompat.installActivity(this) }
Java
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // Emulates installation of on demand modules using SplitCompat. SplitCompat.installActivity(this); }
Accede a los componentes definidos en los módulos de funciones
Inicia una actividad definida en un módulo de funciones
Puedes iniciar actividades definidas en los módulos de funciones mediante startActivity()
después de habilitar SplitCompat.
Kotlin
startActivity(Intent() .setClassName("com.package", "com.package.module.MyActivity") .setFlags(...))
Java
startActivity(new Intent() .setClassName("com.package", "com.package.module.MyActivity") .setFlags(...));
El primer parámetro de setClassName
es el nombre del paquete de la app, y el segundo es el nombre completo de la clase de la actividad.
Cuando tienes una actividad en un módulo de funciones que descargaste a pedido, debes habilitar SplitCompat en la actividad.
Inicia un servicio definido en un módulo de funciones
Puedes iniciar servicios definidos en los módulos de funciones mediante startService()
después de habilitar SplitCompat.
Kotlin
startService(Intent() .setClassName("com.package", "com.package.module.MyService") .setFlags(...))
Java
startService(new Intent() .setClassName("com.package", "com.package.module.MyService") .setFlags(...));
Exporta un componente definido en un módulo de funciones
No debes incluir componentes de Android exportados en módulos opcionales.
El sistema de compilación combina entradas de manifiesto para todos los módulos en el módulo básico. Si un módulo opcional contuviera un componente exportado, sería accesible incluso antes de que se instale el módulo y podría causar una falla debida a la falta del código cuando se invoca desde otra app.
Este no es un problema para los componentes internos: solo la app accede a ellos, por lo que esta puede verificar que el módulo esté instalado antes de acceder al componente.
Si necesitas un componente exportado y deseas que su contenido esté en un módulo opcional, procura implementar un patrón de proxy.
Puedes hacerlo agregando un componente exportado del proxy en la base. Cuando se accede a él, este puede verificar la presencia del módulo con el contenido. Si el módulo está presente, el componente del proxy puede iniciar el componente interno desde el módulo a través de un Intent
y retransmitir el intent desde la app que realiza la llamada. Si el módulo no está presente, el componente puede descargarlo o mostrar un mensaje de error apropiado a dicha app.
Cómo acceder al código y a los recursos desde los módulos instalados
Si habilitas SplitCompat para el contexto de tu aplicación de base y las actividades en tu módulo de funciones, puedes usar el código y los recursos desde un módulo de funciones como si fueran parte del APK de base una vez que se instala el módulo opcional.
Accede al código desde otro módulo
Accede al código base desde un módulo
Es posible que otros módulos usen de forma directa el código que se encuentra dentro de tu módulo básico. No necesitas hacer nada especial: solo importa y usa las clases que necesites.
Accede al código del módulo desde otro módulo
No es posible que otros módulos accedan directamente a un objeto o a una clase dentro de un módulo de forma estática, pero pueden acceder a él de manera indirecta mediante la reflexión.
Debes tener cuidado con la frecuencia con la que esto sucede, debido a los costos que la reflexión impone sobre el rendimiento. En casos de uso complejos, usa frameworks de inyección de dependencias, como Dagger 2, a fin de garantizar una sola llamada de reflexión por cada ciclo de vida de la aplicación.
Para simplificar las interacciones con el objeto después de que se cree la instancia, te recomendamos que definas una interfaz en el módulo básico y su implementación en el módulo de funciones. Por ejemplo:
Kotlin
// In the base module interface MyInterface { fun hello(): String } // In the feature module object MyInterfaceImpl : MyInterface { override fun hello() = "Hello" } // In the base module, where we want to access the feature module code val stringFromModule = (Class.forName("com.package.module.MyInterfaceImpl") .kotlin.objectInstance as MyInterface).hello();
Java
// In the base module public interface MyInterface { String hello(); } // In the feature module public class MyInterfaceImpl implements MyInterface { @Override public String hello() { return "Hello"; } } // In the base module, where we want to access the feature module code String stringFromModule = ((MyInterface) Class.forName("com.package.module.MyInterfaceImpl").getConstructor().newInstance()).hello();
Accede a los recursos y elementos desde otro módulo
Una vez que se instala un módulo, puedes acceder a los recursos y elementos dentro de este de la manera estándar, pero hay dos advertencias:
- Si accedes a un recurso desde un módulo diferente, el módulo no tendrá acceso al identificador de recursos, aunque aún se podrá acceder al recurso mediante su nombre. Ten en cuenta que el paquete que se debe usar para hacer referencia al recurso es el del módulo en el que se define el recurso.
- Si deseas acceder a elementos o recursos que existen en un módulo recién instalado desde otro módulo instalado de tu app, debes hacerlo usando el contexto de la aplicación. El contexto del componente que intenta acceder a los recursos todavía no estará actualizado. Como alternativa, puedes volver a crear ese componente (por ejemplo, llamando a Activity.recreate()) o reinstalar SplitCompat en él después de la instalación del módulo de funciones.
Cómo cargar código nativo en una app con entrega a pedido
Recomendamos usar ReLinker para cargar todas tus bibliotecas nativas cuando uses la entrega a pedido de módulos de funciones. ReLinker soluciona un problema en la carga de bibliotecas nativas después de la instalación de un módulo de funciones. Puedes obtener más información sobre ReLinker en las Sugerencias de JNI de Android.
Cómo cargar código nativo desde un módulo opcional
Una vez que se instala una división, te recomendamos que cargues su código nativo a través de ReLinker. Para las apps instantáneas, deberías usar este método especial.
Si usas System.loadLibrary()
para cargar tu código nativo y la biblioteca nativa tiene una dependencia de otra biblioteca del módulo, primero debes cargar manualmente esa otra biblioteca.
Si usas ReLinker, la operación equivalente es Relinker.recursively().loadLibrary()
.
Si usas dlopen()
en código nativo para cargar una biblioteca definida en un módulo opcional, no funcionará con rutas de acceso relativas de bibliotecas.
La mejor solución es recuperar la ruta de acceso absoluta de la biblioteca desde el código Java a través de ClassLoader.findLibrary()
y, luego, usarla en tu llamada a dlopen()
.
Haz esto antes de ingresar el código nativo o usa una llamada JNI desde tu código nativo a Java.
Cómo acceder a Apps instantáneas Android instaladas
Después de que se informe un módulo de App instantánea Android como INSTALLED
, puedes acceder a su código y sus recursos mediante el contexto actualizado de una app. Un contexto que tu app crea antes de instalar un módulo (por ejemplo, uno que ya está instalado en una variable) no incluye el contenido del módulo nuevo. Sin embargo, un contexto actualizado sí lo incluye y lo puedes obtener, por ejemplo, mediante createPackageContext
.
Kotlin
// 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 } } } }
Java
// Generate a new context as soon as a request for a new module // reports as INSTALLED. @Override public void onStateUpdate(SplitInstallSessionState state) { if (state.sessionId() == mySessionId) { switch (state.status()) { ... case SplitInstallSessionStatus.INSTALLED: Context newContext = context.createPackageContext(context.getPackageName(), 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. AssetManager am = newContext.getAssets(); } } }
Apps instantáneas Android en Android 8.0 y versiones posteriores
Cuando solicitas un módulo a pedido para una App instantánea Android en Android 8.0 (nivel de API 26) y versiones posteriores, después de que una solicitud de instalación se informa como INSTALLED
, debes actualizar la app con el contexto de módulo nuevo a través de una llamada a SplitInstallHelper.updateAppInfo(Context context)
.
De lo contrario, la app no está al tanto del código y los recursos del módulo. Después de actualizar los metadatos de la app, debes cargar el contenido del módulo durante el próximo evento de subproceso principal. Para ello, invoca un Handler
nuevo, como se indica a continuación:
Kotlin
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 ... } } } } } }
Java
@Override public void onStateUpdate(SplitInstallSessionState state) { if (state.sessionId() == mySessionId) { switch (state.status()) { ... case 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); new Handler().post(new Runnable() { @Override public void run() { // Loads contents from the module using AssetManager AssetManager am = context.getAssets(); ... } }); } } } }
Cómo cargar bibliotecas C/C++
Si quieres cargar bibliotecas C/C++ desde un módulo que el dispositivo ya descargó en una app instantánea, usa SplitInstallHelper.loadLibrary(Context context, String libName)
, como se muestra a continuación:
Kotlin
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”) ... } } } }
Java
public void onStateUpdate(SplitInstallSessionState state) { if (state.sessionId() == mySessionId) { switch (state.status()) { case SplitInstallSessionStatus.INSTALLED: // Updates the app’s context as soon as a module is installed. Context newContext = context.createPackageContext(context.getPackageName(), 0); // To load C/C++ libraries from an installed module, use the following API // instead of System.load(). SplitInstallHelper.loadLibrary(newContext, “my-cpp-lib”); ... } } }
Limitaciones conocidas
- No es posible usar WebView de Android en una actividad que accede a recursos o elementos desde un módulo opcional. Esto se debe a una incompatibilidad entre WebView y SplitCompat en el nivel de API 28 y versiones anteriores de Android.
- No puedes almacenar en caché los objetos
ApplicationInfo
de Android, su contenido ni objetos que los incluyan dentro de tu app. Siempre debes recuperar estos objetos según sea necesario desde el contexto de una app. Si almacenas esos objetos en caché, podrías provocar que la app falle cuando instale un módulo de funciones.
Cómo administrar módulos instalados
Para verificar qué módulos de funciones están instalados actualmente en el dispositivo, puedes llamar a SplitInstallManager.getInstalledModules()
, que muestra un Set<String>
de los nombres de los módulos instalados, como se muestra a continuación.
Kotlin
val installedModules: Set<String> = splitInstallManager.installedModules
Java
Set<String> installedModules = splitInstallManager.getInstalledModules();
Cómo desinstalar módulos
Puedes pedirle al dispositivo que desinstale módulos. Para ello, invoca SplitInstallManager.deferredUninstall(List<String> moduleNames)
, como se muestra a continuación.
Kotlin
// Specifies two feature modules for deferred uninstall. splitInstallManager.deferredUninstall(listOf("pictureMessages", "promotionalFilters"))
Java
// Specifies two feature modules for deferred uninstall. splitInstallManager.deferredUninstall(Arrays.asList("pictureMessages", "promotionalFilters"));
Las desinstalaciones del módulo no se realizan inmediatamente. Es decir, el dispositivo los desinstala en el segundo plano según sea necesario para ahorrar espacio de almacenamiento.
Para confirmar que el dispositivo haya quitado un módulo, invoca SplitInstallManager.getInstalledModules()
y también inspecciona el resultado, como se describe en la sección anterior.
Cómo descargar recursos de idiomas adicionales
Con los paquetes de aplicaciones, los dispositivos solo descargan el código y los recursos necesarios para ejecutar tu app. Por lo tanto, para los recursos de idioma, el dispositivo de un usuario solo descarga los recursos de idioma de tu app que coinciden con uno o más idiomas seleccionados actualmente en la configuración del dispositivo.
Si quieres que tu app tenga acceso a recursos de idiomas adicionales, por ejemplo, para implementar un selector de idiomas en la app, puedes usar la biblioteca de Play Feature Delivery para descargarlos a pedido. El proceso es similar al de descargar un módulo de funciones, como se muestra a continuación.
Kotlin
// 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)
Java
// 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. SplitInstallRequest 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);
La solicitud se maneja como si fuese una solicitud de un módulo de funciones. Es decir, puedes supervisar su estado como lo harías normalmente.
Si tu app no requiere los recursos de idiomas adicionales de inmediato, puedes postergar la instalación de la app para cuando esté en el segundo plano, como se muestra a continuación.
Kotlin
splitInstallManager.deferredLanguageInstall( Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
Java
splitInstallManager.deferredLanguageInstall( Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));
Cómo acceder a los recursos de idiomas descargados
Para obtener acceso a los recursos de idiomas descargados, tu app necesita ejecutar el método SplitCompat.installActivity()
dentro del método attachBaseContext()
de cada actividad que requiera acceso a esos recursos, como se muestra a continuación.
Kotlin
override fun attachBaseContext(base: Context) { super.attachBaseContext(base) SplitCompat.installActivity(this) }
Java
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); SplitCompat.installActivity(this); }
Para cada actividad en la que quieres usar recursos de idiomas que descargó tu app, actualiza el contexto de la base y define una configuración regional nueva a través de su Configuration
:
Kotlin
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) }
Java
@Override protected void attachBaseContext(Context base) { Configuration configuration = new Configuration(); configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION))); Context context = base.createConfigurationContext(configuration); super.attachBaseContext(context); SplitCompat.install(this); }
Para que estos cambios surtan efecto, tienes que volver a crear tu actividad después de que un idioma nuevo se instale y esté listo para usar. Puedes usar el método Activity#recreate()
.
Kotlin
when (state.status()) { SplitInstallSessionStatus.INSTALLED -> { // Recreates the activity to load resources for the new language // preference. activity.recreate() } ... }
Java
switch (state.status()) { case SplitInstallSessionStatus.INSTALLED: // Recreates the activity to load resources for the new language // preference. activity.recreate(); ... }
Cómo desinstalar recursos de idiomas adicionales
Del mismo modo que los módulos de funciones, puedes desinstalar recursos adicionales en cualquier momento. Antes de solicitar una desinstalación, determina qué idiomas están instalados actualmente, de la siguiente manera.
Kotlin
val installedLanguages: Set<String> = splitInstallManager.installedLanguages
Java
Set<String> installedLanguages = splitInstallManager.getInstalledLanguages();
Luego, puedes decidir qué idiomas desinstalar con el método deferredLanguageUninstall()
, como se muestra a continuación.
Kotlin
splitInstallManager.deferredLanguageUninstall( Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
Java
splitInstallManager.deferredLanguageUninstall( Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));
Cómo realizar pruebas locales de instalaciones de módulos
La biblioteca de Play Feature Delivery te permite probar de forma local la capacidad de tu app para hacer lo siguiente, sin necesidad de conectarte a Play Store:
- Solicitar y supervisar las instalaciones de módulos
- Procesar los errores de instalación
- Usar
SplitCompat
para acceder a módulos
En esta página, se describe cómo implementar los APK divididos de tu app en el dispositivo de prueba para que Play Feature Delivery use automáticamente esos APK para simular la solicitud, descarga e instalación de módulos desde Play Store.
Si bien no necesitas realizar ningún cambio en la lógica de tu app, deberás cumplir con los siguientes requisitos:
- Descarga e instala la versión más reciente de
bundletool
. Necesitasbundletool
para compilar un nuevo conjunto de APK instalables desde el paquete de tu app.
Cómo compilar un conjunto de APK
Si aún no lo hiciste, compila los APK divididos de tu app de la siguiente manera:
- Crea un paquete de aplicación para tu app con uno de los siguientes métodos:
- Usa Android Studio y el complemento de Android para Gradle a fin de compilar y firmar un Android App Bundle.
- Compila tu paquete de aplicación desde la línea de comandos.
Usa
bundletool
para generar un conjunto de APK para todas las configuraciones del dispositivo con el siguiente comando:bundletool build-apks --local-testing --bundle my_app.aab --output my_app.apks
La marca --local-testing
incluye metadatos en los manifiestos de tus APK que permiten que la biblioteca de Play Feature Delivery sepa usar los APK de división local para probar la instalación de los módulos de funciones sin necesidad de conectarse a Play Store.
Cómo implementar tu app en el dispositivo
Después de compilar un conjunto de APK con la marca --local-testing
, usa bundletool
para instalar la versión base de tu app y transferir los APK adicionales al almacenamiento local de tu dispositivo. Puedes realizar ambas acciones con el siguiente comando:
bundletool install-apks --apks my_app.apks
Ahora, cuando inicias tu app y completas el flujo de usuarios para descargar e instalar un módulo de funciones, la biblioteca de Play Feature Delivery usa los APK que bundletool
transfirió al almacenamiento local del dispositivo.
Cómo simular un error de red
Para simular las instalaciones de módulos desde Play Store, la biblioteca de Play Feature Delivery usa una alternativa a SplitInstallManager
, llamada FakeSplitInstallManager
, para solicitar el módulo. Cuando usas bundletool
con la marca --local-testing
para compilar un conjunto de APK y, luego, implementarlos en el dispositivo de prueba, se incluyen metadatos que le indican a la biblioteca de Play Feature Delivery que debe cambiar automáticamente las llamadas a la API de tu app para invocar FakeSplitInstallManager
, en lugar de SplitInstallManager
.
FakeSplitInstallManager
incluye una marca booleana que puedes habilitar para simular un error de red la próxima vez que tu app solicite instalar un módulo. Para acceder a FakeSplitInstallManager
en tus pruebas, puedes obtener una instancia usando FakeSplitInstallManagerFactory
, como se muestra a continuación:
Kotlin
// Creates an instance of FakeSplitInstallManager with the app's context. val fakeSplitInstallManager = FakeSplitInstallManagerFactory.create(context) // Tells Play Feature Delivery Library to force the next module request to // result in a network error. fakeSplitInstallManager.setShouldNetworkError(true)
Java
// Creates an instance of FakeSplitInstallManager with the app's context. FakeSplitInstallManager fakeSplitInstallManager = FakeSplitInstallManagerFactory.create(context); // Tells Play Feature Delivery Library to force the next module request to // result in a network error. fakeSplitInstallManager.setShouldNetworkError(true);