توجه: WorkManager را بهعنوان راهکار توصیهشده برای اکثر موارد استفاده از پردازش پسزمینهای توصیه میکنیم. لطفاً به راهنمای پردازش پسزمینه مراجعه کنید تا ببینید کدام راهحل برای شما مناسبتر است.
در درسهای قبلی این کلاس، با نحوه ایجاد مؤلفه وفقدهنده همگامسازی که کد انتقال داده را کپسوله میکند و نحوه افزودن مؤلفههای اضافی که به شما امکان میدهد وفقدهنده همگامسازی را به سیستم متصل کنید آشنا شدید. اکنون هرآنچه را که برای نصب برنامهای که شامل وفقدهنده همگامسازی است نیاز دارید دراختیار دارید، اما هیچکدام از کدهایی که دیدهاید درواقع وفقدهنده همگامسازی را اجرا نمیکند.
باید سعی کنید وفقدهنده همگامسازی را براساس زمانبندی یا بهعنوان نتیجه غیرمستقیم برخی رویدادها اجرا کنید. برای مثال، ممکن است بخواهید وفقدهنده همگامسازی شما طبق زمانبندی منظمی اجرا شود، یا پساز مدت زمان مشخصی یا در زمان خاصی از روز. همچنین ممکن است بخواهید وفقدهنده همگامسازیتان را وقتی اجرا کنید که تغییراتی در دادههای ذخیرهشده در دستگاه ایجاد میشود. باید از اجرای وفقدهنده همگامسازی بهعنوان نتیجه مستقیم کنش کاربر اجتناب کنید، زیرا با انجام این کار، از مزیت کامل توانایی زمانبندی چارچوب وفقدهنده همگامسازی بهرهمند نمیشوید. برای مثال، باید از ارائه دکمه بازآوری در میانای کاربر خودداری کنید.
گزینههای زیر را برای اجرای وفقدهنده همگامسازی دارید:
- وقتی دادههای سرور تغییر میکند
- وفقدهنده همگامسازی را در پاسخ به پیامی از سرور اجرا کنید، که نشان میدهد دادههای مبتنی بر سرور تغییر کرده است. این گزینه به شما امکان میدهد دادهها را از سرور به دستگاه بازآوری کنید بدون اینکه با نظرسنجی از سرور، عملکرد را کاهش دهید یا عمر باتری را هدر دهید.
- وقتی دادههای دستگاه تغییر میکند
- وقتی دادهها در دستگاه تغییر میکند، وفقدهنده همگامسازی را اجرا کنید. این گزینه به شما امکان میدهد دادههای اصلاحشده را از دستگاه به سرور ارسال کنید، و بهویژه اگر لازم باشد مطمئن شوید که سرور همیشه جدیدترین دادههای دستگاه را دارد مفید است. اگر واقعاً دادهها را در ارائهدهنده محتوایتان ذخیره میکنید، پیادهسازی این گزینه ساده است. اگر از ارائهدهنده محتوای ساختگی استفاده میکنید، تشخیص تغییرات داده ممکن است دشوارتر باشد.
- در فواصل زمانی منظم
- وفقدهنده همگامسازی را پساز انقضای بازه زمانی انتخابیتان اجرا کنید، یا آن را در زمان مشخصی در هر روز اجرا کنید.
- در صورت درخواست
- وفقدهنده همگامسازی را در پاسخ به کنش کاربر اجرا کنید. بااینحال، برای ارائه بهترین تجربه کاربری، باید عمدتاً به یکی از گزینههای خودکارتر تکیه کنید. بااستفاده از گزینههای خودکارشده، در مصرف باتری و منابع شبکه صرفهجویی میکنید.
بقیه این درس هریک از گزینهها را با جزئیات بیشتر شرح میدهد.
اجرای وفقدهنده همگامسازی هنگام تغییر دادههای سرور
اگر برنامهتان دادهها را از سرور منتقل میکند و دادههای سرور مکرراً تغییر میکند، میتوانید از
وفقدهنده همگامسازی برای بارگیری در پاسخ به تغییرات دادهها استفاده کنید. برای اجرای وفقدهنده همگامسازی،
سرور باید پیام ویژهای به BroadcastReceiver در برنامهتان ارسال کند.
در پاسخ به این پیام، ContentResolver.requestSync() را فراخوانی کنید تا به چارچوب وفقدهنده همگامسازی علامت دهد که
وفقدهنده همگامسازی شما را اجرا کند.
سیستم پیامرسانی Google Cloud (GCM) هم مؤلفههای سرور و هم مؤلفههای دستگاه را که برای کار کردن این سیستم پیامرسانی نیاز دارید ارائه میدهد. استفاده از GCM برای راهاندازی
انتقالها نسبت به نظرسنجی از سرورها برای وضعیت، قابلاطمینانتر و کارآمدتر است. درحالیکه نظرسنجی
به Service نیاز دارد که همیشه فعال است، GCM از
BroadcastReceiver استفاده میکند که وقتی پیامی میرسد فعال میشود. درحالیکه نظرسنجی در فواصل منظم حتی اگر بهروزرسانی دردسترس نباشد از شارژ باتری استفاده میکند، GCM فقط درصورت نیاز پیام ارسال میکند.
توجه: اگر از GCM برای راهاندازی وفقدهنده همگامسازی ازطریق پخش به همه دستگاههایی که برنامهتان در آنها نصب شده است استفاده میکنید، بهخاطر داشته باشید که آنها پیام شما را تقریباً در یک زمان دریافت میکنند. این وضعیت میتواند باعث شود چند نمونه از وفقدهنده همگامسازی شما بهطور همزمان اجرا شود و درنتیجه سرور و شبکه اضافهبار داشته باشند. برای جلوگیری از این وضعیت برای همهفرستی به همه دستگاهها، باید شروع وفقدهنده همگامسازی را برای دورهای که برای هر دستگاه منحصربهفرد است بهتعویق بیندازید.
تکهکد زیر نحوه اجرای
requestSync() را در پاسخ به
پیام GCM ورودی نشان میدهد:
کاتلین
... // Constants // Content provider authority const val AUTHORITY = "com.example.android.datasync.provider" // Account type const val ACCOUNT_TYPE = "com.example.android.datasync" // Account const val ACCOUNT = "default_account" // Incoming Intent key for extended data const val KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST" ... class GcmBroadcastReceiver : BroadcastReceiver() { ... override fun onReceive(context: Context, intent: Intent) { // Get a GCM object instance val gcm: GoogleCloudMessaging = GoogleCloudMessaging.getInstance(context) // Get the type of GCM message val messageType: String? = gcm.getMessageType(intent) /* * Test the message type and examine the message contents. * Since GCM is a general-purpose messaging system, you * may receive normal messages that don't require a sync * adapter run. * The following code tests for a a boolean flag indicating * that the message is requesting a transfer from the device. */ if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE == messageType && intent.getBooleanExtra(KEY_SYNC_REQUEST, false)) { /* * Signal the framework to run your sync adapter. Assume that * app initialization has already created the account. */ ContentResolver.requestSync(mAccount, AUTHORITY, null) ... } ... } ... }
جاوا
public class GcmBroadcastReceiver extends BroadcastReceiver { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Account type public static final String ACCOUNT_TYPE = "com.example.android.datasync"; // Account public static final String ACCOUNT = "default_account"; // Incoming Intent key for extended data public static final String KEY_SYNC_REQUEST = "com.example.android.datasync.KEY_SYNC_REQUEST"; ... @Override public void onReceive(Context context, Intent intent) { // Get a GCM object instance GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(context); // Get the type of GCM message String messageType = gcm.getMessageType(intent); /* * Test the message type and examine the message contents. * Since GCM is a general-purpose messaging system, you * may receive normal messages that don't require a sync * adapter run. * The following code tests for a a boolean flag indicating * that the message is requesting a transfer from the device. */ if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType) && intent.getBooleanExtra(KEY_SYNC_REQUEST)) { /* * Signal the framework to run your sync adapter. Assume that * app initialization has already created the account. */ ContentResolver.requestSync(mAccount, AUTHORITY, null); ... } ... } ... }
اجرای وفقدهنده همگامسازی هنگام تغییر دادههای ارائهدهنده محتوا
اگر برنامهتان دادهها را در ارائهدهنده محتوا جمعآوری میکند و میخواهید هرزمان که ارائهدهنده را بهروز میکنید، سرور را بهروز کنید، میتوانید برنامهتان را طوری تنظیم کنید که وفقدهنده همگامسازی را بهطور خودکار اجرا کند. برای انجام این کار،
یک ناظر برای ارائهدهنده محتوا ثبت میکنید. وقتی دادهها در ارائهدهنده محتوا
تغییر میکند، چارچوب ارائهدهنده محتوا ناظر را فرامیخواند. در ناظر،
requestSync() را فراخوانی کنید تا به چارچوب بگویید
وفقدهنده همگامسازی شما را اجرا کند.
توجه: اگر از ارائهدهنده محتوای تهی استفاده میکنید، هیچ دادهای در ارائهدهنده محتوا ندارید و onChange() هرگز فراخوانی نمیشود. در این حالت، باید سازوکار خودتان را برای شناسایی تغییرات در دادههای دستگاه ارائه دهید. این سازوکار همچنین مسئول فراخوانی
requestSync() هنگام تغییر دادهها است.
برای ایجاد ناظر برای ارائهدهنده محتوایتان، کلاس
ContentObserver را گسترش دهید و هر دو شکل روش
onChange() آن را پیادهسازی کنید. در
onChange()،
requestSync() را برای شروع وفقدهنده همگامسازی فراخوانی کنید.
برای ثبت ناظر، آن را بهعنوان آرگومان در تماس با
registerContentObserver() ارسال کنید. در
این فراخوانی، باید شناسه منبع یکسان محتوا را نیز برای دادههایی که میخواهید تماشا کنید ارسال کنید. چارچوب ارائهدهنده محتوا این شناسه منبع یکسان تماشا را با شناسههای منبع یکسان محتوا که بهعنوان آرگومان به روشهای ContentResolver که ارائهدهنده شما را اصلاح میکنند (مانند ContentResolver.insert()) ارسال شدهاند مقایسه میکند. اگر مطابقت وجود داشته باشد،
پیادهسازی شما از ContentObserver.onChange()
فراخوانده میشود.
تکهکد زیر به شما نشان میدهد که چگونه یک ContentObserver
تعریف کنید که هنگام تغییر جدول،
requestSync() را فراخوانی کند:
کاتلین
// Constants // Content provider scheme const val SCHEME = "content://" // Content provider authority const val AUTHORITY = "com.example.android.datasync.provider" // Path for the content provider table const val TABLE_PATH = "data_table" ... class MainActivity : FragmentActivity() { ... // A content URI for the content provider's data table private lateinit var uri: Uri // A content resolver for accessing the provider private lateinit var mResolver: ContentResolver ... inner class TableObserver(...) : ContentObserver(...) { /* * Define a method that's called when data in the * observed content provider changes. * This method signature is provided for compatibility with * older platforms. */ override fun onChange(selfChange: Boolean) { /* * Invoke the method signature available as of * Android platform version 4.1, with a null URI. */ onChange(selfChange, null) } /* * Define a method that's called when data in the * observed content provider changes. */ override fun onChange(selfChange: Boolean, changeUri: Uri?) { /* * Ask the framework to run your sync adapter. * To maintain backward compatibility, assume that * changeUri is null. */ ContentResolver.requestSync(account, AUTHORITY, null) } ... } ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... // Get the content resolver object for your app mResolver = contentResolver // Construct a URI that points to the content provider data table uri = Uri.Builder() .scheme(SCHEME) .authority(AUTHORITY) .path(TABLE_PATH) .build() /* * Create a content observer object. * Its code does not mutate the provider, so set * selfChange to "false" */ val observer = TableObserver(false) /* * Register the observer for the data table. The table's path * and any of its subpaths trigger the observer. */ mResolver.registerContentObserver(uri, true, observer) ... } ... }
جاوا
public class MainActivity extends FragmentActivity { ... // Constants // Content provider scheme public static final String SCHEME = "content://"; // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Path for the content provider table public static final String TABLE_PATH = "data_table"; // Account public static final String ACCOUNT = "default_account"; // Global variables // A content URI for the content provider's data table Uri uri; // A content resolver for accessing the provider ContentResolver mResolver; ... public class TableObserver extends ContentObserver { /* * Define a method that's called when data in the * observed content provider changes. * This method signature is provided for compatibility with * older platforms. */ @Override public void onChange(boolean selfChange) { /* * Invoke the method signature available as of * Android platform version 4.1, with a null URI. */ onChange(selfChange, null); } /* * Define a method that's called when data in the * observed content provider changes. */ @Override public void onChange(boolean selfChange, Uri changeUri) { /* * Ask the framework to run your sync adapter. * To maintain backward compatibility, assume that * changeUri is null. */ ContentResolver.requestSync(mAccount, AUTHORITY, null); } ... } ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Get the content resolver object for your app mResolver = getContentResolver(); // Construct a URI that points to the content provider data table uri = new Uri.Builder() .scheme(SCHEME) .authority(AUTHORITY) .path(TABLE_PATH) .build(); /* * Create a content observer object. * Its code does not mutate the provider, so set * selfChange to "false" */ TableObserver observer = new TableObserver(false); /* * Register the observer for the data table. The table's path * and any of its subpaths trigger the observer. */ mResolver.registerContentObserver(uri, true, observer); ... } ... }
اجرای دورهای وفقدهنده همگامسازی
میتوانید وفقدهنده همگامسازی را با تنظیم مدت زمان انتظار بین اجراها، یا با اجرای آن در زمانهای خاصی از روز، یا هر دو، بهصورت دورهای اجرا کنید. اجرای دورهای وفقدهنده همگامسازی به شما امکان میدهد تقریباً با فاصله زمانی بهروزرسانی سرورتان مطابقت داشته باشید.
بهطور مشابه، میتوانید دادهها را از دستگاه بارگذاری کنید وقتی سرور شما نسبتاً آزاد است، با زمانبندی وفقدهنده همگامسازی برای اجرا در شب. اکثر کاربران دستگاهشان را در شب روشن و به برق متصل میگذارند، بنابراین این زمان معمولاً دردسترس است. علاوهبراین، دستگاه همزمان با وفقدهنده همگامسازی شما، تکالیف دیگری را اجرا نمیکند. اما اگر این رویکرد را اتخاذ کنید، باید مطمئن شوید که هر دستگاه انتقال داده را در زمان کمی متفاوت راهاندازی میکند. اگر همه دستگاهها بهطور همزمان وفقدهنده همگامسازی شما را اجرا کنند، احتمالاً بار اضافی بر سرور و شبکههای داده ارائهدهنده تلفن همراه شما تحمیل خواهد شد.
بهطور کلی، اگر کاربران شما به بهروزرسانیهای فوری نیاز نداشته باشند اما انتظار داشته باشند که بهروزرسانیهای منظم دریافت کنند، اجراهای دورهای منطقی است. اجراهای دورهای همچنین زمانی منطقی است که بخواهید توازنی بین دردسترس بودن دادههای بهروز و کارایی اجراهای کوچکتر وفقدهنده همگامسازی برقرار کنید که از منابع دستگاه بیشازحد استفاده نمیکنند.
برای اجرای وفقدهنده همگامسازی در فواصل زمانی منظم،
addPeriodicSync() را فراخوانی کنید. این کار وفقدهنده همگامسازی شما را زمانبندی میکند تا پساز گذشت مدت زمان مشخصی اجرا شود. ازآنجاییکه چارچوب وفقدهنده همگامسازی باید اجراهای دیگر وفقدهنده همگامسازی را درنظر بگیرد و سعی میکند کارایی باتری را به حداکثر برساند، زمان سپریشده ممکن است چند ثانیه متفاوت باشد. همچنین، اگر شبکه دردسترس نباشد، چارچوب وفقدهنده همگامسازی شما را اجرا نخواهد کرد.
توجه داشته باشید که addPeriodicSync()
وفقدهنده همگامسازی را در زمان خاصی از روز اجرا نمیکند. برای اجرای وفقدهنده همگامسازی تقریباً در
زمان یکسان در هر روز، از زنگ تکرارشونده بهعنوان راهانداز استفاده کنید. زنگهای تکرارشونده با جزئیات بیشتر در سند مرجع AlarmManager شرح داده شدهاند. اگر از
روش setInexactRepeating() برای تنظیم
راهاندازهای زمان روز که دارای برخی تغییرات هستند استفاده میکنید، همچنان باید زمان شروع را تصادفی کنید تا
مطمئن شوید که اجرای وفقدهنده همگامسازی از دستگاههای مختلف بهصورت متناوب انجام میشود.
روش addPeriodicSync()
setSyncAutomatically() را غیرفعال نمیکند،
بنابراین ممکن است در مدت زمان نسبتاً کوتاهی چندین اجرای همگامسازی دریافت کنید. همچنین، فقط چند پرچم کنترل وفقدهنده همگامسازی در فراخوانی به addPeriodicSync() مجاز است؛ پرچمهایی که مجاز نیستند در اسناد مرجع برای addPeriodicSync() شرح داده شدهاند.
تکهکد زیر نحوه زمانبندی کردن اجرای دورهای وفقدهنده همگامسازی را نشان میدهد:
کاتلین
// Content provider authority const val AUTHORITY = "com.example.android.datasync.provider" // Account const val ACCOUNT = "default_account" // Sync interval constants const val SECONDS_PER_MINUTE = 60L const val SYNC_INTERVAL_IN_MINUTES = 60L const val SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE ... class MainActivity : FragmentActivity() { ... // A content resolver for accessing the provider private lateinit var mResolver: ContentResolver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... // Get the content resolver for your app mResolver = contentResolver /* * Turn on periodic syncing */ ContentResolver.addPeriodicSync( mAccount, AUTHORITY, Bundle.EMPTY, SYNC_INTERVAL) ... } ... }
جاوا
public class MainActivity extends FragmentActivity { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Account public static final String ACCOUNT = "default_account"; // Sync interval constants public static final long SECONDS_PER_MINUTE = 60L; public static final long SYNC_INTERVAL_IN_MINUTES = 60L; public static final long SYNC_INTERVAL = SYNC_INTERVAL_IN_MINUTES * SECONDS_PER_MINUTE; // Global variables // A content resolver for accessing the provider ContentResolver mResolver; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Get the content resolver for your app mResolver = getContentResolver(); /* * Turn on periodic syncing */ ContentResolver.addPeriodicSync( mAccount, AUTHORITY, Bundle.EMPTY, SYNC_INTERVAL); ... } ... }
اجرای وفقدهنده همگامسازی بهمحض درخواست
اجرای وفقدهنده همگامسازی در پاسخ به درخواست کاربر، کمترین اولویت را در بین استراتژیهای اجرای وفقدهنده همگامسازی دارد. این چارچوب بهطور خاص برای صرفهجویی در مصرف باتری طراحی شده است وقتی وفقدهندههای همگامسازی را طبق زمانبندی اجرا میکند. گزینههایی که همگامسازی را در پاسخ به تغییرات داده اجرا میکنند، بهطور مؤثری از شارژ باتری استفاده میکنند، زیرا شارژ برای ارائه دادههای جدید استفاده میشود.
درمقابل، اگر به کاربران اجازه دهید همگامسازی را بهصورت درخواستی اجرا کنند، همگامسازی بهصورت خودکار اجرا میشود که استفاده ناکارآمدی از منابع شبکه و برق است. همچنین، ارائه همگامسازی درصورت درخواست باعث میشود کاربران حتی اگر شواهدی مبنی بر تغییر دادهها وجود نداشته باشد، درخواست همگامسازی کنند و اجرای همگامسازیای که دادهها را بازآوری نمیکند، استفاده ناکارآمد از شارژ باتری است. بهطور کلی، برنامهتان باید از سیگنالهای دیگر برای راهاندازی همگامسازی استفاده کند یا آنها را در فواصل منظم، بدون ورودی کاربر، زمانبندی کند.
بااینحال، اگر همچنان میخواهید وفقدهنده همگامسازی را بهصورت درخواستی اجرا کنید، پرچمهای وفقدهنده همگامسازی را برای اجرای وفقدهنده همگامسازی دستی تنظیم کنید، سپس ContentResolver.requestSync() را فراخوانی کنید.
انتقالهای درخواستی را با پرچمهای زیر اجرا کنید:
-
SYNC_EXTRAS_MANUAL -
همگامسازی دستی را اجباری میکند. چارچوب وفقدهنده همگامسازی تنظیمات موجود را نادیده میگیرد،
مثل پرچم تنظیمشده توسط
setSyncAutomatically(). -
SYNC_EXTRAS_EXPEDITED - همگامسازی را مجبور میکند فوراً شروع شود. اگر این را تنظیم نکنید، ممکن است سیستم چند ثانیه قبلاز اجرای درخواست همگامسازی صبر کند، زیرا سعی میکند با زمانبندی درخواستهای متعدد در مدتزمان کوتاه، مصرف باتری را بهینهسازی کند.
تکهکد زیر نحوه فراخوانی
requestSync() را در پاسخ به کلیک
دکمه نشان میدهد:
کاتلین
// Constants // Content provider authority val AUTHORITY = "com.example.android.datasync.provider" // Account type val ACCOUNT_TYPE = "com.example.android.datasync" // Account val ACCOUNT = "default_account" ... class MainActivity : FragmentActivity() { ... // Instance fields private lateinit var mAccount: Account ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... /* * Create the placeholder account. The code for CreateSyncAccount * is listed in the lesson Creating a Sync Adapter */ mAccount = createSyncAccount() ... } /** * Respond to a button click by calling requestSync(). This is an * asynchronous operation. * * This method is attached to the refresh button in the layout * XML file * * @param v The View associated with the method call, * in this case a Button */ fun onRefreshButtonClick(v: View) { // Pass the settings flags by inserting them in a bundle val settingsBundle = Bundle().apply { putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true) putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true) } /* * Request the sync for the default account, authority, and * manual sync settings */ ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle) }
جاوا
public class MainActivity extends FragmentActivity { ... // Constants // Content provider authority public static final String AUTHORITY = "com.example.android.datasync.provider"; // Account type public static final String ACCOUNT_TYPE = "com.example.android.datasync"; // Account public static final String ACCOUNT = "default_account"; // Instance fields Account mAccount; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... /* * Create the placeholder account. The code for CreateSyncAccount * is listed in the lesson Creating a Sync Adapter */ mAccount = CreateSyncAccount(this); ... } /** * Respond to a button click by calling requestSync(). This is an * asynchronous operation. * * This method is attached to the refresh button in the layout * XML file * * @param v The View associated with the method call, * in this case a Button */ public void onRefreshButtonClick(View v) { // Pass the settings flags by inserting them in a bundle Bundle settingsBundle = new Bundle(); settingsBundle.putBoolean( ContentResolver.SYNC_EXTRAS_MANUAL, true); settingsBundle.putBoolean( ContentResolver.SYNC_EXTRAS_EXPEDITED, true); /* * Request the sync for the default account, authority, and * manual sync settings */ ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle); }