این راهنما نحوه پشتیبانی از بهروزرسانیهای درونبرنامهای در برنامه شما را با استفاده از 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' ... }
کاتلین
// 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 برای بررسی بهروزرسانی استفاده کنید:
کاتلین
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. } }
جاوا
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 شامل وضعیت در دسترس بودن بهروزرسانی است. بسته به وضعیت بهروزرسانی، نمونه همچنین شامل موارد زیر است:
- اگر بهروزرسانی موجود باشد و بهروزرسانی مجاز باشد، نمونه همچنین شامل یک intent برای شروع بهروزرسانی است.
- اگر بهروزرسانی درونبرنامهای در حال انجام باشد، نمونه همچنین وضعیت بهروزرسانی در حال انجام را گزارش میدهد.
بررسی قدیمی بودن بهروزرسانی
علاوه بر بررسی اینکه آیا بهروزرسانی در دسترس است یا خیر، ممکن است بخواهید بررسی کنید که از آخرین باری که کاربر از طریق فروشگاه Play از بهروزرسانی مطلع شده است، چقدر زمان گذشته است. این میتواند به شما کمک کند تصمیم بگیرید که آیا باید بهروزرسانی انعطافپذیر یا بهروزرسانی فوری را آغاز کنید. به عنوان مثال، ممکن است چند روز قبل از اطلاعرسانی به کاربر با بهروزرسانی انعطافپذیر و چند روز بعد از آن قبل از نیاز به بهروزرسانی فوری، صبر کنید.
از clientVersionStalenessDays() برای بررسی تعداد روزهایی که از انتشار بهروزرسانی در فروشگاه Play میگذرد، استفاده کنید:
کاتلین
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. } }
جاوا
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. } });
بررسی اولویت بهروزرسانی
رابط برنامهنویسی کاربردی توسعهدهندگان گوگل پلی به شما امکان میدهد اولویت هر بهروزرسانی را تعیین کنید. این به برنامه شما اجازه میدهد تا تصمیم بگیرد که با چه شدتی یک بهروزرسانی را به کاربر توصیه کند. برای مثال، استراتژی زیر را برای تنظیم اولویت بهروزرسانی در نظر بگیرید:
- بهبودهای جزئی رابط کاربری: بهروزرسانی با اولویت پایین ؛ نه بهروزرسانی انعطافپذیر و نه بهروزرسانی فوری درخواست نکنید. فقط زمانی بهروزرسانی کنید که کاربر با برنامه شما تعامل ندارد.
- بهبود عملکرد: بهروزرسانی با اولویت متوسط ؛ درخواست بهروزرسانی انعطافپذیر.
- بهروزرسانی امنیتی حیاتی: بهروزرسانی با اولویت بالا ؛ درخواست بهروزرسانی فوری.
برای تعیین اولویت، گوگل پلی از یک مقدار صحیح بین ۰ تا ۵ استفاده میکند که ۰ مقدار پیشفرض و ۵ بالاترین اولویت را دارد. برای تنظیم اولویت یک بهروزرسانی، از فیلد inAppUpdatePriority در زیر Edits.tracks.releases در Google Play Developer API استفاده کنید. همه نسخههای تازه اضافه شده در نسخه، اولویت یکسانی با نسخه دارند. اولویت فقط هنگام انتشار نسخه جدید قابل تنظیم است و بعداً قابل تغییر نیست.
اولویت را با استفاده از Google Play Developer API همانطور که در مستندات Play Developer API توضیح داده شده است، تنظیم کنید. اولویت بهروزرسانی درونبرنامهای باید در منبع Edit.tracks که در متد Edit.tracks: update ارسال شده است، مشخص شود. مثال زیر انتشار یک برنامه با کد نسخه ۸۸ و inAppUpdatePriority 5 را نشان میدهد:
{ "releases": [{ "versionCodes": ["88"], "inAppUpdatePriority": 5, "status": "completed" }] }
در کد برنامه خود، میتوانید با استفاده از updatePriority() سطح اولویت را برای یک بهروزرسانی مشخص بررسی کنید. اولویت بازگشتی، inAppUpdatePriority برای همه کدهای نسخه برنامه بین نسخه نصب شده و آخرین نسخه موجود، صرف نظر از مسیر انتشار، در نظر میگیرد. به عنوان مثال، سناریوی زیر را در نظر بگیرید:
- شما نسخه ۱ را بدون اولویت در یک مسیر تولید منتشر میکنید.
- شما نسخه ۲ را با اولویت ۵ در یک مسیر آزمایشی داخلی منتشر میکنید.
- شما نسخه ۳ را بدون هیچ اولویتی در یک مسیر تولید منتشر میکنید.
وقتی کاربران نسخهٔ اصلی از نسخهٔ ۱ به نسخهٔ ۳ بهروزرسانی میکنند، اولویت ۵ را دریافت میکنند، حتی اگر نسخهٔ ۲ در مسیر متفاوتی منتشر شده باشد.
کاتلین
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. } }
جاوا
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() درخواست بهروزرسانی دهید:
کاتلین
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())
جاوا
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 یک پرتابکنندهی نتیجهی فعالیت (activity result launcher) ثبت کنید. بخش مربوط به دریافت فراخوانی برای وضعیت بهروزرسانی را بررسی کنید.
مراحل بعدی بستگی به این دارد که آیا درخواست بهروزرسانی انعطافپذیر دارید یا بهروزرسانی فوری .
پیکربندی بهروزرسانی با AppUpdateOptions
AppUpdateOptions حاوی یک فیلد AllowAssetPackDeletion است که مشخص میکند آیا بهروزرسانی مجاز به پاک کردن بستههای دارایی در صورت محدودیت فضای ذخیرهسازی دستگاه است یا خیر. این فیلد به طور پیشفرض روی false تنظیم شده است، اما میتوانید از متد setAllowAssetPackDeletion() برای تنظیم آن روی true استفاده کنید:
کاتلین
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())
جاوا
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());
برای اطلاع از وضعیت بهروزرسانی، تماس مجدد دریافت کنید
پس از شروع بهروزرسانی، فراخوانی لانچر نتیجه فعالیت ثبتشده، نتیجه کادر محاورهای تأیید را دریافت میکند:
کاتلین
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. } }
جاوا
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: خطای دیگری مانع از ارائه رضایت کاربر یا ادامه بهروزرسانی شد.
مدیریت یک بهروزرسانی انعطافپذیر
وقتی یک بهروزرسانی انعطافپذیر را شروع میکنید، ابتدا یک کادر محاورهای برای درخواست رضایت کاربر ظاهر میشود. اگر کاربر رضایت دهد، دانلود در پسزمینه شروع میشود و کاربر میتواند به تعامل با برنامه شما ادامه دهد. این بخش نحوه نظارت و تکمیل یک بهروزرسانی انعطافپذیر درونبرنامهای را شرح میدهد.
نظارت بر وضعیت بهروزرسانی انعطافپذیر
پس از شروع دانلود برای یک بهروزرسانی انعطافپذیر، برنامه شما باید وضعیت بهروزرسانی را رصد کند تا بداند چه زمانی میتوان بهروزرسانی را نصب کرد و پیشرفت را در رابط کاربری برنامه شما نمایش دهد.
شما میتوانید با ثبت یک شنونده برای بهروزرسانیهای وضعیت نصب، وضعیت بهروزرسانی در حال انجام را رصد کنید. همچنین میتوانید یک نوار پیشرفت در رابط کاربری برنامه ایجاد کنید تا کاربران را از پیشرفت دانلود مطلع کنید.
کاتلین
// 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)
جاوا
// 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 را تشخیص دادید، برای نصب بهروزرسانی باید برنامه را مجدداً راهاندازی کنید.
برخلاف بهروزرسانیهای فوری، گوگل پلی برای بهروزرسانی انعطافپذیر، بهطور خودکار برنامه را مجدداً راهاندازی نمیکند. دلیل این امر این است که در طول بهروزرسانی انعطافپذیر، کاربر انتظار دارد تا زمانی که تصمیم به نصب بهروزرسانی بگیرد، به تعامل با برنامه ادامه دهد.
توصیه میشود قبل از راهاندازی مجدد برنامه، یک اعلان (یا برخی نشانههای رابط کاربری دیگر) ارائه دهید تا به کاربر اطلاع دهید که بهروزرسانی آماده نصب است و درخواست تأیید کنید.
مثال زیر پیادهسازی یک اسنکبار طراحی متریال را نشان میدهد که برای راهاندازی مجدد برنامه، از کاربر تأییدیه درخواست میکند:
کاتلین
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() } }
جاوا
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 دارد، از کاربر بخواهید که بهروزرسانی را نصب کند. در غیر این صورت، دادههای بهروزرسانی همچنان فضای ذخیرهسازی دستگاه کاربر را اشغال میکنند.
کاتلین
// 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() } } }
جاوا
// 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(); } }); }
رسیدگی به بهروزرسانی فوری
وقتی بهروزرسانی فوری را شروع میکنید و کاربر با شروع بهروزرسانی موافقت میکند، گوگل پلی پیشرفت بهروزرسانی را در بالای رابط کاربری برنامه شما در تمام مدت بهروزرسانی نمایش میدهد. اگر کاربر در حین بهروزرسانی برنامه شما را ببندد یا از آن خارج شود، بهروزرسانی باید بدون تأیید اضافی کاربر، در پسزمینه به دانلود و نصب ادامه دهد.
با این حال، وقتی برنامه شما به پیشزمینه برمیگردد، باید تأیید کنید که بهروزرسانی در حالت UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS متوقف نشده است. اگر بهروزرسانی در این حالت متوقف شد، بهروزرسانی را از سر بگیرید:
کاتلین
// 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()) } } }
جاوا
// 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 بسته میشود. برنامه شما باید بهترین روش برای ادامه کار را تعیین کند.
در صورت امکان، اجازه دهید کاربر بدون بهروزرسانی به کار خود ادامه دهد و بعداً دوباره از او بخواهید. اگر برنامه شما بدون بهروزرسانی نمیتواند کار کند، قبل از راهاندازی مجدد جریان بهروزرسانی یا درخواست بستن برنامه توسط کاربر، یک پیام آموزنده نمایش دهید. به این ترتیب، کاربر متوجه میشود که میتواند برنامه شما را پس از آماده شدن برای نصب بهروزرسانی مورد نیاز، مجدداً راهاندازی کند.
مراحل بعدی
بهروزرسانیهای درونبرنامهای برنامهتان را آزمایش کنید تا مطمئن شوید که یکپارچهسازی به درستی کار میکند.