Descripción general de la entrega de funciones en Play

El modelo de entrega de apps de Google Play usa paquetes Android App Bundle a fin de generar y publicar APK optimizados para la configuración del dispositivo de cada usuario, de manera que los usuarios descarguen solo el código y los recursos necesarios para ejecutar la app.

Play Feature Delivery usa capacidades avanzadas de los paquetes de aplicaciones, lo que permite que ciertas funciones de tu app se entreguen de manera condicional o se descarguen a pedido. Para ello, primero debes separar estas funciones de tu app de base en módulos de funciones.

Configuración de compilación de módulos de funciones

Cuando creas un nuevo módulo de funciones con Android Studio, el IDE aplica el siguiente complemento de Gradle al archivo build.gradle del módulo.

// The following applies the dynamic-feature plugin to your feature module.
// The plugin includes the Gradle tasks and properties required to configure and build
// an app bundle that includes your feature module.

apply plugin: 'com.android.dynamic-feature'

Muchas de las propiedades disponibles para el complemento de app estándar también están disponibles a fin de que puedas utilizarlas en tu módulo de funciones. En las siguientes secciones, se describen las propiedades que deberías y las que no deberías incluir en la configuración de compilación de tu módulo de funciones.

Elementos que no debes incluir en la configuración de compilación del módulo de funciones

Debido a que cada módulo de funciones depende del módulo base, también hereda ciertas configuraciones. Por lo tanto, en el archivo build.gradle del módulo de funciones, debes omitir lo siguiente:

  • Configuraciones de firma: Los paquetes de aplicación se firman con las configuraciones de firma que especificas en el módulo base.
  • La propiedad minifyEnabled: Puedes habilitar la reducción de código para todo el proyecto de la app solo desde la configuración de compilación del módulo base. Por lo tanto, debes omitir esa propiedad en los módulos de funciones. No obstante, puedes especificar reglas adicionales de ProGuard para cada módulo de funciones.
  • versionCode y versionName: cuando se compila el paquete de aplicación, Gradle utiliza la información de versión de la app que proporciona el módulo base. Debes omitir esas propiedades del archivo build.gradle de tu módulo de funciones.

Cómo establecer una relación con el módulo base

Cuando Android Studio crea tu módulo de funciones, lo hace visible para el módulo base agregando la propiedad android.dynamicFeatures al archivo build.gradle del módulo base, como se muestra a continuación:

// In the base module’s build.gradle file.
android {
    ...
    // Specifies feature modules that have a dependency on
    // this base module.
    dynamicFeatures = [":dynamic_feature", ":dynamic_feature2"]
}

Además, Android Studio incluye el módulo base como una dependencia del módulo de funciones, según se muestra a continuación:

// In the feature module’s build.gradle file:
...
dependencies {
    ...
    // Declares a dependency on the base module, ':app'.
    implementation project(':app')
}

Cómo especificar reglas adicionales de ProGuard

Aunque solo la configuración de compilación del módulo base puede habilitar la reducción del código para el proyecto de tu app, puedes proporcionar reglas de ProGuard personalizadas con cada módulo de funciones a través de la propiedad proguardFiles, como se muestra a continuación.

android.buildTypes {
     release {
         // You must use the following property to specify additional ProGuard
         // rules for feature modules.
         proguardFiles 'proguard-rules-dynamic-features.pro'
     }
}

Ten en cuenta que estas reglas de ProGuard se fusionan con las de otros módulos (incluido el módulo base) durante la compilación. Por lo tanto, si bien cada módulo de funciones puede especificar un nuevo conjunto de reglas, esas reglas se aplican a todos los módulos del proyecto de la app.

Cómo implementar la app

Mientras desarrollas una app compatible con módulos de funciones, puedes implementarla en un dispositivo conectado como lo harías normalmente seleccionando Run > Run en la barra de menú (o haciendo clic en Run en la barra de herramientas).

Si el proyecto de tu app incluye uno o más módulos de funciones, puedes elegir qué funciones incluir cuando implementas tu app si modificas tu configuración de ejecución y depuración existente de la siguiente manera:

  1. Selecciona Run > Edit Configurations en la barra de menú.
  2. En el panel izquierdo del cuadro de diálogo Run/Debug Configurations, elige la configuración de Android App que desees.
  3. En Dynamic features to deploy, en la pestaña General, marca la casilla junto a cada módulo de funciones dinámicas que desees incluir cuando implementes la app.
  4. Haz clic en OK.

De forma predeterminada, Android Studio no implementa la app usando paquetes de aplicación. En cambio, el IDE crea e instala en tu dispositivo los APK optimizados para la velocidad de implementación, en lugar del tamaño del APK. Si quieres configurar Android Studio para que compile e implemente APK y experiencias instantáneas desde un paquete de aplicación, modifica la configuración de ejecución y depuración.

Cómo usar módulos de funciones para la entrega personalizada

Un beneficio único de los módulos de funciones es la habilidad de personalizar cómo y cuándo se descargan las diferentes funciones de la app en dispositivos con Android 5.0 (nivel de API 21) o versiones posteriores. Por ejemplo, a fin de reducir el tamaño de la descarga inicial de tu app, puedes configurar ciertas funciones para que se descarguen a pedido según sea necesario o solo en dispositivos que admitan ciertas capacidades, como la posibilidad de tomar fotografías o admitir funciones de realidad aumentada.

Si bien cuando subes tu app como un paquete de aplicación obtienes una descarga altamente optimizada de manera predeterminada, las opciones de entrega de funciones más avanzadas y personalizables requieren una configuración adicional y la modularización de las funciones de la app mediante módulos de funciones. Es decir, los módulos de funciones proporcionan los componentes básicos para crear funciones modulares que puedes configurar a fin de que se descarguen según sea necesario.

Piensa en una app que permite a los usuarios comprar y vender bienes en un mercado en línea. Puedes modularizar razonablemente cada una de las siguientes funciones de la app en módulos de funciones independientes:

  • Acceso y creación de cuentas
  • Navegación por el mercado
  • Colocación de un artículo a la venta
  • Procesamiento de pagos

En la tabla que sigue, se describen las diferentes opciones de entrega que admiten los módulos de funciones y cómo se pueden usar para optimizar el tamaño de descarga inicial en el ejemplo de la aplicación de Marketplace.

Opción de entrega Comportamiento Caso práctico de muestra Cómo comenzar
Entrega durante la instalación Los módulos de funciones que no configuran ninguna de las opciones de entrega descritas con anterioridad se descargan de forma predeterminada cuando se instala la app. Este es un comportamiento importante porque permite adoptar de manera gradual opciones de entrega avanzadas. Por ejemplo, puedes aprovechar la modularización de funciones de la app y habilitar la entrega a pedido solo después de haber implementado por completo las descargas a pedido usando la biblioteca de Play Core.

Además, la app puede solicitar la desinstalación de funciones más adelante. Por lo tanto, si necesitas ciertas funciones al instalar la app, pero no después de ese momento, puedes reducir el tamaño de la instalación solicitando que se quiten esas funciones del dispositivo.

Si la app tiene ciertas actividades de capacitación, como una guía interactiva sobre cómo comprar y vender artículos en el mercado, puedes incluir esa función en la instalación de la app de manera predeterminada.

Sin embargo, para reducir el tamaño instalado de la app, esta puede solicitar eliminar la función después de que el usuario haya completado la capacitación.

Modulariza tu app con módulos de funciones que no configuren opciones de entrega avanzadas.

Para aprender a reducir el tamaño instalado de tu app gracias a la extracción de ciertos módulos de funciones que el usuario ya no necesita, consulta Cómo administrar módulos instalados.

Entrega a pedido Permite que la app solicite y descargue módulos de funciones según sea necesario. Si solo el 20% de los usuarios de la aplicación de Marketplace publican artículos para la venta, una buena estrategia de reducción del tamaño de descarga inicial para la mayoría de los usuarios es ofrecer la funcionalidad de tomar fotos, incluir una descripción del artículo y colocar un artículo a la venta como descarga a pedido. Es decir, puedes configurar el módulo de funciones para que la funcionalidad de venta de la app se descargue solo cuando un usuario muestre interés en colocar artículos para la venta en el mercado.

Además, si el usuario ya no vende artículos después de un cierto tiempo, la app puede solicitar la desinstalación de la función para reducir su tamaño instalado.

Crea un módulo de funciones y configura la entrega a pedido. La app puede usar la biblioteca de Play Core para solicitar la descarga del módulo a pedido.
Entrega condicional Permite especificar ciertos requisitos del dispositivo del usuario, como funciones de hardware, configuración regional y un nivel mínimo de API, para determinar si una función modularizada se descarga cuando se instala la app. Si la aplicación de Marketplace tiene alcance global, puede ser necesario admitir formas de pago que se usan solo en ciertas regiones o países. A fin de reducir el tamaño de descarga inicial de la app, puedes crear módulos de funciones individuales para procesar ciertos tipos de formas de pago y, luego, instalarlos de forma condicional en el dispositivo de un usuario según su configuración regional registrada. Crea un módulo de funciones y configura la entrega condicional.
Entrega instantánea Google Play Instant permite a los usuarios interactuar con tu app sin necesidad de que la instalen en sus dispositivos. En cambio, pueden experimentar la app a través del botón "Probar ahora" en Google Play Store o una URL que crees. Esta forma de entregar contenido hace que sea más fácil aumentar la interacción con tu app.

Con la entrega instantánea, puedes usar Google Play Instant para permitir que los usuarios experimenten al instante ciertas funciones de la app sin instalación.

Por ejemplo, un juego que incluya los primeros niveles en un módulo de funciones liviano. Puedes habilitar al instante ese módulo para que los usuarios experimenten el juego a través de un vínculo a una URL o el botón "Probar ahora", sin necesidad de instalar la app. Crea un módulo de funciones y configura la entrega instantánea. La app puede usar la biblioteca de Play Core para solicitar la descarga del módulo a pedido.

Ten en cuenta que modularizar las funciones de tu app mediante módulos de funciones es solo el primer paso. Para admitir Google Play Instant, el tamaño de descarga del módulo base de la app y una cierta función habilitada instantáneamente deben cumplir con estrictas restricciones de tamaño. Para obtener más información, lee Cómo habilitar experiencias instantáneas mediante la reducción del tamaño de la app o el juego.

Cómo compilar un URI para un recurso

Si deseas acceder a un recurso almacenado en un módulo de funciones mediante un URI, consulta la siguiente información para aprender a generar un URI de recurso de módulo de funciones con Uri.Builder():

Kotlin

val uri = Uri.Builder()
                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
                .authority(context.getPackageName()) // Look up the resources in the application with its splits loaded
                .appendPath(resources.getResourceTypeName(resId))
                .appendPath(String.format("%s:%s",
                  resources.getResourcePackageName(resId), // Look up the dynamic resource in the split namespace.
                  resources.getResourceEntryName(resId)
                  ))
                .build()

Java

String uri = Uri.Builder()
                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
                .authority(context.getPackageName()) // Look up the resources in the application with its splits loaded
                .appendPath(resources.getResourceTypeName(resId))
                .appendPath(String.format("%s:%s",
                  resources.getResourcePackageName(resId), // Look up the dynamic resource in the split namespace.
                  resources.getResourceEntryName(resId)
                  ))
                .build().toString();

Cada parte de la ruta de acceso al recurso se construye en el tiempo de ejecución, lo que garantiza que se genere el espacio de nombres correcto después de cargar los APK divididos.

Si deseas un ejemplo de cómo se genera el URI, supongamos que tienes una app y módulos de funciones con estos nombres:

  • Nombre del paquete de apps: com.example.my_app_package
  • Nombre del paquete de recursos de la función: com.example.my_app_package.my_dynamic_feature

Si el resId del fragmento de código anterior hace referencia a un recurso de archivos sin procesar con el nombre "my_video" en tu módulo de funciones, entonces el código de Uri.Builder() debería generar el siguiente resultado:

android.resource://com.example.my_app_package/raw/com.example.my_app_package.my_dynamic_feature:my_video

Tu app puede usar este URI para acceder al recurso del módulo de funciones.

Para validar las rutas de acceso de tu URI, puedes usar el Analizador de APK a fin de inspeccionar el APK del módulo de funciones y determinar el nombre del paquete:

Captura de pantalla del Analizador de APK que inspecciona el contenido de un archivo de recursos compilado.

Figura 2: Usa el Analizador de APK para inspeccionar el nombre del paquete en un archivo de recursos compilado.

Consideraciones para los módulos de funciones

Con los módulos de funciones, puedes mejorar la velocidad de compilación y la de ingeniería, y personalizar ampliamente la entrega de las funciones de tu app a fin de reducir su tamaño. Sin embargo, hay algunas limitaciones y casos extremos que se deben tener en cuenta cuando se usan módulos de funciones:

  • Instalar 50 módulos de funciones o más en un solo dispositivo, a través de la entrega condicional o a pedido, puede generar problemas de rendimiento. Los módulos de tiempo de instalación, que no están configurados como extraíbles, se incluyen automáticamente en el módulo base y solo se cuentan como un módulo de funciones en cada dispositivo.
  • Limita la cantidad de módulos que configures como extraíbles para la entrega en el momento de la instalación a 10 o menos. De lo contrario, el tiempo de descarga e instalación de tu app podría aumentar.
  • Solo los dispositivos con Android 5.0 (nivel de API 21) y versiones posteriores admiten la descarga y la instalación de funciones a pedido. Para que la función esté disponible en versiones anteriores de Android, habilita Fusing cuando crees un módulo de funciones.
  • Habilita SplitCompat para que la app tenga acceso a los módulos de funciones descargados que se entregan a pedido.
  • Los módulos de funciones no deben especificar actividades en su manifiesto con android:exported establecido en true. Esto se debe a que no hay garantía de que el dispositivo haya descargado el módulo de funciones cuando otra app intente iniciar la actividad. Además, tu app debe confirmar que se descarga una función antes de intentar acceder a su código y a sus recursos. Para obtener más información, consulta Cómo administrar módulos instalados.
  • Dado que la entrega de funciones de Play requiere que publiques tu app con un paquete de aplicación, asegúrate de tener en cuenta los problemas conocidos del paquete de aplicación.

Referencia del manifiesto del módulo de funciones

Cuando creas un módulo de funciones nuevo con Android Studio, el IDE incluye la mayoría de los atributos de manifiesto que el módulo requiere para comportarse como un módulo de funciones. Además, el sistema de compilación inserta algunos atributos en el tiempo de compilación, por lo que no necesitas especificarlos ni modificarlos. En la tabla siguiente, se describen los atributos de manifiesto que son importantes para los módulos de funciones.

Atributo Descripción
<manifest
...
Este es un bloque <manifest> típico.
xmlns:dist="http://schemas.android.com/apk/distribution" Especifica un nuevo espacio de nombres XML dist: que se describe más adelante.
split="split_name" Cuando Android Studio compila el paquete de aplicación, incluye este atributo automáticamente. Por lo tanto, no debes incluir ni modificar este atributo.

Define el nombre del módulo, que la app especifica cuando solicita un módulo a pedido desde la biblioteca de Play Core.

Cómo hace Gradle para determinar el valor de este atributo:

De forma predeterminada, cuando creas un módulo de funciones con Android Studio, el IDE usa lo que especificas como su nombre del módulo 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 de la ruta de acceso del subproyecto para insertar este atributo del manifiesto en el manifiesto del módulo. Por ejemplo, si creas un módulo de función nuevo en el directorio MyAppProject/features/ y especificas el valor "dynamic_feature1" como nombre del módulo, el IDE agrega ':features:dynamic_feature1' como un subproyecto en tu archivo settings.gradle. Cuando compilas tu paquete de aplicación, Gradle inserta <manifest split="dynamic_feature1"> en el manifiesto del módulo.

android:isFeatureSplit="true | false"> Cuando Android Studio compila el paquete de aplicación, incluye este atributo automáticamente. Por lo tanto, no debes incluir ni modificar este atributo de forma manual.

Especifica que este módulo es un módulo de función. Los manifiestos en el módulo base y los APK de configuración omiten este atributo o lo configuran como false.

<dist:module Este nuevo elemento XML define atributos que determinan cómo se empaqueta y distribuye el módulo como APK.
dist:instant="true | false" Especifica si el módulo debe estar disponible a través de Google Play Instant como una experiencia instantánea.

Si tu app incluye uno o más módulos de funciones habilitadas instantáneamente, también debes habilitar al instante el módulo base. Si usas Android Studio 3.5 o versiones posteriores, el IDE lo hace automáticamente cuando creas un módulo de funciones habilitadas instantáneamente.

No es posible configurar este elemento XML en true y, al mismo tiempo, establecer <dist:on-demand/>. Sin embargo, todavía puedes solicitar descargas a pedido de los módulos de funciones habilitadas instantáneamente como experiencias instantáneasmediante la biblioteca de Play Core. Cuando un usuario descarga la app y la instala, el dispositivo descarga y luego instala los módulos de funciones habilitadas instantáneamente de la app, junto con el APK base, de forma predeterminada.

dist:title="@string/feature_name" Especifica un título para el módulo que se mostrará al usuario. Por ejemplo, el dispositivo puede mostrar este título cuando solicita confirmación de descarga.

Debes incluir el recurso de strings para este título en el archivo module_root/src/source_set/res/values/strings.xml del módulo base.

<dist:fusing dist:include="true | false" />
</dist:module>
Especifica si se debe incluir el módulo en APK múltiples orientados a dispositivos con Android 4.4 (nivel de API 20) y versiones anteriores.

Además, cuando usas bundletool para generar APK a partir de un paquete de aplicación, solo los módulos de funciones que establecen esta propiedad en true se incluyen en el APK universal, que es un APK monolítico que incluye código y recursos para todas las configuraciones del dispositivo que admite tu app.

<dist:delivery> Encapsula opciones que personalizan la entrega de los módulos, como se muestra a continuación. Ten en cuenta que en cada módulo de funciones se debe configurar solo un tipo de estas opciones de entrega personalizadas.
<dist:install-time> Especifica que el módulo debe estar disponible en el momento de la instalación. Ese es el comportamiento predeterminado de los módulos de funciones que no especifican otro tipo de opción de entrega personalizada.

Para obtener más información sobre las descargas durante la instalación, consulta Cómo configurar la entrega durante la instalación.

Ese nodo también puede especificar condiciones que limitan el módulo a dispositivos que cumplen con ciertos requisitos, como las funciones del dispositivo, el país del usuario o el nivel mínimo de API. Para obtener más información, consulta Cómo configurar la entrega condicional.

<dist:removable dist:value="true | false" />

Si bundletool no se configura o se establece en false, se fusionarán los módulos de tiempo de instalación con el módulo base cuando generes APK divididos desde el paquete. Como quedarán menos APK divididos después de la fusión, esta configuración puede mejorar el rendimiento de tu app.

Cuando se establece removable en true, los módulos de tiempo de instalación no se fusionarán con el módulo base. Configúralo en true si deseas desinstalar módulos en el futuro. Sin embargo, configurar demasiados módulos removibles puede aumentar el tiempo de instalación de tu app.

La configuración predeterminada es false. Solo es necesario establecer este valor en el manifiesto si deseas inhabilitar la fusión de un módulo de funciones.

Nota: Esta función solo está disponible cuando usas el complemento de Gradle para Android 4.2 o cuando usas bundletool v1.0 desde la línea de comandos.

</dist:install-time>  
<dist:on-demand/> Especifica que el módulo debe estar disponible como descarga a pedido. Es decir, el módulo no está disponible durante la instalación, pero es posible que la app solicite descargarlo más tarde.

Si deseas obtener más información sobre las descargas a pedido, consulta Cómo configurar la entrega a pedido.

</dist:delivery>
<application
android:hasCode="true | false">
...
</application>
Si el módulo de funciones no genera archivos DEX, es decir, no contiene ningún código que luego se compile en el formato de archivo DEX, debes hacer lo siguiente (de lo contrario, puedes encontrar errores en el tiempo de ejecución):
  1. Configura android:hasCode como "false" en el manifiesto del módulo de funciones.
  2. Agrega lo siguiente al manifiesto del módulo base:
    
    <application
      android:hasCode="true"
      tools:replace="android:hasCode">
      ...
    </application>
    

Recursos adicionales

Para obtener más información sobre cómo usar los módulos de funciones, consulta los siguientes recursos.

Entradas de blog

Videos