שילוב העברת נכסים (Kotlin ו-Java)

כדי לגשת לחבילות הנכסים של האפליקציה מקוד Java, צריך לפעול לפי השלבים במדריך הזה.

פיתוח ל-Kotlin ול-Java

כדי להוסיף את Play Asset Delivery ל-Android App Bundle של הפרויקט: לא צריך להשתמש ב-Android Studio כדי לבצע את השלבים האלה.

  1. מעדכנים את גרסת הפלאגין של Android Gradle בקובץ build.gradle של הפרויקט לגרסה 4.0.0 ואילך.

  2. בספרייה ברמה העליונה של הפרויקט, יוצרים ספרייה לחבילת הנכסים. שם הספרייה הזו משמש כשם של חבילת הנכסים. השמות של חבילות הנכסים חייבים להתחיל באות ויכולים להכיל רק אותיות, מספרים וקווים תחתונים.

  3. בספרייה של חבילת הנכסים, יוצרים קובץ build.gradle ומוסיפים את הקוד הבא. חשוב לציין את השם של חבילת הנכסים ורק סוג אחד של העברה:

    מגניב

    // 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. בקובץ build.gradle של האפליקציה בפרויקט, מוסיפים את השם של כל חבילת נכסים בפרויקט, כמו שמוצג בהמשך:

    מגניב

    // 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. בקובץ settings.gradle של הפרויקט, כוללים את כל חבילות הנכסים בפרויקט, כמו שמוצג בהמשך:

    מגניב

    // 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. בספרייה של חבילת הנכסים, יוצרים את ספריית המשנה הבאה: src/main/assets.

  7. ממקמים את הנכסים בספרייה src/main/assets. אפשר גם ליצור כאן תיקיות משנה. מבנה הספרייה של האפליקציה אמור להיראות כך:

    • build.gradle
    • settings.gradle
    • app/
    • asset-pack-name/build.gradle
    • asset-pack-name/src/main/assets/your-asset-directories
  8. יוצרים את קובץ ה-Android App Bundle באמצעות Gradle. ב-App Bundle שנוצר, ספריית הרמה הבסיסית כוללת עכשיו את הפריטים הבאים:

    • asset-pack-name/manifest/AndroidManifest.xml: מגדיר את המזהה ואת מצב ההעברה של חבילת הנכסים
    • asset-pack-name/assets/your-asset-directories: הספרייה שמכילה את כל הנכסים שנמסרו כחלק מחבילת הנכסים

    ‫Gradle יוצר את המניפסט לכל חבילת נכסים ומוציא את הספרייה assets/.

  9. (אופציונלי) כוללים את ספריית Play Asset Delivery אם מתכננים להשתמש בהעברה מסוג Fast-Follow ובהעברה על פי דרישה

    מגניב

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

    Kotlin

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

  10. (אופציונלי) מגדירים את ה-App Bundle כך שיתמוך בפורמטים שונים של דחיסת טקסטורה.

שילוב עם Play Asset Delivery API

Play Asset Delivery Java API מספק את המחלקה AssetPackManager כדי לבקש חבילות נכסים, לנהל הורדות ולגשת לנכסים. קודם כל, צריך לוודא שהוספתם את ספריית Play Asset Delivery לפרויקט.

אתם מטמיעים את ה-API הזה בהתאם לסוג ההעברה של חבילת הנכסים שאתם רוצים לגשת אליה. השלבים האלה מוצגים בתרשים הזרימה הבא.

תרשים זרימה של חבילת נכסים בשפת התכנות Java

איור 1. תרשים זרימה לגישה לחבילות נכסים

העברה בזמן ההתקנה

חבילות נכסים שמוגדרות כ-install-time זמינות מיד כשהאפליקציה מופעלת. כדי לגשת לנכסים שמוצגים במצב הזה, משתמשים ב-AssetManager API של Java:

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

מסירה מסוג Fast-Follow ועל פי דרישה

בקטעים הבאים מוסבר איך לקבל מידע על חבילות נכסים לפני שמורידים אותן, איך להפעיל את ה-API כדי להתחיל את ההורדה ואיך לגשת לחבילות שהורדו. הקטעים האלה רלוונטיים לחבילות נכסים מסוג fast-follow ו-on-demand.

בדיקת הסטטוס

כל חבילת נכסים מאוחסנת בתיקייה נפרדת באחסון הפנימי של האפליקציה. משתמשים ב-method‏ getPackLocation() כדי לקבוע את תיקיית הבסיס של חבילת הנכסים. השיטה הזו מחזירה את הערכים הבאים:

הערך המוחזר סטטוס
אובייקט AssetPackLocation תקין תיקיית הבסיס של חבילת הנכסים מוכנה לגישה מיידית בכתובת assetsPath()
null חבילת הנכסים לא ידועה או שהנכסים לא זמינים

קבלת מידע על הורדה של חבילות נכסים

האפליקציות נדרשות לחשוף את גודל ההורדה לפני שהן מאחזרות את חבילת הנכסים. כדי לבדוק את גודל ההורדה ואם החבילה כבר יורדת, משתמשים ב-requestPackStates() או ב-getPackStates().

Kotlin

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

Java

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

‫requestPackStates() היא פונקציית השהיה שמחזירה אובייקט AssetPackStates, ו-getPackStates() היא שיטה אסינכרונית שמחזירה Task<AssetPackStates>. ה-method‏ packStates() של אובייקט AssetPackStates מחזירה Map<String, AssetPackState>. המפה הזו מכילה את המצב של כל חבילת נכסים שהתבקשה, עם מפתח לפי השם שלה:

Kotlin

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

Java

Map<String, AssetPackState> AssetPackStates#packStates()

הבקשה הסופית מוצגת כך:

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

השיטות הבאות של AssetPackState מספקות את הגודל של חבילת הנכסים, את הכמות שהורדה עד עכשיו (אם התבקשה) ואת הכמות שכבר הועברה לאפליקציה:

כדי לקבל את הסטטוס של חבילת נכסים, משתמשים ב-method‏ status(), שמחזירה את הסטטוס כמספר שלם שתואם לשדה קבוע במחלקה AssetPackStatus. חבילת נכסים שעדיין לא הותקנה מסומנת בסטטוס AssetPackStatus.NOT_INSTALLED.

אם הבקשה נכשלת, משתמשים ב-method‏ errorCode(), שהערך המוחזר שלה תואם לשדה קבוע במחלקה AssetPackErrorCode.

התקנה

כדי להוריד חבילת נכסים בפעם הראשונה או כדי להשלים את העדכון של חבילת נכסים, משתמשים ב-requestFetch() או ב-fetch():

Kotlin

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

Java

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

השיטה הזו מחזירה אובייקט AssetPackStates שמכיל רשימה של חבילות, מצבי ההורדה הראשוניים שלהן והגדלים שלהן. אם חבילת נכסים שביקשתם באמצעות requestFetch() או fetch() כבר נמצאת בתהליך הורדה, סטטוס ההורדה מוחזר ולא מתחילה הורדה נוספת.

מעקב אחרי מצבי ההורדה

כדאי להטמיע AssetPackStateUpdatedListener כדי לעקוב אחרי התקדמות ההתקנה של חבילות הנכסים. עדכוני הסטטוס מחולקים לפי חבילה כדי לתמוך במעקב אחרי הסטטוס של חבילות נכסים ספציפיות. אתם יכולים להתחיל להשתמש בחבילות הנכסים הזמינות לפני שכל שאר ההורדות של הבקשה שלכם הושלמו.

Kotlin

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

Java

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

הורדות גדולות

אם גודל ההורדה הוא יותר מ-200MB והמשתמש לא מחובר ל-Wi-Fi, ההורדה לא תתחיל עד שהמשתמש יאשר במפורש להמשיך בהורדה באמצעות חיבור לחבילת גלישה. באופן דומה, אם ההורדה גדולה והמשתמש מאבד את החיבור ל-Wi-Fi, ההורדה מושהית ונדרשת הסכמה מפורשת כדי להמשיך באמצעות חיבור לחבילת גלישה. חבילה מושהית נמצאת במצב WAITING_FOR_WIFI. כדי להפעיל את תהליך ממשק המשתמש שבו מוצגת למשתמש בקשה להסכמה, משתמשים ב-method‏ showConfirmationDialog().

חשוב לדעת: אם האפליקציה לא קוראת ל-method הזה, ההורדה מושהית ותתחדש באופן אוטומטי רק כשהמשתמש יתחבר שוב לחיבור Wi-Fi.

נדרש אישור מהמשתמש

אם לחבילה יש את הסטטוס REQUIRES_USER_CONFIRMATION, ההורדה לא תמשיך עד שהמשתמש יאשר את תיבת הדו-שיח שמוצגת עם showConfirmationDialog(). הסטטוס הזה יכול להופיע כשהאפליקציה לא מזוהה על ידי Play, למשל אם האפליקציה הותקנה ממקור לא מוכר. שימו לב שאם תפעילו את showConfirmationDialog() במקרה הזה, האפליקציה תתעדכן. אחרי העדכון, תצטרכו לבקש את הנכסים שוב.

הדוגמה הבאה היא הטמעה של מאזין:

Kotlin

private val activityResultLauncher = registerForActivityResult(
    ActivityResultContracts.StartIntentSenderForResult()
) { result ->
    if (result.resultCode == RESULT_OK) {
        Log.d(TAG, "Confirmation dialog has been accepted.")
    } else if (result.resultCode == RESULT_CANCELED) {
        Log.d(TAG, "Confirmation dialog has been denied by the user.")
    }
}

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,
    AssetPackStatus.REQUIRES_USER_CONFIRMATION -> {
      if (!confirmationDialogShown) {
        assetPackManager.showConfirmationDialog(activityResultLauncher);
        confirmationDialogShown = true
      }
    }
    AssetPackStatus.NOT_INSTALLED -> {
      // Asset pack is not downloaded yet.
    }
    AssetPackStatus.UNKNOWN -> {
      Log.wtf(TAG, "Asset pack status unknown")
    }
  }
}

Java

assetPackStateUpdateListener = new AssetPackStateUpdateListener() {
    private final ActivityResultLauncher<IntentSenderRequest> activityResultLauncher =
      registerForActivityResult(
          new ActivityResultContracts.StartIntentSenderForResult(),
          new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
              if (result.getResultCode() == RESULT_OK) {
                Log.d(TAG, "Confirmation dialog has been accepted.");
              } else if (result.getResultCode() == RESULT_CANCELED) {
                Log.d(TAG, "Confirmation dialog has been denied by the user.");
              }
            }
          });

    @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:
        case AssetPackStatus.REQUIRES_USER_CONFIRMATION:
          if (!confirmationDialogShown) {
            assetPackManager.showConfirmationDialog(activityResultLauncher);
            confirmationDialogShown = true;
          }
          break;

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

לחלופין, אפשר להשתמש ב-method‏ getPackStates() כדי לקבל את הסטטוס של ההורדות הנוכחיות. AssetPackStates מכיל את התקדמות ההורדה, סטטוס ההורדה וקודי שגיאה של כשלים.

גישה לחבילות נכסים

אפשר לגשת לחבילת נכסים באמצעות קריאות למערכת הקבצים אחרי שבקשת ההורדה מגיעה למצב COMPLETED. משתמשים ב-method‏ getPackLocation() כדי לקבל את תיקיית הבסיס של חבילת הנכסים.

הנכסים מאוחסנים בספרייה assets בתוך ספריית הבסיס של חבילת הנכסים. כדי לקבל את הנתיב לספרייה assets, אפשר להשתמש ב-method הנוחה assetsPath(). כדי לקבל את הנתיב לנכס ספציפי, משתמשים בשיטה הבאה:

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

שיטות אחרות של Play Asset Delivery API

בהמשך מפורטות כמה שיטות API נוספות שאולי תרצו להשתמש בהן באפליקציה.

ביטול הבקשה

כדי לבטל בקשה פעילה של חבילת נכסים, משתמשים ב-cancel(). חשוב לדעת שהבקשה הזו היא פעולה שמתבצעת כמיטב היכולת.

הסרה של חבילת נכסים

משתמשים ב-requestRemovePack() או ב-removePack() כדי לתזמן את ההסרה של חבילת נכסים.

קבלת המיקומים של כמה חבילות נכסים

משתמשים ב-getPackLocations() כדי לשלוח שאילתה על הסטטוס של כמה חבילות נכסים בבת אחת. הפעולה הזו מחזירה מפה של חבילות הנכסים והמיקומים שלהן. המפה שמוחזרת על ידי getPackLocations() מכילה רשומה לכל חבילה שהורדה ועדכנית.

השלב הבא

בודקים את Play Asset Delivery באופן מקומי ומ-Google Play.