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