Cómo integrar Asset Delivery (Kotlin y Java)

Sigue los pasos que se indican en esta guía para acceder a los paquetes de elementos de tu app desde el código de Java.

Cómo compilar para Kotlin y Java

Usa los siguientes pasos para compilar Play Asset Delivery en el Android App Bundle de tu proyecto. No es necesario que uses Android Studio para seguir estos pasos.

  1. Actualiza la versión del complemento de Android para Gradle en el archivo build.gradle de tu proyecto a 4.0.0 o a una versión posterior.

  2. En el directorio de nivel superior de tu proyecto, crea un directorio para el paquete de elementos. Se usa el nombre de ese directorio como el nombre del paquete de elementos. Los nombres de los paquetes de elementos deben comenzar con una letra y solo pueden contener letras, números y guiones bajos.

  3. En el directorio del paquete de recursos, crea un archivo build.gradle y agrega el siguiente código. Asegúrate de especificar el nombre del paquete de elementos y solo un tipo de entrega:

    Groovy

    // In the asset pack’s build.gradle file:
    plugins {
      id 'com.android.asset-pack'
    }
    
    assetPack {
        packName = "asset-pack-name" // Directory name for the asset pack
        dynamicDelivery {
            deliveryType = "[ install-time | fast-follow | on-demand ]"
        }
    }
    

    Kotlin

    // In the asset pack’s build.gradle.kts file:
    plugins {
      id("com.android.asset-pack")
    }
    
    assetPack {
      packName.set("asset-pack-name") // Directory name for the asset pack
      dynamicDelivery {
        deliveryType.set("[ install-time | fast-follow | on-demand ]")
      }
    }
    
  4. En el archivo build.gradle de la app del proyecto, agrega el nombre de cada paquete de elementos de tu proyecto, como se muestra a continuación:

    Groovy

    // In the app build.gradle file:
    android {
        ...
        assetPacks = [":asset-pack-name", ":asset-pack2-name"]
    }
    

    Kotlin

    // In the app build.gradle.kts file:
    android {
        ...
        assetPacks += listOf(":asset-pack-name", ":asset-pack2-name")
    }
    
  5. En el archivo settings.gradle del proyecto, incluye todos los paquetes de elementos de tu proyecto, como se muestra a continuación:

    Groovy

    // In the settings.gradle file:
    include ':app'
    include ':asset-pack-name'
    include ':asset-pack2-name'
    

    Kotlin

    // In the settings.gradle.kts file:
    include(":app")
    include(":asset-pack-name")
    include(":asset-pack2-name")
    
  6. En el directorio del paquete de elementos, crea el siguiente subdirectorio: src/main/assets.

  7. Coloca los recursos en el directorio src/main/assets. Aquí también puedes crear subdirectorios. Ahora la estructura del directorio de tu app debería verse de la siguiente manera:

    • build.gradle
    • settings.gradle
    • app/
    • asset-pack-name/build.gradle
    • asset-pack-name/src/main/assets/your-asset-directories
  8. Compila el Android App Bundle con Gradle. En el paquete de aplicación que se generó, el directorio raíz ahora incluye lo siguiente:

    • asset-pack-name/manifest/AndroidManifest.xml: configura el identificador y el modo de entrega del paquete de elementos.
    • asset-pack-name/assets/your-asset-directories: es el directorio que contiene todos los elementos entregados como parte del paquete de elementos.

    Gradle genera el manifiesto para cada paquete de elementos y genera el directorio assets/ por ti.

  9. Incluye la Biblioteca de Play Core si planeas usar la entrega de seguimiento rápido y on demand (opcional).

    Groovy

    implementation "com.google.android.play:core:$play_core_version"
    // For Kotlin use core-ktx
    implementation "com.google.android.play:core-ktx:$play_core_version"
    

    Kotlin

    implementation("com.google.android.play:core:$playCoreVersion")
    // For Kotlin use core-ktx
    implementation("com.google.android.play:core-ktx:$playCoreVersion")
    

  10. Opcional: Configura el paquete de aplicación para que sea compatible con diferentes formatos de compresión de texturas.

Realiza la integración con la API de Play Core

La API de Java de Play Core proporciona la clase AssetPackManager para solicitar paquetes de elementos, administrar descargas y acceder a elementos. Primero, asegúrate de agregar la biblioteca de Play Core a tu proyecto.

Esta API se implementa de acuerdo con el tipo de entrega del paquete de elementos al que deseas acceder. Estos pasos se muestran en el siguiente diagrama de flujo.

Diagrama de flujo del paquete de elementos para el lenguaje de programación Java

Figura 1: Diagrama de flujo para acceder a paquetes de elementos

Entrega durante la instalación

Los paquetes de elementos configurados como install-time están disponibles apenas se inicia la app. Usa la API de AssetManager de Java para acceder a los elementos que se entregan en este modo:

Kotlin

import android.content.res.AssetManager
...
val context: Context = createPackageContext("com.example.app", 0)
val assetManager: AssetManager = context.assets
val stream: InputStream = assetManager.open("asset-name")

Java

import android.content.res.AssetManager;
...
Context context = createPackageContext("com.example.app", 0);
AssetManager assetManager = context.getAssets();
InputStream is = assetManager.open("asset-name");

Cómo realizar entregas rápidas y a pedido

En las siguientes secciones, se muestra cómo obtener información sobre los paquetes de elementos antes de descargarlos, cómo llamar a la API para comenzar la descarga y cómo acceder a los paquetes descargados. Estas secciones se aplican a los paquetes de elementos fast-follow y on-demand.

Comprueba el estado

Cada paquete de elementos se almacena en una carpeta independiente del almacenamiento interno de la app. Usa el método getPackLocation() para determinar la carpeta raíz de un paquete de elementos. Este método muestra los siguientes valores:

Valor que se muestra Estado
Un objeto AssetPackLocation válido La carpeta raíz del paquete de elementos está lista para acceder de inmediato a assetsPath()
null Los paquetes de elementos o los elementos desconocidos no están disponibles

Obtén información de descarga sobre paquetes de elementos

Las apps deben divulgar el tamaño de la descarga antes de recuperar el paquete de elementos. Usa el método requestPackStates() o getPackStates() para determinar el tamaño de la descarga y si ya se está descargando el paquete.

Kotlin

suspend fun requestPackStates(packNames: List<String>): AssetPackStates

Java

Task<AssetPackStates> getPackStates(List<String> packNames)

requestPackStates() es una función de suspensión que muestra un objeto AssetPackStates, mientras que getPackStates() es un método asíncrono que muestra un elemento Task<AssetPackStates>. El método packStates() de un objeto AssetPackStates muestra un elemento Map<String, AssetPackState>. Este mapa contiene el estado de cada paquete de elementos solicitado, codificado por su nombre:

Kotlin

AssetPackStates#packStates(): Map<String, AssetPackState>

Java

Map<String, AssetPackState> AssetPackStates#packStates()

La solicitud final se muestra mediante lo siguiente:

Kotlin

const val assetPackName = "assetPackName"
coroutineScope.launch {
  try {
    val assetPackStates: AssetPackStates =
      manager.requestPackStates(listOf(assetPackName))
    val assetPackState: AssetPackState =
      assetPackStates.packStates()[assetPackName]
  } catch (e: RuntimeExecutionException) {
    Log.d("MainActivity", e.message)
  }
}

Java

final String assetPackName = "myasset";

assetPackManager
    .getPackStates(Collections.singletonList(assetPackName))
    .addOnCompleteListener(new OnCompleteListener<AssetPackStates>() {
        @Override
        public void onComplete(Task<AssetPackStates> task) {
            AssetPackStates assetPackStates;
            try {
                assetPackStates = task.getResult();
                AssetPackState assetPackState =
                    assetPackStates.packStates().get(assetPackName);
            } catch (RuntimeExecutionException e) {
                Log.d("MainActivity", e.getMessage());
                return;
            })

Los siguientes métodos AssetPackState proporcionan el tamaño del paquete de elementos, la cantidad descargada hasta el momento (si se solicita) y la cantidad que ya se transfirió a la app:

Para obtener el estado de un paquete de elementos, usa el status() que muestra el estado como un número entero que corresponde a un campo constante en la clase AssetPackStatus. Un paquete de elementos que aún no está instalado aún tiene el estado AssetPackStatus.NOT_INSTALLED.

Si falla una solicitud, usa el método errorCode(), cuyo valor que se muestra corresponde a un campo constante en la clase AssetPackErrorCode.

Instalar

Usa el método requestFetch() o fetch() para descargar un paquete de recursos por primera vez o realiza la llamada a fin de que se complete la actualización de un paquete:

Kotlin

suspend fun AssetPackManager.requestFetch(packs: List<String>): AssetPackStates

Java

Task<AssetPackStates> fetch(List<String> packNames)

Este método muestra un objeto AssetPackStates que contiene una lista de paquetes y sus estados y tamaños de descarga iniciales. Si ya se está descargando un paquete de recursos solicitado a través de requestFetch() o fetch(), se mostrará el estado de descarga y no se iniciará ninguna descarga adicional.

Supervisa los estados de descarga

Debes implementar un objeto AssetPackStateUpdatedListener para realizar un seguimiento del progreso de la instalación de paquetes de recursos. Las actualizaciones de estado se desglosan por paquete para admitir el seguimiento del estado de paquetes de elementos individuales. Puedes comenzar a usar los paquetes de elementos disponibles antes de que se completen todas las demás descargas de la solicitud.

Kotlin

fun registerListener(listener: AssetPackStateUpdatedListener)
fun unregisterListener(listener: AssetPackStateUpdatedListener)

Java

void registerListener(AssetPackStateUpdatedListener listener)
void unregisterListener(AssetPackStateUpdatedListener listener)

Descargas grandes

Si la descarga supera los 150 MB y el usuario no está conectado a una red Wi-Fi, no se iniciará la descarga hasta que el usuario otorgue explícitamente su consentimiento para continuar con la descarga mediante una conexión de datos móviles. Del mismo modo, si la descarga es grande y el usuario pierde la conexión Wi-Fi, esta se detiene y se necesita el consentimiento explícito para continuar usando una conexión de datos móviles. Un paquete detenido tiene el estado WAITING_FOR_WIFI. Para activar el flujo de la IU a fin de solicitar el consentimiento del usuario, usa el método requestCellularDataConfirmation() o showCellularDataConfirmation().

Ten en cuenta que si la app no llama a este método, la descarga se detiene y se reanudará automáticamente cuando el usuario vuelva a una conexión Wi-Fi.

A continuación, se muestra un ejemplo de implementación de un objeto de escucha:

Kotlin

assetPackManager.registerListener { assetPackState ->
  when(assetPackState.status()) {
    AssetPackStatus.PENDING -> {
      Log.i(TAG, "Pending")
    }
    AssetPackStatus.DOWNLOADING -> {
      val downloaded = assetPackState.bytesDownloaded()
      val totalSize = assetPackState.totalBytesToDownload()
      val percent = 100.0 * downloaded / totalSize

      Log.i(TAG, "PercentDone=" + String.format("%.2f", percent))
    }
    AssetPackStatus.TRANSFERRING -> {
      // 100% downloaded and assets are being transferred.
      // Notify user to wait until transfer is complete.
    }
    AssetPackStatus.COMPLETED -> {
      // Asset pack is ready to use. Start the game.
    }
    AssetPackStatus.FAILED -> {
      // Request failed. Notify user.
      Log.e(TAG, assetPackState.errorCode())
    }
    AssetPackStatus.CANCELED -> {
      // Request canceled. Notify user.
    }
    AssetPackStatus.WAITING_FOR_WIFI -> {
      if (!waitForWifiConfirmationShown) {
        coroutineScope.launch {
          val resultCode =
            assetPackManager.requestCellularDataConfirmation(this@MainActivity)
          if (resultCode == RESULT_OK) {
            Log.d(TAG, "Confirmation dialog has been accepted.")
          } else if (resultCode == RESULT_CANCELED) {
            Log.d(TAG, "Confirmation dialog has been denied by the user.")
          }
        }
        waitForWifiConfirmationShown = true
      }
    }
    AssetPackStatus.NOT_INSTALLED -> {
      // Asset pack is not downloaded yet.
    }
    AssetPackStatus.UNKNOWN -> {
      Log.wtf(TAG, "Asset pack status unknown")
    }
  }
}

Java

assetPackStateUpdateListener = new AssetPackStateUpdateListener() {
    @Override
    public void onStateUpdate(AssetPackState assetPackState) {
      switch (assetPackState.status()) {
        case AssetPackStatus.PENDING:
          Log.i(TAG, "Pending");
          break;

        case AssetPackStatus.DOWNLOADING:
          long downloaded = assetPackState.bytesDownloaded();
          long totalSize = assetPackState.totalBytesToDownload();
          double percent = 100.0 * downloaded / totalSize;

          Log.i(TAG, "PercentDone=" + String.format("%.2f", percent));
          break;

        case AssetPackStatus.TRANSFERRING:
          // 100% downloaded and assets are being transferred.
          // Notify user to wait until transfer is complete.
          break;

        case AssetPackStatus.COMPLETED:
          // Asset pack is ready to use. Start the game.
          break;

        case AssetPackStatus.FAILED:
          // Request failed. Notify user.
          Log.e(TAG, assetPackState.errorCode());
          break;

        case AssetPackStatus.CANCELED:
          // Request canceled. Notify user.
          break;

        case AssetPackStatus.WAITING_FOR_WIFI:
          if (!waitForWifiConfirmationShown) {
            assetPackManager.showCellularDataConfirmation(MainActivity.this)
              .addOnSuccessListener(new OnSuccessListener<Integer> () {
                @Override
                public void onSuccess(Integer resultCode) {
                  if (resultCode == RESULT_OK) {
                    Log.d(TAG, "Confirmation dialog has been accepted.");
                  } else if (resultCode == RESULT_CANCELED) {
                    Log.d(TAG, "Confirmation dialog has been denied by the user.");
                  }
                }
              });
            waitForWifiConfirmationShown = true;
          }
          break;

        case AssetPackStatus.NOT_INSTALLED:
          // Asset pack is not downloaded yet.
          break;
        case AssetPackStatus.UNKNOWN:
          Log.wtf(TAG, "Asset pack status unknown")
          break;
      }
    }
}

También puedes usar el método getPackStates() para obtener el estado de las descargas actuales. AssetPackStates contiene el progreso de descarga, el estado de descarga y los códigos de error con fallas.

Accede a los paquetes de elementos

Puedes acceder a un paquete de elementos mediante llamadas al sistema de archivos después de que la solicitud de descarga alcanza el estado COMPLETED. Usa el método getPackLocation() para obtener la carpeta raíz del paquete de elementos.

Los elementos se almacenan en el directorio assets dentro del directorio raíz del paquete de elementos. Puedes obtener la ruta al directorio assets con el método útil assetsPath(). Usa el siguiente método para obtener la ruta a un elemento específico:

Kotlin

private fun getAbsoluteAssetPath(assetPack: String, relativeAssetPath: String): String? {
    val assetPackPath: AssetPackLocation =
      assetPackManager.getPackLocation(assetPack)
      // asset pack is not ready
      ?: return null

    val assetsFolderPath = assetPackPath.assetsPath()
    // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets")
    return FilenameUtils.concat(assetsFolderPath, relativeAssetPath)
}

Java

private String getAbsoluteAssetPath(String assetPack, String relativeAssetPath) {
    AssetPackLocation assetPackPath = assetPackManager.getPackLocation(assetPack);

    if (assetPackPath == null) {
        // asset pack is not ready
        return null;
    }

    String assetsFolderPath = assetPackPath.assetsPath();
    // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets");
    String assetPath = FilenameUtils.concat(assetsFolderPath, relativeAssetPath);
    return assetPath;
}

Otros métodos de la API de Play Core

Estos son algunos métodos de API adicionales que recomendamos que uses en tu app.

Cancelar solicitud

Usa cancel() para cancelar una solicitud activa de paquete de elementos. Ten en cuenta que esta solicitud es una operación de mejor esfuerzo.

Quita un paquete de elementos

Usa el método requestRemovePack() o removePack() para programar la eliminación de un paquete de recursos.

Obtén las ubicaciones de varios paquetes de elementos

Usa getPackLocations() para consultar el estado de varios paquetes de elementos de forma masiva, lo que muestra un mapa de los paquetes de elementos y sus ubicaciones. El mapa que muestra getPackLocations() contiene una entrada para cada paquete que se descarga y está actualizado.

Siguiente paso

Prueba Play Asset Delivery de forma local y desde Google Play.