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

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

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

تُعدّ مكتبة Play In-App Update جزءًا من مكتبات 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 الذي تم عرضه على حالة توفّر التحديث. استنادًا إلى حالة التعديل، يتضمّن المثيل أيضًا ما يلي:

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

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

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

استخدِم clientVersionStalenessDays() لمعرفة عدد الأيام التي مرّت منذ أن أصبح التحديث متاحًا في &quot;متجر Google Play&quot;:

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

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

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

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

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

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

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