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

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

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

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

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

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

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