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

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

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

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

رائع

// 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 ضبط أولوية كل تحديث. يتيح ذلك لتطبيقك تحديد مدى قوة اقتراح التحديث على المستخدم. على سبيل المثال، يمكنك اتّباع الاستراتيجية التالية لضبط أولوية التعديل:

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

لتحديد الأولوية، يستخدم 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 تشغيل التطبيق تلقائيًا عند إجراء تحديث مرن. ويعود السبب في ذلك إلى أنّه أثناء التحديث المرن، يتوقع العميل مواصلة التفاعل مع التطبيق إلى أن يقرر تثبيت التحديث.

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

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

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. يجب أن يحدِّد تطبيقك أفضل طريقة للمتابعة.

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

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

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