دعم التحديثات داخل التطبيق (Kotlin أو Java)

يوضِّح هذا الدليل طريقة دعم التحديثات داخل التطبيق في تطبيقك باستخدام لغة Kotlin أو Java. تتوفّر أدلة منفصلة للحالات التي تستخدم فيها عملية التنفيذ الرموز البرمجية الأصلية (C/C++ ) والحالات التي يستخدم فيها التنفيذ Unity.

إعداد بيئة التطوير

مكتبة التحديثات داخل التطبيق في Play هي جزء من مكتبات Google Play الأساسية. يُرجى تضمين البيانات الاعتمادية التالية لنظام Gradle لدمج مكتبة تحديثات Play In-App App.

رائع

// In your app’s build.gradle file:
...
dependencies {
    // This dependency is downloaded from the Google’s Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation 'com.google.android.play:app-update:2.1.0'

    // For Kotlin users also add the Kotlin extensions library for Play In-App Update:
    implementation 'com.google.android.play:app-update-ktx:2.1.0'
    ...
}

Kotlin

// In your app’s build.gradle.kts file:
...
dependencies {
    // This dependency is downloaded from the Google’s Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation("com.google.android.play:app-update:2.1.0")

    // For Kotlin users also import the Kotlin extensions library for Play In-App Update:
    implementation("com.google.android.play:app-update-ktx:2.1.0")
    ...
}

التحقّق من توفّر تحديث

قبل طلب تحديث، تحقَّق من توفُّر تحديث لتطبيقك. استخدِم AppUpdateManager للتحقّق من توفُّر تحديث:

Kotlin

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
        // This example applies an immediate update. To apply a flexible update
        // instead, pass in AppUpdateType.FLEXIBLE
        && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
    ) {
        // Request the update.
    }
}

Java

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          // This example applies an immediate update. To apply a flexible update
          // instead, pass in AppUpdateType.FLEXIBLE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request the update.
    }
});

يحتوي المثيل AppUpdateInfo الذي يتم عرضه على حالة مدى توفّر التحديث. واعتمادًا على حالة التحديث، يحتوي المثيل أيضًا على ما يلي:

  • إذا كان التحديث متاحًا ويُسمح بالتحديث، فإن المثيل يحتوي أيضًا على نية لبدء التحديث.
  • إذا كان هناك تحديث داخل التطبيق قيد التقدم، ستبلغ المثيل أيضًا عن حالة التحديث قيد التقدم.

التحقّق من أنّ تحديث التحديث قديم

بالإضافة إلى التحقق مما إذا كان هناك تحديث متاح أم لا، يمكنك أيضًا التحقق من مقدار الوقت الذي انقضى منذ آخر مرة تم إعلام المستخدم فيها بالتحديث عبر "متجر Play". ويمكن أن يساعدك ذلك في تحديد ما إذا كان عليك بدء تحديث مرن أو تحديث فوري. على سبيل المثال، يمكنك الانتظار بضعة أيام قبل إبلاغ المستخدم بتحديث مرن، وبعد بضعة أيام من ذلك قبل طلب إجراء تحديث فوري.

استخدِم clientVersionStalenessDays() للتحقُّق من عدد الأيام التي مرت منذ توفُّر التحديث على "متجر Play":

Kotlin

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks whether the platform allows the specified type of update,
// and current version staleness.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && (appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
    }
}

Java

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks whether the platform allows the specified type of update,
// and current version staleness.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.clientVersionStalenessDays() != null
          && appUpdateInfo.clientVersionStalenessDays() >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
    }
});

التحقّق من أولوية التحديث

تتيح لك واجهة برمجة التطبيقات Google Play Developer API ضبط أولوية كل تحديث. ويتيح ذلك لتطبيقك تحديد مدى اقتراح تحديث للمستخدم. على سبيل المثال، يمكنك اتّباع الاستراتيجية التالية لضبط أولوية التحديث:

  • تحسينات طفيفة في واجهة المستخدم: التحديث ذو الأولوية المنخفضة، أي عدم طلب تحديث مرن أو فوري. لا تستخدم التحديث إلا في حال عدم تفاعل المستخدم مع تطبيقك.
  • تحسينات الأداء: تحديث أولوية متوسطة، طلب تحديث مرن.
  • تحديث مهم بشأن الأمان: تحديث ذو أولوية عالية، لذلك اطلب تحديثًا فوريًا.

لتحديد الأولوية، يستخدم Google Play قيمة عدد صحيح تتراوح بين 0 و5، ويكون الرقم 0 هو القيمة التلقائية و5 يشير إلى الأولوية القصوى. لضبط أولوية أحد التحديثات، استخدِم الحقل inAppUpdatePriority ضمن Edits.tracks.releases في واجهة برمجة التطبيقات Google Play Developer API. تُعد جميع الإصدارات المضافة حديثًا في الإصدار نفس أولوية الإصدار. لا يمكن ضبط الأولوية إلا عند طرح إصدار جديد ولا يمكن تغييرها لاحقًا.

حدِّد الأولوية باستخدام Google Play Developer API كما هو موضّح في مستندات Play Developer API. يجب تحديد أولوية التحديث داخل التطبيق في المرجع Edit.tracks الذي تم تمريره في طريقة Edit.tracks: update. يوضّح المثال التالي إطلاق تطبيق برمز الإصدار 88 وinAppUpdatePriority 5:

{
  "releases": [{
      "versionCodes": ["88"],
      "inAppUpdatePriority": 5,
      "status": "completed"
  }]
}

في رمز تطبيقك، يمكنك التحقّق من مستوى الأولوية لتحديث معيّن باستخدام updatePriority(). وتراعي الأولوية التي يتم إرجاعها القيمة inAppUpdatePriority لجميع رموز إصدارات التطبيق بين الإصدار المثبَّت وأحدث إصدار متاح، بغض النظر عن مسار الإصدار. على سبيل المثال، ضع في اعتبارك السيناريو التالي:

  • طرح الإصدار 1 في مسار إنتاج بدون أولوية
  • تطلق الإصدار 2 في مسار اختبار داخلي مع تحديد الأولوية 5.
  • طرح الإصدار 3 في مسار إنتاج بدون أولوية

عند تحديث مستخدمي مرحلة الإنتاج من الإصدار 1 إلى الإصدار 3، سيحصلون على الأولوية 5، على الرغم من نشر الإصدار 2 على مسار مختلف.

Kotlin

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks whether the platform allows the specified type of update,
// and checks the update priority.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.updatePriority() >= 4 /* high priority */
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request an immediate update.
    }
}

Java

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks whether the platform allows the specified type of update,
// and checks the update priority.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.updatePriority() >= 4 /* high priority */
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request an immediate update.
    }
});

بدء تحديث

بعد التأكّد من توفّر تحديث، يمكنك طلب التحديث باستخدام AppUpdateManager.startUpdateFlowForResult():

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build())

Java

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());

يمكن استخدام كل مثيل AppUpdateInfo لبدء عملية تحديث مرة واحدة فقط. لإعادة محاولة التحديث في حال تعذُّر ذلك، اطلب AppUpdateInfo جديد وتحقَّق مرة أخرى من أنّ التحديث متاح ومسموح به.

يمكنك تسجيل مشغّل نتائج النشاط باستخدام العقد ActivityResultContracts.StartIntentSenderForResult المدمَج. راجِع القسم الخاص بتلقّي معاودة الاتصال لمعرفة حالة التحديث.

تعتمد الخطوات التالية على ما إذا كنت تطلب تحديثًا مرنًا أو تحديثًا فوريًا.

ضبط تحديث باستخدام AppUpdateOptions

AppUpdateOptions تحتوي على حقل AllowAssetPackDeletion الذي يحدد ما إذا كان التحديث مسموحًا به بمحو حِزم مواد العرض في حال توفّر مساحة تخزين محدودة على الجهاز. يتم ضبط هذا الحقل على false تلقائيًا، ولكن يمكنك استخدام الطريقة setAllowAssetPackDeletion() لضبطه على true بدلاً من ذلك:

Kotlin

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build())

Java

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build());

تلقّي اتصال لمعرفة حالة التحديث

بعد بدء التحديث، تحصل معاودة الاتصال بمشغّل نتائج النشاط المسجَّل على نتيجة مربّع حوار التأكيد:

Kotlin

registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult ->
    // handle callback
    if (result.resultCode != RESULT_OK) {
        log("Update flow failed! Result code: " + result.resultCode);
        // If the update is canceled or fails,
        // you can request to start the update again.
    }
}

Java

registerForActivityResult(
    new ActivityResultContracts.StartIntentSenderForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // handle callback
            if (result.getResultCode() != RESULT_OK) {
                log("Update flow failed! Result code: " + result.getResultCode());
                // If the update is canceled or fails,
                // you can request to start the update again.
            }
        }
    });

هناك عدة قيم قد تتلقّاها من معاودة الاتصال في "onActivityResult()":

  • RESULT_OK: وافق المستخدم على التعديل. للحصول على تحديثات فورية، قد لا تتلقّى معاودة الاتصال هذه لأنّه يجب أن يكون التحديث قد انتهى فعلاً بحلول وقت إعادة ضبط الوقت إلى تطبيقك.
  • RESULT_CANCELED: رفض المستخدم التعديل أو ألغاه.
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED: حدث خطأ آخر يمنع المستخدم من تقديم الموافقة أو مواصلة عملية التحديث.

التعامل مع التحديث المرن

عند بدء تحديث مرن، يظهر مربع حوار للمستخدم أولاً لطلب الموافقة. في حال وافق المستخدم، ستبدأ عملية التنزيل في الخلفية، وسيستمر المستخدم في التفاعل مع تطبيقك. يصف هذا القسم كيفية مراقبة التحديث المرن داخل التطبيق وإكماله.

مراقبة حالة التحديث المرن

بعد بدء عملية تنزيل التحديث للحصول على تحديث مرن، يحتاج التطبيق إلى تتبُّع حالة التحديث لمعرفة متى يمكن تثبيته وعرض مستوى التقدّم في واجهة المستخدم.

يمكنك تتبُّع حالة تحديث قيد التقدّم من خلال تسجيل أداة معالجة لتحديثات حالة التثبيت. يمكنك أيضًا توفير شريط تقدّم في واجهة المستخدم للتطبيق لإعلام المستخدمين بمدى تقدّم عملية التنزيل.

Kotlin

// Create a listener to track request state updates.
val listener = InstallStateUpdatedListener { state ->
    // (Optional) Provide a download progress bar.
    if (state.installStatus() == InstallStatus.DOWNLOADING) {
      val bytesDownloaded = state.bytesDownloaded()
      val totalBytesToDownload = state.totalBytesToDownload()
      // Show update progress bar.
    }
    // Log state or install the update.
}

// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(listener)

// Start an update.

// When status updates are no longer needed, unregister the listener.
appUpdateManager.unregisterListener(listener)

Java

// Create a listener to track request state updates.
InstallStateUpdatedListener listener = state -> {
  // (Optional) Provide a download progress bar.
  if (state.installStatus() == InstallStatus.DOWNLOADING) {
      long bytesDownloaded = state.bytesDownloaded();
      long totalBytesToDownload = state.totalBytesToDownload();
      // Implement progress bar.
  }
  // Log state or install the update.
};

// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(listener);

// Start an update.

// When status updates are no longer needed, unregister the listener.
appUpdateManager.unregisterListener(listener);

تثبيت تحديث مرن

عند رصد حالة InstallStatus.DOWNLOADED، عليك إعادة تشغيل التطبيق لتثبيت التحديث.

على عكس التحديثات الفورية، لا يعيد Google Play تشغيل التطبيق تلقائيًا لإجراء تحديث مرن. وذلك لأنه أثناء إجراء تحديث مرن، يتوقع المستخدم أن يستمر في التفاعل مع التطبيق حتى يقرر أنه يريد تثبيت التحديث.

ويُنصح بإرسال إشعار (أو مؤشّرات أخرى لواجهة المستخدم) لإعلام المستخدم بأنّ التحديث جاهز للتثبيت وطلب تأكيد قبل إعادة تشغيل التطبيق.

يوضح المثال التالي تنفيذ أداة snackbar للتصميم المتعدد الأبعاد والتي تطلب تأكيدًا من المستخدم لإعادة تشغيل التطبيق:

Kotlin

val listener = { state ->
    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        // After the update is downloaded, show a notification
        // and request user confirmation to restart the app.
        popupSnackbarForCompleteUpdate()
    }
    ...
}

// Displays the snackbar notification and call to action.
fun popupSnackbarForCompleteUpdate() {
    Snackbar.make(
        findViewById(R.id.activity_main_layout),
        "An update has just been downloaded.",
        Snackbar.LENGTH_INDEFINITE
    ).apply {
        setAction("RESTART") { appUpdateManager.completeUpdate() }
        setActionTextColor(resources.getColor(R.color.snackbar_action_text_color))
        show()
    }
}

Java

InstallStateUpdatedListener listener = state -> {
    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        // After the update is downloaded, show a notification
        // and request user confirmation to restart the app.
        popupSnackbarForCompleteUpdate();
    }
    ...
};

// Displays the snackbar notification and call to action.
private void popupSnackbarForCompleteUpdate() {
  Snackbar snackbar =
      Snackbar.make(
          findViewById(R.id.activity_main_layout),
          "An update has just been downloaded.",
          Snackbar.LENGTH_INDEFINITE);
  snackbar.setAction("RESTART", view -> appUpdateManager.completeUpdate());
  snackbar.setActionTextColor(
      getResources().getColor(R.color.snackbar_action_text_color));
  snackbar.show();
}

عند طلب الرمز appUpdateManager.completeUpdate() في المقدّمة، تعرض المنصة واجهة مستخدم بملء الشاشة تعيد تشغيل التطبيق في الخلفية. بعد تثبيت النظام الأساسي للتحديث، تتم إعادة تشغيل التطبيق وينتقل إلى نشاطه الرئيسي.

إذا طلبت بدلاً من ذلك استخدام completeUpdate() عندما يكون تطبيقك في الخلفية، سيتم تثبيت التحديث تلقائيًا بدون حجب واجهة مستخدم الجهاز.

عندما يعرض المستخدم تطبيقك في المقدّمة، تحقَّق مما إذا كان هناك تحديث في انتظار التثبيت. إذا توفّر تحديث لتطبيقك في حالة DOWNLOADED، اطلب من المستخدم تثبيت التحديث. وإلا، تستمر بيانات التحديث في شغل مساحة تخزين جهاز المستخدم.

Kotlin

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all app entry points.
override fun onResume() {
    super.onResume()

    appUpdateManager
        .appUpdateInfo
        .addOnSuccessListener { appUpdateInfo ->
            ...
            // If the update is downloaded but not installed,
            // notify the user to complete the update.
            if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                popupSnackbarForCompleteUpdate()
            }
        }
}

Java

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all app entry points.
@Override
protected void onResume() {
  super.onResume();

  appUpdateManager
      .getAppUpdateInfo()
      .addOnSuccessListener(appUpdateInfo -> {
              ...
              // If the update is downloaded but not installed,
              // notify the user to complete the update.
              if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                  popupSnackbarForCompleteUpdate();
              }
          });
}

معالجة تحديث فوري

عند بدء تحديث فوري والموافقة من المستخدم على بدء التحديث، يعرض Google Play مستوى تقدم التحديث في أعلى واجهة المستخدم للتطبيق طوال مدة التحديث. إذا أغلق المستخدم تطبيقك أو أنهه أثناء التحديث، من المفترض أن يستمر التحديث في التنزيل والتثبيت في الخلفية بدون تأكيد إضافي من المستخدم.

ومع ذلك، عند عودة تطبيقك إلى المقدّمة، عليك التأكّد من أنّ التحديث لم يتوقّف في حالة UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS. إذا توقّف التحديث في هذه الحالة، يمكنك استئنافه:

Kotlin

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all entry points into the app.
override fun onResume() {
    super.onResume()

    appUpdateManager
        .appUpdateInfo
        .addOnSuccessListener { appUpdateInfo ->
            ...
            if (appUpdateInfo.updateAvailability()
                == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
            ) {
                // If an in-app update is already running, resume the update.
                appUpdateManager.startUpdateFlowForResult(
                  appUpdateInfo,
                  activityResultLauncher,
                  AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build())
            }
        }
}

Java

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all entry points into the app.
@Override
protected void onResume() {
  super.onResume();

  appUpdateManager
      .getAppUpdateInfo()
      .addOnSuccessListener(
          appUpdateInfo -> {
            ...
            if (appUpdateInfo.updateAvailability()
                == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
                // If an in-app update is already running, resume the update.
                appUpdateManager.startUpdateFlowForResult(
                  appUpdateInfo,
                  activityResultLauncher,
                  AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());
            }
          });
}

تعرض عملية التحديث نتيجة كما هو موضّح في المستندات المرجعية الخاصة بـ startUpdateFlowForResult(). وخصوصًا، يجب أن يتمكّن تطبيقك من التعامل مع الحالات التي يرفض فيها المستخدم التحديث أو يلغي التنزيل. عندما ينفِّذ المستخدم أيًا من هذين الإجراءين، يتم إغلاق واجهة مستخدم Google Play. يجب أن يحدّد تطبيقك أفضل طريقة للمتابعة.

إن أمكن، اسمح للمستخدم بالمتابعة بدون التحديث واطلب منه مرة أخرى في وقت لاحق. إذا لم يعمل تطبيقك بدون التحديث، يمكنك عرض رسالة إعلامية قبل إعادة بدء عملية التحديث أو الطلب من المستخدم إغلاق التطبيق. بهذه الطريقة، يدرك المستخدم أنّه يمكنه إعادة تشغيل التطبيق عندما يكون مستعدًا لتثبيت التحديث المطلوب.

الخطوات التالية

اختبِر تحديثات تطبيقك داخل التطبيق للتأكّد من أنّ عملية الدمج تعمل بشكل صحيح.