Archivos de expansión de APK

Google Play requiere que el APK comprimido que descarguen los usuarios no supere los 100 MB. En la mayoría de las apps, este espacio es suficiente para todos los elementos y el código correspondiente. Sin embargo, algunas necesitan más espacio para gráficos de alta fidelidad, archivos multimedia u otros elementos grandes. Antes, si el tamaño de la descarga comprimida de tu app superaba los 100 MB, tenías que alojar y descargar los recursos adicionales por tu cuenta cuando el usuario la abría, lo cual puede ser costoso y suele afectar la experiencia del usuario. Con el objetivo de que este proceso sea más fácil para ti y más agradable para los usuarios, Google Play permite adjuntar dos archivos grandes de expansión que complementan tu APK.

Google Play aloja los archivos de expansión para tu app y los envía al dispositivo sin costo alguno. Los archivos de expansión se guardan en la ubicación de almacenamiento compartido del dispositivo (la tarjeta SD o la partición USB extraíble, también conocida como almacenamiento "externo") en donde tu app puede acceder a estos archivos. En la mayoría de los dispositivos, Google Play descarga los archivos de expansión al mismo tiempo que el APK, por lo que tu app tiene todo lo que necesita cuando el usuario la abre por primera vez. Sin embargo, en algunos casos, tu app debe descargar los archivos en Google Play cuando se inicia.

Si no deseas usar archivos de expansión y el tamaño de descarga comprimida de tu app supera los 100 MB, debes subir la app con Android App Bundles, que permite subir una descarga comprimida de hasta 150 MB. Además, debido a que el uso de paquetes de aplicaciones delega la generación de APK y la firma a Google Play, los usuarios descargan APK optimizados solo con el código y los recursos que se necesitan para ejecutar tu app. No tienes que crear, firmar ni administrar varios archivos de expansión o archivos APK, y los usuarios realizan descargas más pequeñas y más optimizadas.

Descripción general

Cada vez que subes un APK con Google Play Console, tienes la opción de agregar uno o dos archivos de expansión. Cada archivo no puede superar los 2 GB y puede tener el formato que elijas, pero te recomendamos que utilices un archivo comprimido para conservar el ancho de banda durante la descarga. De forma conceptual, cada archivo de expansión cumple un papel diferente:

  • El archivo principal de expansión es el archivo fundamental de expansión para los recursos adicionales que necesite tu app.
  • El archivo de parche de expansión es opcional y está destinado a pequeñas actualizaciones del archivo principal de expansión.

Si bien puedes usar los dos archivos de expansión de la forma que desees, recomendamos que el archivo principal de expansión envíe los elementos principales y que rara vez se actualice; el archivo de parche de expansión debe ser más pequeño y debe funcionar como un "proveedor de parches", que se actualiza con cada lanzamiento importante o según sea necesario.

Sin embargo, incluso si la actualización de tu app necesita solo un nuevo archivo de parche de expansión, debes subir un nuevo APK con un objeto versionCode actualizado en el manifiesto. (Play Console no te permite subir un archivo de expansión a un APK existente).

Nota: El archivo de parche de expansión es igual a nivel semántico que el archivo principal de expansión. Puedes usar cada archivo de la forma que desees.

Formato del nombre del archivo

Cada archivo de expansión que subes puede tener el formato que elijas (ZIP, PDF, MP4, etc.). También puedes usar la herramienta JOBB con el objetivo de encapsular y encriptar un conjunto de archivos de recursos y los parches posteriores para ese conjunto. Sin importar el tipo de archivo, Google Play considera los archivos blobs binarios opacos y les cambia el nombre con el siguiente esquema:

    [main|patch].<expansion-version>.<package-name>.obb
    

Este esquema tiene tres componentes:

main o patch
Especifica si se trata del archivo principal o de parche de expansión. Cada APK solo puede tener un archivo principal y un archivo de parche.
<expansion-version>
Es un valor entero que coincide con el código de la versión del APK con el que primero se asocia la expansión (coincide con el valor android:versionCode de la app).

Se enfatiza "primero" porque, si bien Play Console te permite reutilizar un archivo de expansión que se haya subido con un nuevo APK, el nombre del archivo de expansión no cambia, sino que se conserva la versión que se aplicó en el momento en que se subió el archivo por primera vez.

<package-name>
El nombre del paquete de estilo Java de tu app.

Por ejemplo, supongamos que la versión de tu APK es 314159 y que el nombre de tu paquete es com.example.app. Si subes un archivo principal de expansión, se modificará el nombre del archivo por el siguiente:

main.314159.com.example.app.obb

Ubicación del almacenamiento

Cuando Google Play descarga tus archivos de expansión en un dispositivo, los guarda en la ubicación de almacenamiento compartido del sistema. Para garantizar un comportamiento adecuado, no debes borrar, mover ni cambiar el nombre de los archivos de expansión. En el caso de que tu app deba realizar la descargar en Google Play por su cuenta, debes guardar los archivos en la misma ubicación.

El método getObbDir() muestra la ubicación específica de los archivos de expansión de la siguiente manera:

    <shared-storage>/Android/obb/<package-name>/
    

Cada app no puede tener más de dos archivos de expansión en este directorio. Uno es el archivo principal de expansión y el otro es el archivo de parche de expansión (si es necesario). Se reemplazan las versiones anteriores cuando actualizas tu app con nuevos archivos de expansión. A partir de Android 4.4 (API nivel 19), las apps pueden leer archivos de expansión OBB sin solicitar permiso de almacenamiento externo. Sin embargo, algunas implementaciones de Android 6.0 (API nivel 23) y posteriores aún requieren permiso, por lo que deberás declarar el permiso READ_EXTERNAL_STORAGE en el manifiesto de la app y solicitar permiso durante el tiempo de ejecución de la siguiente manera:

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    

Para la versión 6 y posteriores de Android, se debe solicitar permiso de almacenamiento externo durante el tiempo de ejecución. Sin embargo, algunas implementaciones de Android no requieren permiso para leer archivos OBB. En el siguiente fragmento de código, se muestra cómo verificar el acceso de lectura antes de solicitar permiso de almacenamiento externo:

Kotlin

    val obb = File(obb_filename)
    var open_failed = false

    try {
        BufferedReader(FileReader(obb)).also { br ->
            ReadObbFile(br)
        }
    } catch (e: IOException) {
        open_failed = true
    }

    if (open_failed) {
        // request READ_EXTERNAL_STORAGE permission before reading OBB file
        ReadObbFileWithPermission()
    }
    

Java

    File obb = new File(obb_filename);
     boolean open_failed = false;

     try {
         BufferedReader br = new BufferedReader(new FileReader(obb));
         open_failed = false;
         ReadObbFile(br);
     } catch (IOException e) {
         open_failed = true;
     }

     if (open_failed) {
         // request READ_EXTERNAL_STORAGE permission before reading OBB file
         ReadObbFileWithPermission();
     }
    

Si debes comprimir el contenido de tus archivos de expansión, no borres estos archivos OBB después ni guardes los datos comprimidos en el mismo directorio. Debes guardar los archivos comprimidos en el directorio que especifique el objeto getExternalFilesDir(). Sin embargo, si es posible, te recomendamos que uses un formato de archivo de expansión que te permita leer directamente desde el archivo, en lugar de un formato que te solicite comprimir los datos. Por ejemplo, proporcionamos un proyecto de biblioteca denominado biblioteca de ZIP de expansión de APK que lee tus datos directamente desde el archivo ZIP.

Precaución: A diferencia de los archivos APK, el usuario y otras apps pueden leer los archivos guardados en el almacenamiento compartido.

Sugerencia: Si comprimes archivos multimedia en un ZIP, puedes reproducir los archivos en segundo plano con controles de desplazamiento y longitud (como los objetos MediaPlayer.setDataSource() y SoundPool.load()) sin la necesidad de descomprimir el ZIP. Para que funcione esta reproducción, no debes realizar ninguna compresión adicional en los archivos multimedia cuando crees los paquetes ZIP. Por ejemplo, cuando uses la herramienta zip, debes usar la opción -n para especificar los sufijos de archivo que no deben comprimirse:
zip -n .mp4;.ogg main_expansion media_files

Proceso de descarga

En la mayoría de los casos, Google Play descarga y guarda tus archivos de expansión al mismo tiempo que descarga el APK en el dispositivo. Sin embargo, en algunos casos, Google Play no puede descargar los archivos de expansión, o el usuario podría haber borrado los archivos que se descargaron antes. Para controlar estas situaciones, tu app debe ser capaz de descargar los archivos por su cuenta cuando comience la actividad principal, con una URL que proporcione Google Play.

El proceso de descarga desde un nivel alto es similar al siguiente:

  1. El usuario elige instalar tu app desde Google Play.
  2. Si Google Play puede descargar los archivos de expansión (que sucede en la mayoría de los dispositivos), los descarga junto con el APK.

    Si Google Play no puede descargar los archivos de expansión, solo descarga el APK.

  3. Cuando el usuario inicia tu app, esta debe verificar si los archivos de expansión ya están guardados en el dispositivo.
    1. Si es así, tu app está lista para usarse.
    2. Si no es así, tu app debe descargar los archivos de expansión mediante HTTP en Google Play. Tu app debe enviarle una solicitud al cliente de Google Play mediante el servicio de licencias de apps de Google Play, que responde con el nombre, el tamaño y la URL de cada archivo de expansión. Con esta información, puedes descargar los archivos y guardarlos en la ubicación de almacenamiento correcta.

Precaución: Es fundamental que incluyas el código necesario para descargar los archivos de expansión desde Google Play en el caso de que los archivos ya no estén en el dispositivo cuando se inicie tu app. Como se explica en la sección sobre Cómo descargar los archivos de expansión más adelante, hemos habilitado una biblioteca que simplifica en gran medida este proceso y realiza la descarga en un servicio con una cantidad mínima de código.

Lista de comprobación de desarrollo

A continuación, se incluye un resumen de las tareas que debes realizar para usar archivos de expansión con tu app:

  1. Primero, determina si el tamaño de descarga comprimida de tu app debe superar los 100 MB. El espacio es valioso, y debes reducir el tamaño total de descarga lo más posible. Si tu app necesita más de 100 MB con el objetivo de proporcionar varias versiones de tus recursos gráficos para diferentes densidades de pantalla, procura publicar varios APK a fin de que cada uno solo incluya los recursos necesarios para las pantallas a las que se orienta. Para obtener mejores resultados cuando publiques contenido en Google Play, sube un Android App Bundle que incluya una compilación de todos los recursos y el código de la app, pero que delegue la generación de APK y la firma a Google Play.
  2. Determina qué recursos de la app se separan de tu APK y comprímelos en un archivo para usarlo como archivo principal de expansión.

    Por lo general, solo debes usar el segundo archivo de parche de expansión cuando realices actualizaciones del archivo principal de expansión. Sin embargo, si tus recursos superan el límite de 2 GB para el archivo principal de expansión, puedes usar el archivo de parche para el resto de tus elementos.

  3. Desarrolla tu app, de modo que use los recursos de tus archivos de expansión en la ubicación de almacenamiento compartido del dispositivo.

    Recuerda que no debes borrar, mover ni cambiar el nombre de los archivos de expansión.

    Si tu app no exige un formato específico, te sugerimos que crees archivos ZIP para tus archivos de expansión y, luego, los leas con la biblioteca de ZIP de expansión de APK.

  4. Agrega lógica a la actividad principal de tu app que verifique si los archivos de expansión se encuentran en el dispositivo cuando se inicia tu app. Si los archivos no están en el dispositivo, usa el servicio de licencias de apps de Google Play a fin de solicitar la URL para los archivos de expansión. Luego, descárgalos y guárdalos.

    A fin de reducir en gran medida la cantidad de código que debes escribir y de garantizar una buena experiencia del usuario durante la descarga, te recomendamos que uses la biblioteca de descargas para implementar tu comportamiento de descarga.

    Si creas tu propio servicio de descarga en lugar de usar la biblioteca, ten en cuenta que no debes cambiar el nombre de los archivos de expansión y que debes guardarlos en la ubicación de almacenamiento correcta.

Una vez que hayas terminado de desarrollar tu app, sigue la guía sobre Cómo probar tus archivos de expansión.

Reglas y limitaciones

Agregar archivos de expansión de APK es una función que tienes disponible cuando subes tu app con Play Console. Cuando subes tu app por primera vez o cuando actualizas una app que usa archivos de expansión, debes tener en cuenta las siguientes reglas y limitaciones:

  1. Cada archivo de expansión no puede superar los 2 GB.
  2. Para descargar tus archivos de expansión en Google Play, el usuario debe haber adquirido tu app desde Google Play. Google Play no proporcionará las URL para tus archivos de expansión si se instaló la app de otra forma.
  3. Cuando realices la descarga desde tu app, la URL que proporciona Google Play para cada archivo es única para cada descarga, y cada una se vence poco después de enviarse a tu app.
  4. Si actualizas tu app con un nuevo APK o subes varios APK para la misma app, puedes seleccionar archivos de expansión que hayas subido para un APK anterior. No cambia el nombre del archivo de expansión; conserva la versión recibida por el APK con el que se asocia originalmente el archivo.
  5. Si usas archivos de expansión junto con varios APK a fin de proporcionar diferentes archivos de expansión para varios dispositivos, aún debes subir el APK por separado para cada dispositivo con el objetivo de proporcionar un valor versionCode único y debes declarar diferentes filtros para cada APK.
  6. No puedes publicar una actualización de tu app si solo cambias los archivos de expansión; debes subir un nuevo APK para actualizarla. Si los cambios solo afectan a los elementos de tus archivos de expansión, puedes actualizar el archivo APK si solo cambias el objeto versionCode (y tal vez también el elemento versionName).

  7. No guardes otros datos en tu directorio obb/. Si debes descomprimir algunos datos, guárdalos en la ubicación que especifique el objeto getExternalFilesDir().
  8. No borres ni cambies el nombre del archivo de expansión .obb (a menos que realices una actualización). Si lo haces, Google Play (o tu propia app) descargarán el archivo de expansión de forma reiterada.
  9. Cuando actualices un archivo de expansión de forma manual, debes borrar el archivo de expansión anterior.

Cómo descargar los archivos de expansión

En la mayoría de los casos, Google Play descarga y guarda archivos de expansión en el dispositivo al mismo tiempo que instala o actualiza el APK. De esta manera, los archivos de expansión estarán disponibles cuando se inicie la app por primera vez. Sin embargo, en algunos casos, para descargar los archivos de expansión, tu app debe solicitarlos desde una URL que se te proporcionó en una respuesta del servicio de licencias de apps de Google Play.

Necesitas la siguiente lógica básica para descargar archivos de expansión:

  1. Cuando se inicie tu app, busca los archivos de expansión en la ubicación de almacenamiento compartido (en el directorio Android/obb/<package-name>/).
    1. Si los archivos de expansión están en esa ubicación, ya está todo listo, y tu app puede continuar.
    2. Si los archivos de expansión no están en esa ubicación:
      1. Realiza una solicitud mediante las licencias de apps de Google Play para obtener los nombres, los tamaños y las URL de los archivos de expansión de tu app.
      2. Usa las URL que proporciona Google Play para descargar y guardar los archivos de expansión. Debes guardar los archivos en la ubicación de almacenamiento compartido (Android/obb/<package-name>/) y usar el nombre exacto del archivo que se proporcione mediante la respuesta de Google Play.

        Nota: La URL que proporciona Google Play para tus archivos de expansión es única para cada descarga, y cada una vence poco después de que se envía a tu app.

Si tu app es gratuita (es decir, no es pagada), es probable que no hayas usado el servicio de licencias de apps. Este servicio se diseñó principalmente con el objetivo de que puedas aplicar políticas de licencia para tu app y asegurarte de que el usuario tenga derecho a usarla (es decir, él o ella la pagó de forma legal en Google Play). Para facilitar la funcionalidad de los archivos de expansión, se mejoró el servicio de licencias a fin de proporcionar una respuesta a tu app que incluya la URL de los archivos de expansión que se alojen en Google Play. Por lo tanto, incluso si tu app es gratuita para los usuarios, debes incluir la biblioteca de verificación de licencias (LVL) con el objetivo de usar los archivos de expansión de APK. Por supuesto, si tu app es gratuita, no necesitas aplicar la verificación de licencias; solo necesitas la biblioteca para realizar la solicitud que muestra la URL de tus archivos de expansión.

Nota: Independientemente de si tu app sea gratuita o no, Google Play muestra las URL del archivo de expansión solo si el usuario adquirió tu app en Google Play.

Además de la LVL, necesitas un conjunto de código que descargue los archivos de expansión mediante una conexión HTTP y los guarde en la ubicación correcta, en el almacenamiento compartido del dispositivo. A medida que realizas este procedimiento en tu app, debes tener en cuenta varias cuestiones:

  • Es posible que el dispositivo no tenga suficiente espacio para los archivos de expansión, por lo que debes verificar antes de comenzar la descarga y advertirle al usuario si no hay suficiente espacio.
  • Las descargas de archivos deben realizarse en un servicio que esté en segundo plano para evitar bloquear la interacción del usuario y para permitir que el usuario salga de la app mientras se completa la descarga.
  • Pueden producirse varios errores durante la solicitud y la descarga que debes resolver de manera óptima.
  • La conectividad de la red puede cambiar durante la descarga, por lo que debes controlar esos cambios y, si se interrumpe, debes reanudar la descarga cuando sea posible.
  • Si bien la descarga se realiza en segundo plano, debes proporcionar una notificación que indique el progreso de la descarga, notificarle al usuario cuando haya terminado y enviarlo de vuelta a tu app cuando la seleccione.

Para simplificarte esta tarea, hemos creado la biblioteca de descargas, que solicita las URL del archivo de expansión mediante el servicio de licencias, descarga los archivos de expansión, realiza todas las tareas que se mencionaron anteriormente e incluso permite que tu actividad detenga y reanude la descarga. Si agregas la biblioteca de descargas y enlazas algunos códigos a tu app, ya tendrás la codificación completa para descargar los archivos de expansión. Por lo tanto, para proporcionar la mejor experiencia del usuario con un mínimo esfuerzo de tu parte, te recomendamos que uses esta biblioteca con el objetivo de descargar tus archivos de expansión. En las siguientes secciones, se explica cómo integrar la biblioteca en tu app.

Si deseas desarrollar tu propia solución para descargar los archivos de expansión con las URL de Google Play, debes seguir la documentación sobreLicencias de apps a fin de realizar una solicitud de licencia, y luego recuperar los nombres, los tamaños y las URL de los elementos adicionales de la respuesta. Debes usar la clase APKExpansionPolicy (que se incluye en la biblioteca de verificación de licencias) como tu política de licencias, que captura los nombres, los tamaños y las URL de los archivos de expansión desde el servicio de licencias.

Acerca de la biblioteca de descargas

Para usar los archivos de expansión de APK con tu app y proporcionar la mejor experiencia del usuario con un mínimo esfuerzo de tu parte, te recomendamos que uses la biblioteca de descargas, que se incluye en el paquete de la biblioteca de expansión de APK de Google Play. Esta biblioteca descarga tus archivos de expansión en un servicio que se ejecuta en segundo plano, muestra una notificación de usuario con el estado de descarga, controla la pérdida de conectividad de red, reanuda la descarga cuando sea posible y mucho más.

Para implementar las descargas de archivos de expansión con la biblioteca de descargas, necesitas hacer lo siguiente:

  • Extiende una subclase Service y una subclase BroadcastReceiver especial que requieran solo unas pocas líneas de código.
  • Agrega un poco de lógica a la actividad principal que verifique si ya se descargaron los archivos de expansión y que, de no ser así, invoque el proceso de descarga y muestre una IU de progreso.
  • Implementa una interfaz de devolución de llamada con algunos métodos en la actividad principal que reciba actualizaciones sobre el progreso de la descarga.

En las siguientes secciones, se explica cómo configurar tu app mediante la biblioteca de descargas.

Cómo prepararse para usar la biblioteca de descargas

Para usar la biblioteca de descargas, debes descargar dos paquetes en SDK Manager y agregar las bibliotecas apropiadas a tu app.

Primero, abre SDK Manager de Android (Tools > SDK Manager) y, en Appearance & Behavior > System Settings > Android SDK, selecciona la pestaña SDK Tools para elegir y descargar:

  • El paquete de la biblioteca de licencias de Google Play
  • El paquete de la biblioteca de expansión de APK de Google Play

Crea un nuevo módulo de biblioteca para la biblioteca de verificación de licencias y la biblioteca de descargas. Para cada biblioteca:

  1. Haz clic en File > New > New Module.
  2. En la ventana Create New Module, selecciona Android Library y luego selecciona Next.
  3. Especifica un app/Library name como "Google Play License Library" y "Google Play Downloader Library", elige Minimum SDK level y luego selecciona Finish.
  4. Selecciona File > Project Structure.
  5. Elige la pestaña Properties y, en Library Repository, ingresa la biblioteca desde el directorio <sdk>/extras/google/ (play_licensing/ para la biblioteca de verificación de licencias o play_apk_expansion/downloader_library/ para el biblioteca de descargas).
  6. Selecciona OK para crear el nuevo módulo.

Nota: La biblioteca de descargas depende de la biblioteca de verificación de licencias. Asegúrate de agregar la biblioteca de verificación de licencias a las propiedades del proyecto de la biblioteca de descargas.

O bien, desde una línea de comandos, actualiza tu proyecto para incluir las bibliotecas:

  1. Cambia los directorios a <sdk>/tools/.
  2. Ejecuta el objeto android update project con la opción --library para agregar la LVL y la biblioteca de descargas a tu proyecto. Por ejemplo:
        android update project --path ~/Android/MyApp \
        --library ~/android_sdk/extras/google/market_licensing \
        --library ~/android_sdk/extras/google/market_apk_expansion/downloader_library
        

Una vez que agregues la biblioteca de verificación de licencias y la biblioteca de descargas a tu app, podrás integrar rápidamente la función de descargar archivos de expansión desde Google Play. El formato que elijas para los archivos de expansión y la manera en que los leas desde el almacenamiento compartido es una implementación independiente que deberás considerar según las necesidades de tu app.

Sugerencia: El paquete de expansión de APK incluye una app de ejemplo que muestra cómo usar la biblioteca de descargas en una app. El ejemplo usa una tercera biblioteca disponible en el paquete de expansión de APK denominada biblioteca de ZIP de expansión de APK. Si tienes pensado usar archivos ZIP para tus archivos de expansión, te sugerimos que también agregues la biblioteca de ZIP a tu app. Para obtener más información, consulta la sección sobre Cómo usar la biblioteca de ZIP de expansión de APK que aparece más adelante.

Cómo declarar los permisos del usuario

Para descargar los archivos de expansión, la biblioteca de descargas requiere los siguientes permisos, que debes declarar en el archivo de manifiesto de tu app:

    <manifest ...>
        <!-- Required to access Google Play Licensing -->
        <uses-permission android:name="com.android.vending.CHECK_LICENSE" />

        <!-- Required to download files from Google Play -->
        <uses-permission android:name="android.permission.INTERNET" />

        <!-- Required to keep CPU alive while downloading files
            (NOT to keep screen awake) -->
        <uses-permission android:name="android.permission.WAKE_LOCK" />

        <!-- Required to poll the state of the network connection
            and respond to changes -->
        <uses-permission
            android:name="android.permission.ACCESS_NETWORK_STATE" />

        <!-- Required to check whether Wi-Fi is enabled -->
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

        <!-- Required to read and write the expansion files on shared storage -->
        <uses-permission
            android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        ...
    </manifest>
    

Nota: De forma predeterminada, la biblioteca de descargas requiere API nivel 4, pero la biblioteca de ZIP de expansión de APK requiere API nivel 5.

Cómo implementar el servicio de descargas

Para realizar descargas en segundo plano, la biblioteca de descargas proporciona su propia subclase Service denominada DownloaderService que debes extender. Además de descargar los archivos de expansión, la subclase DownloaderService también hace lo siguiente:

  • Registra el objeto BroadcastReceiver que detecta cambios en la conectividad de red del dispositivo (la transmisión CONNECTIVITY_ACTION) para detener la descarga cuando sea necesario (por ejemplo, cuando se pierde conectividad) y para reanudar la descarga cuando sea posible (por ejemplo, cuando se adquiere conectividad).
  • Programa una alarma RTC_WAKEUP para volver a intentar la descarga en los casos en que no funcione el servicio.
  • Crea un elemento Notification personalizado que muestre el progreso de la descarga y cualquier error o cambio de estado.
  • Permite que tu app detenga y reanude la descarga de forma manual.
  • Verifica que el almacenamiento compartido esté activado y disponible, que los archivos ya no existan y que haya suficiente espacio, todo antes de descargar los archivos de expansión. Luego, le notifica al usuario si algo de lo anterior no es verdadero.

Todo lo que necesitas hacer es crear una clase en tu app que extienda la clase DownloaderService y anular tres métodos para proporcionar los detalles específicos de la app:

getPublicKey()
Debe mostrar una string que sea la clave pública RSA codificada en Base64 para tu cuenta de publicador, disponible en la página de perfil de Play Console (consulta Configuración de licencias).
getSALT()
Debe mostrar un arreglo de bytes aleatorios que el objeto Policy de licencia usa para crear un elemento Obfuscator. La sal garantiza que tu archivo SharedPreferences ofuscado en el que se guardan tus datos de licencia sea único e indetectable.
getAlarmReceiverClassName()
Debe mostrar el nombre de clase BroadcastReceiver en tu app que debería recibir la alarma que indica que debe reiniciarse la descarga (lo que podría suceder si se detiene el servicio de descargas de forma inesperada).

Por ejemplo, se incluye una implementación completa de DownloaderService:

Kotlin

    // You must use the public key belonging to your publisher account
    const val BASE64_PUBLIC_KEY = "YourLVLKey"
    // You should also modify this salt
    val SALT = byteArrayOf(
            1, 42, -12, -1, 54, 98, -100, -12, 43, 2,
            -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
    )

    class SampleDownloaderService : DownloaderService() {

        override fun getPublicKey(): String = BASE64_PUBLIC_KEY

        override fun getSALT(): ByteArray = SALT

        override fun getAlarmReceiverClassName(): String = SampleAlarmReceiver::class.java.name
    }
    

Java

    public class SampleDownloaderService extends DownloaderService {
        // You must use the public key belonging to your publisher account
        public static final String BASE64_PUBLIC_KEY = "YourLVLKey";
        // You should also modify this salt
        public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98,
                -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
        };

        @Override
        public String getPublicKey() {
            return BASE64_PUBLIC_KEY;
        }

        @Override
        public byte[] getSALT() {
            return SALT;
        }

        @Override
        public String getAlarmReceiverClassName() {
            return SampleAlarmReceiver.class.getName();
        }
    }
    

Aviso: Debes actualizar el valor de BASE64_PUBLIC_KEY para que sea la clave pública que pertenezca a tu cuenta de publicador. Puedes encontrar la clave en Developer Console, en la información de tu perfil. Es necesaria incluso cuando se prueben las descargas.

Recuerda declarar el servicio en tu archivo de manifiesto:

    <app ...>
        <service android:name=".SampleDownloaderService" />
        ...
    </app>
    

Cómo implementar el receptor de alarma

A fin de supervisar el progreso de las descargas de archivos y reiniciarlas cuando sea necesario, la subclase DownloaderService programa una alarma RTC_WAKEUP que envía un objeto Intent a un elemento BroadcastReceiver de tu app. Debes definir el elemento BroadcastReceiver para llamar a una API desde la biblioteca de descargas que verifique el estado de la descarga y la reinicie cuando sea necesario.

Simplemente necesitas anular el método onReceive() para llamar al objeto DownloaderClientMarshaller.startDownloadServiceIfRequired().

Por ejemplo:

Kotlin

    class SampleAlarmReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            try {
                DownloaderClientMarshaller.startDownloadServiceIfRequired(
                        context,
                        intent,
                        SampleDownloaderService::class.java
                )
            } catch (e: PackageManager.NameNotFoundException) {
                e.printStackTrace()
            }
        }
    }
    

Java

    public class SampleAlarmReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            try {
                DownloaderClientMarshaller.startDownloadServiceIfRequired(context,
                    intent, SampleDownloaderService.class);
            } catch (NameNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    

Ten en cuenta que esta es la clase para la que debes mostrar el nombre en el método getAlarmReceiverClassName() de tu servicio (consulta la sección anterior).

Recuerda declarar el receptor en tu archivo de manifiesto:

    <app ...>
        <receiver android:name=".SampleAlarmReceiver" />
        ...
    </app>
    

Cómo comenzar la descarga

La actividad principal en tu app (la que comienza con el ícono de selector) es responsable de verificar si los archivos de expansión ya están en el dispositivo y de comenzar la descarga si no están.

Para comenzar la descarga mediante la biblioteca de descargas, se requieren los siguientes procedimientos:

  1. Verifica si se descargaron los archivos.

    La biblioteca de descargas incluye algunas API de la clase Helper para ayudar con este proceso:

    • getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)
    • doesFileExist(Context c, String fileName, long fileSize)

    Por ejemplo, la app de ejemplo que se proporciona en el paquete de expansión del APK llama al siguiente método en el método onCreate() de la actividad para verificar si los archivos de expansión ya existen en el dispositivo:

    Kotlin

        fun expansionFilesDelivered(): Boolean {
            xAPKS.forEach { xf ->
                Helpers.getExpansionAPKFileName(this, xf.isBase, xf.fileVersion).also { fileName ->
                    if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false))
                        return false
                }
            }
            return true
        }
        

    Java

        boolean expansionFilesDelivered() {
            for (XAPKFile xf : xAPKS) {
                String fileName = Helpers.getExpansionAPKFileName(this, xf.isBase,
                    xf.fileVersion);
                if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false))
                    return false;
            }
            return true;
        }
        

    En este caso, cada objeto XAPKFile contiene el número de versión y el tamaño de un archivo de expansión conocido, y un valor booleano por si es el archivo principal de expansión. (Para obtener más detalles, consulta la clase SampleDownloaderActivity de la app de ejemplo).

    Si este método muestra el valor "false", la app debe comenzar la descarga.

  2. Para ello, llama al método estático DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass).

    El método incluye los siguientes parámetros:

    • context: Es el objeto Context de tu app.
    • notificationClient: Es un elemento PendingIntent que permite comenzar tu actividad principal. Se usa en el objeto Notification que crea la subclase DownloaderService para mostrar el progreso de la descarga. Cuando el usuario selecciona la notificación, el sistema invoca el elemento PendingIntent que suministras aquí, y debes abrir la actividad que muestra el progreso de la descarga (por lo general, la misma actividad que comenzó la descarga).
    • serviceClass: Es el objeto Class que se usa con el propósito de implementar la subclase DownloaderService, que se requiere para iniciar el servicio y comenzar la descarga cuando sea necesario.

    El método muestra un valor entero que indica si se requiere o no la descarga. Los valores posibles son:

    • NO_DOWNLOAD_REQUIRED: Se muestra si los archivos ya existen o una descarga ya está en curso.
    • LVL_CHECK_REQUIRED: Se muestra si se requiere una verificación de licencia para adquirir las URL del archivo de expansión.
    • DOWNLOAD_REQUIRED: Se muestra si ya se conocen las URL del archivo de expansión, pero no se descargaron.

    El comportamiento de los valores LVL_CHECK_REQUIRED y DOWNLOAD_REQUIRED es en esencia el mismo, y por lo general no es necesario preocuparse por ellos. En la actividad principal que llama al objeto startDownloadServiceIfRequired(), simplemente puedes verificar si la respuesta es NO_DOWNLOAD_REQUIRED o no. Si es diferente a NO_DOWNLOAD_REQUIRED, la biblioteca de descargas comenzará la descarga, y deberás actualizar la IU de tu actividad para mostrar el progreso (consulta el siguiente paso). Si la respuesta es NO_DOWNLOAD_REQUIRED, los archivos están disponibles, y se puede iniciar la app.

    Por ejemplo:

    Kotlin

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            // Check if expansion files are available before going any further
            if (!expansionFilesDelivered()) {
                val pendingIntent =
                        // Build an Intent to start this activity from the Notification
                        Intent(this, MainActivity::class.java).apply {
                            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
                        }.let { notifierIntent ->
                            PendingIntent.getActivity(
                                    this,
                                    0,
                                    notifierIntent,
                                    PendingIntent.FLAG_UPDATE_CURRENT
                            )
                        }
    
                // Start the download service (if required)
                val startResult: Int = DownloaderClientMarshaller.startDownloadServiceIfRequired(
                        this,
                        pendingIntent,
                        SampleDownloaderService::class.java
                )
                // If download has started, initialize this activity to show
                // download progress
                if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                    // This is where you do set up to display the download
                    // progress (next step)
                    ...
                    return
                } // If the download wasn't necessary, fall through to start the app
            }
            startApp() // Expansion files are available, start the app
        }
        

    Java

        @Override
        public void onCreate(Bundle savedInstanceState) {
            // Check if expansion files are available before going any further
            if (!expansionFilesDelivered()) {
                // Build an Intent to start this activity from the Notification
                Intent notifierIntent = new Intent(this, MainActivity.getClass());
                notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
                                        Intent.FLAG_ACTIVITY_CLEAR_TOP);
                ...
                PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                        notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
                // Start the download service (if required)
                int startResult =
                    DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                                pendingIntent, SampleDownloaderService.class);
                // If download has started, initialize this activity to show
                // download progress
                if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                    // This is where you do set up to display the download
                    // progress (next step)
                    ...
                    return;
                } // If the download wasn't necessary, fall through to start the app
            }
            startApp(); // Expansion files are available, start the app
        }
        
  3. Cuando el método startDownloadServiceIfRequired() muestra un valor diferente a NO_DOWNLOAD_REQUIRED, llama al elemento DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService) para crear una instancia de IStub. La instancia de IStub proporciona una vinculación entre tu actividad y el servicio de descargas, de modo que tu actividad reciba devoluciones de llamada sobre el progreso de la descarga.

    A fin de llamar al elemento CreateStub() para crear una instancia de IStub, debes enviarle una implementación de la interfaz IDownloaderClient y tu implementación de DownloaderService. En la siguiente sección sobre Cómo recibir el progreso de la descarga, se analiza la interfaz IDownloaderClient, que en general debes implementar en tu clase Activity para poder actualizar la IU de actividad cuando cambie el estado de descarga.

    Te recomendamos que llames al objeto CreateStub() para crear una instancia de IStub durante el método onCreate() de tu actividad, después de que el elemento startDownloadServiceIfRequired() comience la descarga.

    Por ejemplo, en la muestra de código anterior para el método onCreate(), puedes responder al resultado startDownloadServiceIfRequired() de la siguiente manera:

    Kotlin

                // Start the download service (if required)
                val startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(
                        this@MainActivity,
                        pendingIntent,
                        SampleDownloaderService::class.java
                )
                // If download has started, initialize activity to show progress
                if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                    // Instantiate a member instance of IStub
                    downloaderClientStub =
                            DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService::class.java)
                    // Inflate layout that shows download progress
                    setContentView(R.layout.downloader_ui)
                    return
                }
        

    Java

                // Start the download service (if required)
                int startResult =
                    DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
                                pendingIntent, SampleDownloaderService.class);
                // If download has started, initialize activity to show progress
                if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
                    // Instantiate a member instance of IStub
                    downloaderClientStub = DownloaderClientMarshaller.CreateStub(this,
                            SampleDownloaderService.class);
                    // Inflate layout that shows download progress
                    setContentView(R.layout.downloader_ui);
                    return;
                }
        

    Después de que se muestra el método onCreate(), tu actividad recibe una llamada al objeto onResume(), que es donde debes llamar al elemento connect() de la subclase IStub, y enviarle el parámetro Context de tu app. Por el contrario, debes llamar al objeto disconnect() en la devolución de llamada de onStop() de tu actividad.

    Kotlin

        override fun onResume() {
            downloaderClientStub?.connect(this)
            super.onResume()
        }
    
        override fun onStop() {
            downloaderClientStub?.disconnect(this)
            super.onStop()
        }
        

    Java

        @Override
        protected void onResume() {
            if (null != downloaderClientStub) {
                downloaderClientStub.connect(this);
            }
            super.onResume();
        }
    
        @Override
        protected void onStop() {
            if (null != downloaderClientStub) {
                downloaderClientStub.disconnect(this);
            }
            super.onStop();
        }
        

    Llamar al objeto connect() de la subclase IStub vincula tu actividad al elemento DownloaderService, de modo que tu actividad reciba devoluciones de llamada con respecto a los cambios en el estado de descarga mediante la interfaz IDownloaderClient.

Cómo recibir el progreso de la descarga

Para recibir actualizaciones sobre el progreso de la descarga e interactuar con el elemento DownloaderService, debes implementar la interfaz IDownloaderClient de la biblioteca de descargas. Por lo general, la actividad que usas para comenzar la descarga debe implementar esta interfaz a fin de mostrar el progreso de la descarga y enviar solicitudes al servicio.

Los métodos de interfaz obligatorios para IDownloaderClient son los siguientes:

onServiceConnected(Messenger m)
Después de que crees una instancia de IStub en tu actividad, recibirás una llamada a ese método, que enviará un objeto Messenger relacionado con tu instancia de DownloaderService. A fin de enviar solicitudes al servicio, como detener y reanudar descargas, debes llamar al elemento DownloaderServiceMarshaller.CreateProxy() para recibir la interfaz IDownloaderService relacionada con el servicio.

Se recomienda una implementación similar a la siguiente:

Kotlin

    private var remoteService: IDownloaderService? = null
    ...

    override fun onServiceConnected(m: Messenger) {
        remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
            downloaderClientStub?.messenger?.also { messenger ->
                onClientUpdated(messenger)
            }
        }
    }
    

Java

    private IDownloaderService remoteService;
    ...

    @Override
    public void onServiceConnected(Messenger m) {
        remoteService = DownloaderServiceMarshaller.CreateProxy(m);
        remoteService.onClientUpdated(downloaderClientStub.getMessenger());
    }
    

Con el objeto IDownloaderService inicializado, puedes enviar comandos al servicio de descargas, como detener y reanudar la descarga (requestPauseDownload() y requestContinueDownload()).

onDownloadStateChanged(int newState)
El servicio de descargas llama a este valor cuando se produce un cambio en el estado de descarga (por ejemplo, cuando comienza o finaliza la descarga).

El valor newState será uno de varios valores posibles que especifique una de las constantes STATE_* de la clase IDownloaderClient.

A fin de proporcionarles un mensaje útil a los usuarios, llama al objeto Helpers.getDownloaderStringResourceIDFromState() a modo de solicitar una string correspondiente para cada estado. Esto muestra el ID de recurso de una de las strings agrupadas con la biblioteca de descargas. Por ejemplo, la string "Download paused because you are roaming" (Se pausó la descarga porque estás utilizando roaming) le corresponde al elemento STATE_PAUSED_ROAMING.

onDownloadProgress(DownloadProgressInfo progress)
El servicio de descargas llama a este valor con el objetivo de entregar un elemento DownloadProgressInfo, que describe información diversa sobre el progreso de la descarga, lo que incluye el tiempo restante estimado, la velocidad actual, el progreso general y el progreso total para que puedas actualizar la IU del progreso.

Sugerencia: Para ver ejemplos de estas devoluciones de llamada que actualizan la IU del progreso de la descarga, consulta SampleDownloaderActivity en la app de ejemplo que se proporciona con el paquete de expansión de APK.

Es posible que sean útiles algunos de los siguientes métodos públicos para la interfaz IDownloaderService:

requestPauseDownload()
Pausa la descarga.
requestContinueDownload()
Reanuda una descarga pausada.
setDownloadFlags(int flags)
Establece las preferencias del usuario para los tipos de red en los que está bien descargar los archivos. La implementación actual admite una marca, FLAGS_DOWNLOAD_OVER_CELLULAR, pero puedes agregar otras. De forma predeterminada, esta marca no está habilitada, por lo que el usuario debe estar conectado a una red Wi-Fi para descargar los archivos de expansión. Te recomendamos que proporciones una preferencia de usuario para habilitar las descargas mediante la red móvil. En ese caso, puedes llamar a:

Kotlin

    remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
        ...
        setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR)
    }
    

Java

    remoteService
        .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
    

Cómo usar APKExpansionPolicy

Si decides crear tu propio servicio de descargas en lugar de usar la biblioteca de descargas de Google Play, aun así debes usar la clase APKExpansionPolicy que se proporciona en la biblioteca de verificación de licencias. Esa clase APKExpansionPolicy es casi idéntica a ServerManagedPolicy (disponible en la biblioteca de verificación de licencias de Google Play), pero incluye un control más para los elementos adicionales de las respuesta de expansión de APK.

Nota: Si no usas la biblioteca de descargas como se explicó en la sección anterior, la biblioteca realizará toda la interacción con APKExpansionPolicy, por lo que no será necesario usar esta clase directamente.

Esta clase incluye métodos para ayudarte a obtener la información necesaria sobre los archivos de expansión disponibles:

  • getExpansionURLCount()
  • getExpansionURL(int index)
  • getExpansionFileName(int index)
  • getExpansionFileSize(int index)

Para obtener más información sobre cómo usar la clase APKExpansionPolicy cuando no uses la biblioteca de descargas, consulta la documentación sobre cómo agregar licencias a tu app, en la que se explica cómo implementar una política de licencia como esta.

Cómo leer el archivo de expansión

Una vez que se guardan los archivos de expansión de APK en el dispositivo, la forma de leerlos depende del tipo de archivo que hayas usado. Como se explica en la Descripción general, los archivos de expansión pueden ser cualquier tipo de archivo que desees, pero se les cambia el nombre con un formato del nombre del archivo específico y se guardan en <shared-storage>/Android/obb/<package-name>/.

Sin importar la forma en que leas tus archivos, siempre debes verificar primero que el almacenamiento externo esté disponible para leer. Existe la posibilidad de que el usuario tenga el almacenamiento activado en una computadora mediante USB o que haya quitado la tarjeta SD.

Nota: Cuando se inicie tu app, siempre debes verificar si el espacio de almacenamiento externo está disponible y puede leerse cuando se llama al elemento getExternalStorageState(). Esto muestra una de las varias string posibles que representan el estado del almacenamiento externo. Para que tu app pueda leerla, el valor que se muestra debe ser MEDIA_MOUNTED.

Cómo obtener los nombres de los archivos

Como se explica en la Descripción general, tus archivos de expansión de APK se guardan con un formato específico del nombre del archivo:

    [main|patch].<expansion-version>.<package-name>.obb
    

Para obtener la ubicación y los nombres de tus archivos de expansión, debes usar los métodos getExternalStorageDirectory() y getPackageName() a fin de crear la ruta a tus archivos.

A continuación, se incluye un método que puedes usar en tu app para obtener un arreglo que contenga la ruta completa a ambos archivos de expansión:

Kotlin

    fun getAPKExpansionFiles(ctx: Context, mainVersion: Int, patchVersion: Int): Array<String> {
        val packageName = ctx.packageName
        val ret = mutableListOf<String>()
        if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
            // Build the full path to the app's expansion files
            val root = Environment.getExternalStorageDirectory()
            val expPath = File(root.toString() + EXP_PATH + packageName)

            // Check that expansion file path exists
            if (expPath.exists()) {
                if (mainVersion > 0) {
                    val strMainPath = "$expPath${File.separator}main.$mainVersion.$packageName.obb"
                    val main = File(strMainPath)
                    if (main.isFile) {
                        ret += strMainPath
                    }
                }
                if (patchVersion > 0) {
                    val strPatchPath = "$expPath${File.separator}patch.$mainVersion.$packageName.obb"
                    val main = File(strPatchPath)
                    if (main.isFile) {
                        ret += strPatchPath
                    }
                }
            }
        }
        return ret.toTypedArray()
    }
    

Java

    // The shared path to all app expansion files
    private final static String EXP_PATH = "/Android/obb/";

    static String[] getAPKExpansionFiles(Context ctx, int mainVersion,
          int patchVersion) {
        String packageName = ctx.getPackageName();
        Vector<String> ret = new Vector<String>();
        if (Environment.getExternalStorageState()
              .equals(Environment.MEDIA_MOUNTED)) {
            // Build the full path to the app's expansion files
            File root = Environment.getExternalStorageDirectory();
            File expPath = new File(root.toString() + EXP_PATH + packageName);

            // Check that expansion file path exists
            if (expPath.exists()) {
                if ( mainVersion > 0 ) {
                    String strMainPath = expPath + File.separator + "main." +
                            mainVersion + "." + packageName + ".obb";
                    File main = new File(strMainPath);
                    if ( main.isFile() ) {
                            ret.add(strMainPath);
                    }
                }
                if ( patchVersion > 0 ) {
                    String strPatchPath = expPath + File.separator + "patch." +
                            mainVersion + "." + packageName + ".obb";
                    File main = new File(strPatchPath);
                    if ( main.isFile() ) {
                            ret.add(strPatchPath);
                    }
                }
            }
        }
        String[] retArray = new String[ret.size()];
        ret.toArray(retArray);
        return retArray;
    }
    

Para llamar a este método, puedes pasarle el objeto Context de tu app y la versión del archivo de expansión que desees.

Existen muchas formas de determinar el número de versión del archivo de expansión. Una forma simple es consultar el nombre del archivo de expansión con el método getExpansionFileName(int index) de la clase APKExpansionPolicy a fin de guardar la versión en un archivo SharedPreferences cuando comienza la descarga. Luego, para obtener el código de versión, lee el archivo SharedPreferences cuando desees acceder al archivo de expansión.

Para obtener más información sobre la lectura del almacenamiento compartido, consulta la documentación sobre el almacenamiento de datos.

Cómo usar la biblioteca de ZIP de expansión de APK

El paquete de expansión de APK de Google Market incluye una biblioteca denominada Biblioteca de ZIP de expansión de APK (ubicada en <sdk>/extras/google/google_market_apk_expansion/zip_file/), que es una biblioteca opcional que te ayuda a leer tus archivos de expansión cuando se guardan como archivos ZIP. Usar esta biblioteca te permite leer con facilidad los recursos de tus archivos de expansión ZIP como un sistema virtual de archivos.

La biblioteca de ZIP de expansión de APK incluye las siguientes clases y API:

APKExpansionSupport
Proporciona algunos métodos para acceder a los nombres de los archivos de expansión y a los archivos ZIP:
getAPKExpansionFiles()
El mismo método mencionado anteriormente que muestra la ruta completa del archivo a ambos archivos de expansión.
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)
Muestra un elemento ZipResourceFile que representa la suma del archivo principal y del archivo de parche. Es decir, si especificas tanto mainVersion como patchVersion, se muestra un elemento ZipResourceFile que proporciona acceso de lectura a todos los datos, con los datos del archivo de parche combinados sobre el archivo principal.
ZipResourceFile
Representa un archivo ZIP en el almacenamiento compartido y realiza todo el trabajo para proporcionar un sistema virtual de archivos basado en tus archivos ZIP. Para obtener una instancia con el objeto APKExpansionSupport.getAPKExpansionZipFile() o con el elemento ZipResourceFile, envía la ruta a tu archivo de expansión. Esta clase incluye una variedad de métodos útiles, pero en general no es necesario que accedas a la mayoría de ellos. Los siguientes son algunos métodos importantes:
getInputStream(String assetPath)
Proporciona un objeto InputStream para leer un archivo dentro del archivo ZIP. El elemento assetPath debe ser la ruta al archivo deseado, en relación con la raíz del contenido del archivo ZIP.
getAssetFileDescriptor(String assetPath)
Proporciona un objeto AssetFileDescriptor para un archivo dentro del archivo ZIP. El elemento assetPath debe ser la ruta al archivo deseado, en relación con la raíz del contenido del archivo ZIP. Es útil para ciertas API de Android que requieren un objeto AssetFileDescriptor, como algunas API MediaPlayer.
APEZProvider
La mayoría de las apps no necesitan usar esta clase. Esta clase define un elemento ContentProvider que reúne los datos de los archivos ZIP mediante un proveedor de contenido Uri para proporcionar acceso a los archivos de ciertas API de Android que esperan el acceso Uri a los archivos multimedia. Por ejemplo, es útil si deseas reproducir un video con VideoView.setVideoURI().

Cómo omitir la compresión ZIP de archivos multimedia

Si usas tus archivos de expansión con el objetivo de almacenar archivos multimedia, un archivo ZIP te permite usar llamadas de reproducción multimedia de Android que proporcionan controles de desplazamiento y longitud (como los objetos MediaPlayer.setDataSource() y SoundPool.load()). Para que funcione, no debes realizar ninguna compresión adicional en los archivos multimedia cuando crees los paquetes ZIP. Por ejemplo, cuando uses la herramienta zip, debes usar la opción -n para especificar los sufijos de archivo que no se deben comprimir:

zip -n .mp4;.ogg main_expansion media_files

Cómo leer desde un archivo ZIP

Cuando usas la biblioteca de ZIP de expansión de APK, necesitas lo siguiente para leer un archivo de tu ZIP:

Kotlin

    // Get a ZipResourceFile representing a merger of both the main and patch files
    val expansionFile =
            APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion)

    // Get an input stream for a known file inside the expansion file ZIPs
    expansionFile.getInputStream(pathToFileInsideZip).use {
        ...
    }
    

Java

    // Get a ZipResourceFile representing a merger of both the main and patch files
    ZipResourceFile expansionFile =
        APKExpansionSupport.getAPKExpansionZipFile(appContext,
            mainVersion, patchVersion);

    // Get an input stream for a known file inside the expansion file ZIPs
    InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
    

El código anterior proporciona acceso a cualquier archivo que exista en el archivo principal o de parche de expansión, cuando leas desde un mapa combinado de todos los archivos de ambos archivos de expansión. Todo lo que necesitas con el objetivo de proporcionar el método getAPKExpansionFile() es el elemento android.content.Context de tu app y el número de versión para el archivo principal y de parche de expansión.

Si prefieres leer desde un archivo de expansión específico, puedes usar el constructor ZipResourceFile con la ruta al archivo de expansión que desees:

Kotlin

    // Get a ZipResourceFile representing a specific expansion file
    val expansionFile = ZipResourceFile(filePathToMyZip)

    // Get an input stream for a known file inside the expansion file ZIPs
    expansionFile.getInputStream(pathToFileInsideZip).use {
        ...
    }
    

Java

    // Get a ZipResourceFile representing a specific expansion file
    ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip);

    // Get an input stream for a known file inside the expansion file ZIPs
    InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
    

Para obtener más información sobre el uso de esta biblioteca para tus archivos de expansión, consulta la clase SampleDownloaderActivity de la app de ejemplo, que incluye un código adicional a fin de verificar los archivos descargados con CRC. Ten en cuenta que si usas este ejemplo como base para tu propia implementación, requiere que declares el tamaño de bytes de tus archivos de expansión en el arreglo xAPKS.

Cómo probar tus archivos de expansión

Antes de publicar tu app, debes probar dos cosas: leer los archivos de expansión y descargarlos.

Cómo probar la lectura de archivos

Antes de subir tu app a Google Play, debes probar la capacidad de tu app para leer los archivos desde el almacenamiento compartido. Todo lo que debes hacer es agregar los archivos a la ubicación correcta en el almacenamiento compartido del dispositivo y luego iniciar tu app:

  1. En tu dispositivo, crea el directorio apropiado en el almacenamiento compartido, en donde Google Play guardará tus archivos.

    Por ejemplo, si el nombre de tu paquete es com.example.android, debes crear el directorio Android/obb/com.example.android/ en el espacio de almacenamiento compartido. (Conecta el dispositivo de prueba a tu computadora para activar el almacenamiento compartido y crear este directorio de forma manual).

  2. Agrega de forma manual los archivos de expansión a ese directorio. Asegúrate de cambiar el nombre de tus archivos para que coincidan con el formato del nombre del archivo que utilizará Google Play.

    Por ejemplo, sin importar el tipo de archivo, el archivo principal de expansión para la app com.example.android debe ser main.0300110.com.example.android.obb. El código de versión puede ser el valor que desees. Recuerda lo siguiente:

    • El archivo principal de expansión siempre comienza con main y el archivo de parche con patch.
    • El nombre del paquete siempre coincide con el nombre de APK al que se adjunta el archivo en Google Play.
  3. Ahora que los archivos de expansión están en el dispositivo, puedes instalar y ejecutar tu app para probar tus archivos de expansión.

Ten en cuenta la siguiente información sobre el manejo de los archivos de expansión:

  • No borres ni cambies el nombre de los archivos de expansión .obb (incluso si descomprimes los datos en una ubicación diferente). Si lo haces, Google Play (o tu propia app) descargarán el archivo de expansión de forma reiterada.
  • No guardes otros datos en tu directorio obb/. Si debes descomprimir algunos datos, guárdalos en la ubicación que especifique el objeto getExternalFilesDir().

Cómo probar la descarga de archivos

Dado que tu app a veces debe descargar de forma manual los archivos de expansión cuando se abre por primera vez, es importante que pruebes este proceso para asegurarte de que tu app pueda consultar con éxito las URL, descargar los archivos y guardarlos en el dispositivo.

A fin de probar la implementación del procedimiento de descarga manual de tu app, puedes publicarla en el segmento de prueba interna, de modo que solo esté disponible para los verificadores autorizados. Si todo funciona como se espera, tu app debería comenzar a descargar los archivos de expansión en cuanto comience la actividad principal.

Nota: Antes se podía probar el funcionamiento de una app subiendo una versión "en borrador" no publicada. Esta función ya no está disponible. En su lugar, debes publicarla en un segmento de prueba interna, cerrada o abierta. Para obtener más información, consulta Ya no se admiten apps de borrador.

Cómo actualizar tu app

Uno de los principales beneficios de usar archivos de expansión en Google Play es la capacidad de actualizar tu app sin volver a descargar todos los elementos originales. Dado que Google Play te permite proporcionar dos archivos de expansión con cada APK, puedes usar el segundo archivo como un "parche" que proporciona actualizaciones y nuevos elementos. Si lo haces, se evita la necesidad de volver a descargar el archivo principal de expansión, que podría ser grande y costoso para los usuarios.

El archivo de parche de expansión es técnicamente el mismo que el archivo principal de expansión, y ni el sistema Android ni Google Play aplican parches entre el archivo principal y de parche de expansión. El código de tu app debe realizar los parches necesarios por su cuenta.

Si usas archivos ZIP como tus archivos de expansión, la biblioteca de ZIP de expansión de APK que se incluye con el paquete de expansión de APK tendrá la capacidad de fusionar el archivo de parche con el archivo principal de expansión.

Nota: Incluso si solo tienes que hacer cambios en el archivo de parche de expansión, debes actualizar el APK para que Google Play realice una actualización. Si no necesitas cambios de código en la app, simplemente debes actualizar el objeto versionCode en el manifiesto.

Siempre que no cambies el archivo principal de expansión que se asocia con el APK en Play Console, los usuarios que hayan instalado tu app no descargarán el archivo principal de expansión. Los usuarios existentes reciben solo el APK actualizado y el nuevo archivo de parche de expansión (y conservan el archivo principal de expansión anterior).

A continuación, se incluyen algunos problemas para tener en cuenta con respecto a las actualizaciones de los archivos de expansión:

  • Tu app solo puede tener dos archivos de expansión al mismo tiempo: un archivo principal de expansión y un archivo de parche de expansión. Durante una actualización de un archivo, Google Play borra la versión anterior (y tu app también lo debe hacer cuando realiza actualizaciones manuales).
  • Cuando agregues un archivo de parche de expansión, el sistema Android en realidad no aplicará parches en tu app ni en el archivo principal de expansión. Debes diseñar tu app para que sea compatible con los datos del parche. Sin embargo, el paquete de expansión de APK incluye una biblioteca para usar archivos ZIP como archivos de expansión, que combina los datos del archivo de parche con el archivo principal de expansión a fin de que puedas leer con facilidad todos los datos del archivo de expansión.