یکپارچه سازی تحویل دارایی (کاتلین و جاوا)

از مراحل این راهنما برای دسترسی به بسته‌های دارایی برنامه خود از طریق کد جاوا استفاده کنید.

ساخت برای کاتلین و جاوا

برای ساخت Play Asset Delivery در Android App Bundle پروژه خود، از مراحل زیر استفاده کنید. برای انجام این مراحل نیازی به استفاده از اندروید استودیو ندارید.

  1. نسخه افزونه Android Gradle را در فایل build.gradle پروژه خود به 4.0.0 یا بالاتر ارتقا دهید.

  2. در دایرکتوری سطح بالای پروژه خود، یک دایرکتوری برای بسته دارایی ایجاد کنید. نام این دایرکتوری به عنوان نام بسته دارایی استفاده می‌شود. نام بسته‌های دارایی باید با یک حرف شروع شود و فقط می‌تواند شامل حروف، اعداد و زیرخط باشد.

  3. در پوشه‌ی asset pack، یک فایل build.gradle ایجاد کنید و کد زیر را به آن اضافه کنید. حتماً نام asset pack و فقط یک نوع تحویل را مشخص کنید:

    گرووی

    // 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 ]"
        }
    }

    کاتلین

    // 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 برنامه پروژه، نام هر بسته دارایی (assets pack) را مطابق شکل زیر اضافه کنید:

    گرووی

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

    کاتلین

    // In the app build.gradle.kts file:
    android {
        ...
        assetPacks += listOf(":asset-pack-name", ":asset-pack2-name")
    }
  5. در فایل settings.gradle پروژه، تمام بسته‌های دارایی (assets pack) را مطابق شکل زیر در پروژه خود وارد کنید:

    گرووی

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

    کاتلین

    // In the settings.gradle.kts file:
    include(":app")
    include(":asset-pack-name")
    include(":asset-pack2-name")
  6. در دایرکتوری بسته‌ی دارایی‌ها، زیردایرکتوری زیر را ایجاد کنید: src/main/assets .

  7. فایل assets را در دایرکتوری src/main/assets قرار دهید. می‌توانید زیرشاخه‌ها را نیز در اینجا ایجاد کنید. ساختار دایرکتوری برنامه شما اکنون باید به شکل زیر باشد:

    • build.gradle
    • settings.gradle
    • app/
    • asset-pack-name /build.gradle
    • asset-pack-name /src/main/assets/ your-asset-directories
  8. ساخت بسته نرم‌افزاری اندروید با Gradle . در بسته نرم‌افزاری تولید شده، دایرکتوری سطح ریشه اکنون شامل موارد زیر است:

    • asset-pack-name /manifest/AndroidManifest.xml : شناسه و نحوه‌ی تحویل بسته‌ی دارایی را پیکربندی می‌کند.
    • asset-pack-name /assets/ your-asset-directories : دایرکتوری که شامل تمام دارایی‌های ارائه شده به عنوان بخشی از بسته دارایی است.

    Gradle برای هر بسته دارایی، فایل manifest را تولید می‌کند و دایرکتوری assets/ را برای شما خروجی می‌دهد.

  9. (اختیاری) اگر قصد دارید از تحویل سریع و بر اساس تقاضا استفاده کنید ، کتابخانه تحویل دارایی‌های بازی را نیز اضافه کنید

    گرووی

    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"

    کاتلین

    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. (اختیاری) بسته برنامه خود را برای پشتیبانی از قالب‌های مختلف فشرده‌سازی بافت پیکربندی کنید.

با API تحویل دارایی‌های بازی ادغام شوید

API جاوا برای تحویل دارایی‌های Play، کلاس AssetPackManager را برای درخواست بسته‌های دارایی، مدیریت دانلودها و دسترسی به دارایی‌ها ارائه می‌دهد. ابتدا مطمئن شوید که کتابخانه تحویل دارایی‌های Play را به پروژه خود اضافه کرده‌اید .

شما این API را بر اساس نوع تحویل بسته‌ی دارایی که می‌خواهید به آن دسترسی داشته باشید، پیاده‌سازی می‌کنید. این مراحل در فلوچارت زیر نشان داده شده است.

نمودار جریان بسته دارایی برای زبان برنامه‌نویسی جاوا

شکل ۱. نمودار جریان دسترسی به بسته‌های دارایی

تحویل زمان نصب

بسته‌های دارایی پیکربندی‌شده به صورت install-time بلافاصله در هنگام راه‌اندازی برنامه در دسترس هستند. برای دسترسی به دارایی‌های ارائه شده در این حالت، از API Java AssetManager استفاده کنید:

کاتلین

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

جاوا

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

تحویل سریع و بر اساس تقاضا

بخش‌های بعدی نحوه دریافت اطلاعات در مورد بسته‌های دارایی قبل از دانلود آنها، نحوه فراخوانی API برای شروع دانلود و سپس نحوه دسترسی به بسته‌های دانلود شده را نشان می‌دهد. این بخش‌ها برای بسته‌های دارایی fast-follow و on-demand اعمال می‌شوند.

بررسی وضعیت

هر بسته‌ی دارایی در یک پوشه‌ی جداگانه در حافظه‌ی داخلی برنامه ذخیره می‌شود. از متد getPackLocation() برای تعیین پوشه‌ی ریشه‌ی یک بسته‌ی دارایی استفاده کنید. این متد مقادیر زیر را برمی‌گرداند:

مقدار بازگشتی وضعیت
یک شیء AssetPackLocation معتبر پوشه ریشه بسته دارایی برای دسترسی فوری در assetsPath() آماده است.
null بسته یا دارایی‌های ناشناخته در دسترس نیستند

اطلاعات دانلود در مورد بسته‌های دارایی را دریافت کنید

برنامه‌ها موظفند قبل از دریافت بسته‌ی دارایی، حجم دانلود را فاش کنند. از متدهای requestPackStates() یا getPackStates() برای تعیین حجم دانلود و اینکه آیا بسته در حال دانلود است یا خیر، استفاده کنید.

کاتلین

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

جاوا

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

requestPackStates() یک تابع suspend است که یک شیء AssetPackStates را برمی‌گرداند در حالی که getPackStates() یک متد غیرهمزمان است که یک Task<AssetPackStates> برمی‌گرداند. متد packStates() از یک شیء AssetPackStates یک Map<String, AssetPackState> برمی‌گرداند. این map شامل وضعیت هر بسته دارایی درخواستی است که با نام آن مشخص شده است:

کاتلین

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

جاوا

Map<String, AssetPackState> AssetPackStates#packStates()

درخواست نهایی با موارد زیر نشان داده شده است:

کاتلین

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

جاوا

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 زیر، اندازه بسته دارایی، مقدار دانلود شده تاکنون (در صورت درخواست) و مقدار منتقل شده به برنامه را ارائه می‌دهند:

برای دریافت وضعیت یک بسته‌ی دارایی، از متد status() استفاده کنید که وضعیت را به عنوان یک عدد صحیح که مربوط به یک فیلد ثابت در کلاس AssetPackStatus است، برمی‌گرداند. یک بسته‌ی دارایی که هنوز نصب نشده است، وضعیت AssetPackStatus.NOT_INSTALLED را دارد.

اگر درخواستی با شکست مواجه شود، از متد errorCode() استفاده کنید که مقدار بازگشتی آن مربوط به یک فیلد ثابت در کلاس AssetPackErrorCode است.

نصب

برای دانلود یک بسته‌ی دارایی برای اولین بار یا فراخوانی به‌روزرسانی یک بسته‌ی دارایی، از متد requestFetch() یا fetch() استفاده کنید:

کاتلین

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

جاوا

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

این متد یک شیء AssetPackStates را برمی‌گرداند که حاوی لیستی از بسته‌ها و وضعیت و اندازه‌های اولیه دانلود آنها است. اگر یک بسته دارایی که از طریق requestFetch() یا fetch() درخواست شده است، در حال دانلود باشد، وضعیت دانلود برگردانده می‌شود و دانلود دیگری شروع نمی‌شود.

نظارت بر وضعیت دانلودها

شما باید یک AssetPackStateUpdatedListener پیاده‌سازی کنید تا پیشرفت نصب بسته‌های دارایی را پیگیری کنید. به‌روزرسانی‌های وضعیت به ازای هر بسته تقسیم می‌شوند تا از پیگیری وضعیت بسته‌های دارایی منفرد پشتیبانی کنند. می‌توانید قبل از اینکه تمام دانلودهای دیگر برای درخواست شما تکمیل شود، استفاده از بسته‌های دارایی موجود را شروع کنید.

کاتلین

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

جاوا

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

دانلودهای بزرگ

اگر حجم دانلود بیش از ۲۰۰ مگابایت باشد و کاربر به وای‌فای متصل نباشد، دانلود تا زمانی که کاربر صراحتاً رضایت خود را برای ادامه دانلود با استفاده از اتصال داده تلفن همراه اعلام نکند، شروع نمی‌شود. به طور مشابه، اگر حجم دانلود زیاد باشد و کاربر وای‌فای خود را از دست بدهد، دانلود متوقف می‌شود و برای ادامه دانلود با استفاده از اتصال داده تلفن همراه، رضایت صریح لازم است. یک بسته متوقف شده دارای وضعیت WAITING_FOR_WIFI است. برای فعال کردن جریان رابط کاربری و درخواست رضایت از کاربر، از متد showConfirmationDialog() استفاده کنید.

توجه داشته باشید که اگر برنامه این متد را فراخوانی نکند، دانلود متوقف می‌شود و تنها زمانی که کاربر دوباره به اتصال Wi-Fi متصل شود، به طور خودکار از سر گرفته می‌شود.

تایید کاربر الزامی

اگر یک بسته وضعیت REQUIRES_USER_CONFIRMATION داشته باشد، دانلود ادامه پیدا نمی‌کند تا زمانی که کاربر کادر محاوره‌ای نمایش داده شده با showConfirmationDialog() را بپذیرد. این وضعیت می‌تواند زمانی رخ دهد که برنامه توسط Play شناسایی نشده باشد - برای مثال، اگر برنامه از طریق side-loaded بارگذاری شده باشد. توجه داشته باشید که فراخوانی showConfirmationDialog() در این حالت باعث به‌روزرسانی برنامه می‌شود. پس از به‌روزرسانی، باید دوباره فایل‌ها را درخواست کنید.

در زیر نمونه‌ای از پیاده‌سازی یک شنونده (listener) آمده است:

کاتلین

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

جاوا

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

به عنوان یک روش جایگزین، می‌توانید از متد getPackStates() برای دریافت وضعیت دانلودهای فعلی استفاده کنید. AssetPackStates شامل پیشرفت دانلود، وضعیت دانلود و هرگونه کد خطای خرابی است.

دسترسی به بسته‌های دارایی

شما می‌توانید پس از اینکه درخواست دانلود به حالت COMPLETED رسید، با استفاده از فراخوانی‌های سیستم فایل به یک بسته‌ی دارایی دسترسی پیدا کنید. از متد getPackLocation() برای دریافت پوشه‌ی ریشه‌ی بسته‌ی دارایی استفاده کنید.

دارایی‌ها در دایرکتوری assets درون دایرکتوری ریشه بسته دارایی ذخیره می‌شوند. می‌توانید با استفاده از متد assetsPath() مسیر دایرکتوری assets را دریافت کنید. برای دریافت مسیر یک دارایی خاص از روش زیر استفاده کنید:

کاتلین

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

جاوا

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

سایر روش‌های API تحویل دارایی‌های بازی

در ادامه چند متد API اضافی که ممکن است بخواهید در برنامه خود استفاده کنید، آورده شده است.

لغو درخواست

برای لغو درخواست بسته دارایی فعال، cancel() استفاده کنید. توجه داشته باشید که این درخواست یک عملیات با بهترین تلاش است.

حذف یک بسته دارایی

برای زمان‌بندی حذف یک بسته‌ی دارایی، از requestRemovePack() یا removePack() استفاده کنید.

مکان چندین بسته دارایی را دریافت کنید

از getPackLocations() برای پرس و جوی وضعیت چندین بسته دارایی به صورت دسته جمعی استفاده کنید، که نقشه‌ای از بسته‌های دارایی و مکان‌های آنها را برمی‌گرداند. نقشه‌ای که توسط getPackLocations() برگردانده می‌شود شامل یک ورودی برای هر بسته‌ای است که در حال حاضر دانلود شده و به‌روز است.

مرحله بعدی

تحویل دارایی‌های بازی را به صورت محلی و از Google Play آزمایش کنید .