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

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

פיתוח גרסאות build ל-Kotlin ול-Java

כדי להטמיע את Play Asset Delivery בחבילת האפליקציות ל-Android של הפרויקט, פועלים לפי השלבים הבאים. אין צורך להשתמש ב-Android Studio כדי לבצע את השלבים האלה.

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

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

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

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

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

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

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

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

  9. (אופציונלי) אם אתם מתכננים להשתמש בהעברה מסוג fast-follow ובהעברה על פי דרישה, צריך לכלול את ספריית העברת הנכסים של Play.

    Groovy

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

    Kotlin

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

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

שילוב עם 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 חבילה לא ידועה של נכסים או נכסים לא זמינים

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

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

Kotlin

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

Java

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

requestPackStates() היא פונקציית השהיה שמחזירה אובייקט AssetPackStates, ו-getPackStates() היא שיטה אסינכררונית שמחזירה Task<AssetPackStates>. השיטה 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.

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

התקנה

משתמשים ב-method‏ requestFetch() או ב-method‏ 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().

חשוב לזכור שאם האפליקציה לא קוראת לשיטה הזו, ההורדה מושהית ותתחדש באופן אוטומטי רק כשהמשתמש יחזור להתחבר לרשת 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. משתמשים בשיטה 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.