يوضّح هذا الدليل كيفية إتاحة التحديثات داخل التطبيق في تطبيقك باستخدام Kotlin أو Java. تتوفّر أدلة منفصلة للحالات التي تُستخدم فيها لغة البرمجة الأصلية (C/C++) في التنفيذ، والحالات التي تُستخدم فيها Unity أو Unreal Engine في التنفيذ.
إعداد بيئة التطوير
تُعدّ مكتبة Play للتحديثات داخل التطبيق جزءًا من مكتبات Google Play Core. يجب تضمين تبعية 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 كما هو موضّح في مستندات 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 تلقائيًا إلى إعادة تشغيل التطبيق لتحديث مرن. يرجع ذلك إلى أنّ المستخدم يتوقع أثناء التحديث المرن مواصلة التفاعل مع التطبيق إلى أن يقرّر تثبيت التحديث.
ننصحك بعرض إشعار (أو مؤشر آخر في واجهة المستخدم) لإعلام المستخدم بأنّ التحديث جاهز للتثبيت وطلب التأكيد قبل إعادة تشغيل التطبيق.
يوضّح المثال التالي كيفية تنفيذ شريط إعلام منبثق بتصميم 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. يجب أن يحدّد تطبيقك أفضل طريقة للمتابعة.
إذا أمكن، اسمح للمستخدم بالمتابعة بدون التحديث واطلب منه ذلك مرة أخرى لاحقًا. إذا كان تطبيقك لا يعمل بدون التحديث، ننصحك بعرض رسالة إعلامية قبل إعادة تشغيل مسار التحديث أو مطالبة المستخدم بإغلاق التطبيق. وبهذه الطريقة، يفهم المستخدم أنّه يمكنه إعادة تشغيل تطبيقك عندما يكون جاهزًا لتثبيت التحديث المطلوب.
الخطوات التالية
اختبِر التحديثات داخل التطبيق للتأكّد من أنّ عملية الدمج تعمل بشكل صحيح.