Tích hợp Asset Delivery (Kotlin và Java)

Hãy làm theo các bước trong hướng dẫn này để truy cập vào gói tài sản của ứng dụng qua mã Java.

Bản dựng cho Kotlin và Java

Hãy làm theo các bước sau đây để tích hợp Play Asset Delivery vào tệp Android App Bundle của dự án. Bạn không cần sử dụng Android Studio để thực hiện các bước này.

  1. Cập nhật phiên bản trình bổ trợ Android cho Gradle trong tệp build.gradle của dự án lên phiên bản 4.0.0 trở lên.

  2. Trong thư mục cấp cao nhất của dự án, tạo thư mục cho gói tài sản. Tên thư mục này được dùng làm tên gói tài sản. Tên gói tài sản phải bắt đầu bằng một chữ cái và chỉ được chứa chữ cái, số và dấu gạch dưới.

  3. Trong thư mục gói tài sản, hãy tạo tệp build.gradle rồi thêm mã sau đây. Hãy nhớ chỉ định tên của gói tài sản và chỉ một loại hình phân phối:

    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. Trong tệp build.gradle của ứng dụng thuộc dự án, hãy thêm tên của từng gói tài sản vào dự án như trình bày dưới đây:

    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. Trong tệp settings.gradle của dự án, hãy đưa tất cả gói tài sản vào dự án như trình bày dưới đây:

    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. Trong thư mục gói tài sản, hãy tạo thư mục con sau đây: src/main/assets.

  7. Đặt tài sản vào thư mục src/main/assets. Ở đây, bạn cũng có thể tạo thư mục con. Lúc này, cấu trúc thư mục cho ứng dụng sẽ có dạng như sau:

    • build.gradle
    • settings.gradle
    • app/
    • asset-pack-name/build.gradle
    • asset-pack-name/src/main/assets/your-asset-directories
  8. Xây dựng Android App Bundle bằng Gradle. Trong gói ứng dụng đã tạo, thư mục cấp cơ sở hiện bao gồm những phần tử sau:

    • asset-pack-name/manifest/AndroidManifest.xml: Định cấu hình chế độ phân phối và giá trị nhận dạng của gói tài sản
    • asset-pack-name/assets/your-asset-directories: Thư mục chứa tất cả các tài sản được phân phối trong gói tài sản

    Gradle sẽ tạo tệp kê khai cho mỗi gói tài sản và xuất ra thư mục assets/ cho bạn.

  9. (Không bắt buộc) Bao gồm Thư viện Play Asset Delivery nếu bạn định sử dụng tính năng phân phối tiếp nối nhanh và theo yêu cầu

    Groovy

    implementation "com.google.android.play:asset-delivery:2.1.0"
    // For Kotlin use asset-delivery-ktx
    implementation "com.google.android.play:asset-delivery-ktx:2.1.0"
    

    Kotlin

    implementation("com.google.android.play:asset-delivery:2.1.0")
    // For Kotlin use core-ktx
    implementation("com.google.android.play:asset-delivery-ktx:2.1.0")
    

  10. (Không bắt buộc) Định cấu hình gói ứng dụng để hỗ trợ nhiều định dạng nén kết cấu.

Tích hợp với API Play Asset Delivery

API Java của Play Asset Delivery cung cấp lớp AssetPackManager để yêu cầu các gói tài sản, quản lý tệp tải xuống và truy cập vào các tài sản. Hãy nhớ Thêm Thư viện Play Asset Delivery vào dự án của bạn trước.

Bạn triển khai API này theo loại hình phân phối của gói tài sản mà bạn muốn truy cập. Các bước này được thể hiện trong sơ đồ quy trình sau đây.

Sơ đồ quy trình gói tài sản cho ngôn ngữ lập trình Java

Hình 1. Sơ đồ quy trình truy cập gói tài sản

Phân phối khi cài đặt

Các gói tài sản được định cấu hình là install-time sẽ có sẵn ngay khi khởi chạy ứng dụng. Hãy sử dụng AssetManager API trong Java để truy cập tài sản được phân phát ở chế độ này:

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");

Phân phối tiếp nối nhanh và theo yêu cầu

Các phần sau đây cho biết cách lấy thông tin về gói tài sản trước khi tải xuống, cách gọi API để bắt đầu tải xuống và cách truy cập gói đã tải xuống. Các phần này áp dụng cho các gói tài sản fast-followon-demand.

Kiểm tra trạng thái

Mỗi gói tài sản được lưu trữ tại một thư mục riêng ở bộ nhớ trong của ứng dụng. Sử dụng phương thức getPackLocation() để xác định thư mục gốc của gói tài sản. Phương thức này trả về các giá trị sau:

Giá trị trả về Trạng thái
Một đối tượng AssetPackLocation hợp lệ Thư mục gốc của gói tài sản đã sẵn sàng để truy cập ngay tại assetsPath()
null Không có gói tài sản hoặc các tài sản không xác định

Xem thông tin tải xuống về gói tài sản

Ứng dụng phải cho biết kích thước của tệp tải xuống trước khi tìm nạp gói tài sản. Hãy dùng phương thức requestPackStates() hoặc getPackStates() để xác định kích thước của tệp tải xuống và liệu gói đã được tải xuống hay chưa.

Kotlin

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

Java

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

requestPackStates() là một hàm tạm ngưng (suspend function) trả về đối tượng AssetPackStates trong khi getPackStates() là một phương thức không đồng bộ trả về Task<AssetPackStates>. Phương thức packStates() của đối tượng AssetPackStates trả về Map<String, AssetPackState>. Bản đồ này chứa trạng thái của mỗi gói tài sản được yêu cầu và được xác định theo tên:

Kotlin

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

Java

Map<String, AssetPackState> AssetPackStates#packStates()

Yêu cầu cuối cùng sẽ có dạng như sau:

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;
            })

Các phương thức AssetPackState sau đây cho biết kích thước của gói tài sản, số gói đã tải xuống từ trước đến nay (nếu được yêu cầu) và số gói đã chuyển vào ứng dụng:

Để biết trạng thái của gói tài sản, hãy sử dụng status(). Phương thức này sẽ trả về trạng thái dưới dạng số nguyên tương ứng với một trường không đổi trong lớp AssetPackStatus. Gói tài sản chưa được cài đặt sẽ có trạng thái AssetPackStatus.NOT_INSTALLED.

Nếu một yêu cầu không thành công, hãy sử dụng phương thức errorCode(). Phương thức này trả về giá trị tương ứng với một trường không đổi trong lớp AssetPackErrorCode.

Cài đặt

Hãy sử dụng phương thức requestFetch() hoặc fetch() để tải gói tài sản xuống trong lần đầu tiên hoặc gọi để hoàn tất quá trình cập nhật một gói tài sản:

Kotlin

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

Java

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

Phương thức này trả về một đối tượng AssetPackStates chứa danh sách gói, kích thước và trạng thái tải xuống ban đầu của các gói đó. Nếu một gói tài sản được yêu cầu qua requestFetch() hoặc fetch() và đang trong quá trình tải xuống, thì hệ thống sẽ trả về trạng thái tải xuống và không bắt đầu thêm quá trình tải xuống nào.

Theo dõi trạng thái tải xuống

Bạn nên triển khai một AssetPackStateUpdatedListener để theo dõi tiến trình cài đặt gói tài sản. Thông tin cập nhật trạng thái cho từng gói được chia nhỏ để hỗ trợ việc theo dõi trạng thái của từng gói tài sản. Bạn có thể bắt đầu sử dụng các gói tài sản hiện có trước khi tất cả tài nguyên tải xuống khác theo yêu cầu của bạn hoàn tất.

Kotlin

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

Java

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

Tài nguyên tải xuống kích thước lớn

Nếu tài nguyên tải xuống lớn hơn 150 MB và người dùng đang không sử dụng Wi-Fi, thì quá trình tải xuống sẽ chỉ bắt đầu khi người dùng thể hiện rõ sự đồng ý rằng họ sẽ tiếp tục tải xuống qua kết nối dữ liệu di động. Tương tự, nếu tài nguyên tải xuống có kích thước lớn và người dùng mất Wi-Fi, thì quá trình tải xuống sẽ tạm dừng và cần có sự đồng ý rõ ràng của người dùng để tiếp tục sử dụng kết nối dữ liệu di động. Gói bị tạm dừng có trạng thái WAITING_FOR_WIFI. Để kích hoạt luồng giao diện người dùng nhắc người dùng đồng ý, hãy sử dụng phương thức requestCellularDataConfirmation() hoặc showCellularDataConfirmation().

Hãy lưu ý rằng nếu ứng dụng không gọi phương thức này, thì quá trình tải xuống sẽ tạm dừng và tự động tiếp tục lại khi người dùng có kết nối Wi-Fi.

Sau đây là ví dụ về cách triển khai trình nghe:

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;
      }
    }
}

Ngoài ra, bạn có thể sử dụng phương thức getPackStates() để biết trạng thái của các tài nguyên đã tải xuống hiện có. AssetPackStates chứa thông tin về tiến trình tải xuống, trạng thái tải xuống và mọi mã lỗi thất bại (failure).

Truy cập gói tài sản

Bạn có thể truy cập gói tài sản bằng cách sử dụng lệnh gọi hệ thống tệp sau khi yêu cầu tải xuống đạt trạng thái COMPLETED. Hãy sử dụng phương thức getPackLocation() để lấy thư mục gốc của gói tài sản.

Các tài sản được lưu trữ trong thư mục assets thuộc thư mục gốc của gói tài sản. Bạn có thể lấy đường dẫn đến thư mục assets bằng cách sử dụng phương thức assetsPath() thuận tiện. Hãy sử dụng phương thức sau đây để lấy đường dẫn đến một tài sản cụ thể:

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;
}

Các cách khác để gửi API Play Asset Delivery

Sau đây là một số phương thức API khác mà có thể bạn muốn sử dụng trong ứng dụng.

Huỷ yêu cầu

Sử dụng cancel() để huỷ yêu cầu về gói tài sản đang hoạt động. Xin lưu ý rằng yêu cầu này là một thao tác được thực hiện trên cơ sở nỗ lực tối đa.

Xoá gói tài sản

Sử dụng requestRemovePack() hoặc removePack() để lên lịch xoá gói tài sản.

Xem thông tin vị trí của nhiều gói tài sản

Sử dụng getPackLocations() để truy vấn trạng thái của nhiều gói tài sản cùng một lúc, các thao tác này sẽ trả về thông tin bản đồ và vị trí của các gói tài sản. Bản đồ do getPackLocations() trả về chứa mục thông tin cho từng gói đang được tải xuống và cập nhật.

Bước tiếp theo

Kiểm thử Play Asset Delivery trên thiết bị và trên Google Play.