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

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

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

تُعدّ مكتبة "التحديث داخل التطبيق" في Play جزءًا من مكتبات Google Play الأساسية. أدرِج تبعية Gradle التالية لدمج مكتبة Play In-App Update.

Groovy

// 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() callback:

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

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

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

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

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

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

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