توجه: ما WorkManager را به عنوان راه حل توصیه شده برای اکثر موارد استفاده از پردازش پس زمینه توصیه می کنیم. لطفاً به راهنمای پردازش پسزمینه مراجعه کنید تا بدانید کدام راهحل برای شما مناسبتر است.
در درسهای قبلی این کلاس، یاد گرفتید که چگونه یک جزء آداپتور همگامسازی ایجاد کنید که کد انتقال داده را محصور میکند، و چگونه اجزای اضافی را اضافه کنید که به شما امکان میدهد آداپتور همگامسازی را به سیستم وصل کنید. اکنون همه چیزهایی را که برای نصب برنامه ای که شامل آداپتور همگام سازی است نیاز دارید، دارید، اما هیچ کدام از کدهایی که دیده اید، آداپتور همگام سازی را اجرا نمی کند.
باید سعی کنید آداپتور همگام سازی خود را بر اساس یک زمان بندی یا به عنوان نتیجه غیرمستقیم یک رویداد اجرا کنید. به عنوان مثال، ممکن است بخواهید آداپتور همگام سازی شما بر اساس یک برنامه منظم، یا پس از یک دوره زمانی مشخص یا در یک زمان خاص از روز اجرا شود. همچنین ممکن است بخواهید وقتی تغییراتی در داده های ذخیره شده در دستگاه ایجاد می شود، آداپتور همگام سازی خود را اجرا کنید. شما باید از اجرای آداپتور همگامسازی خود به عنوان نتیجه مستقیم یک اقدام کاربر اجتناب کنید، زیرا با انجام این کار از توانایی زمانبندی چارچوب آداپتور همگامسازی بهره کامل نخواهید برد. به عنوان مثال، باید از ارائه دکمه رفرش در رابط کاربری خود اجتناب کنید.
برای اجرای آداپتور همگام سازی خود گزینه های زیر را دارید:
- وقتی داده های سرور تغییر می کند
- آداپتور همگام سازی را در پاسخ به پیامی از یک سرور اجرا کنید که نشان می دهد داده های مبتنی بر سرور تغییر کرده است. این گزینه به شما امکان می دهد تا با نظرسنجی از سرور، داده ها را از سرور به دستگاه بدون کاهش عملکرد یا اتلاف عمر باتری، به روز کنید.
- وقتی داده های دستگاه تغییر می کند
- هنگامی که داده ها در دستگاه تغییر می کنند، یک آداپتور همگام سازی را اجرا کنید. این گزینه به شما امکان می دهد داده های اصلاح شده را از دستگاه به سرور ارسال کنید و به ویژه در صورتی مفید است که باید اطمینان حاصل کنید که سرور همیشه آخرین داده های دستگاه را دارد. اگر واقعاً دادهها را در ارائهدهنده محتوای خود ذخیره میکنید، اجرای این گزینه ساده است. اگر از ارائهدهنده محتوای خرد استفاده میکنید، تشخیص تغییرات دادهها ممکن است دشوارتر باشد.
- در فواصل منظم
- یک آداپتور همگامسازی را بعد از انقضای یک بازه زمانی که انتخاب میکنید اجرا کنید، یا آن را در زمان خاصی هر روز اجرا کنید.
- در صورت تقاضا
- آداپتور همگام سازی را در پاسخ به اقدام کاربر اجرا کنید. با این حال، برای ارائه بهترین تجربه کاربری، باید در درجه اول به یکی از گزینه های خودکارتر تکیه کنید. با استفاده از گزینه های خودکار، در مصرف باتری و منابع شبکه صرفه جویی می کنید.
بقیه این درس هر یک از گزینه ها را با جزئیات بیشتری توضیح می دهد.
هنگامی که داده های سرور تغییر می کند، آداپتور همگام سازی را اجرا کنید
اگر برنامه شما دادهها را از یک سرور منتقل میکند و دادههای سرور مرتباً تغییر میکند، میتوانید از یک آداپتور همگامسازی برای انجام دانلودها در پاسخ به تغییرات دادهها استفاده کنید. برای اجرای آداپتور همگام سازی، از سرور بخواهید یک پیام ویژه به یک BroadcastReceiver
در برنامه شما ارسال کند. در پاسخ به این پیام، ContentResolver.requestSync()
را فراخوانی کنید تا به چارچوب آداپتور همگام سازی سیگنال دهد تا آداپتور همگام سازی شما را اجرا کند.
Google Cloud Messaging (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()
ارسال کنید. در این تماس، همچنین باید یک URI محتوا برای دادههایی که میخواهید تماشا کنید، ارسال کنید. چارچوب ارائهدهنده محتوا، این URI ساعت را با URIهای محتوا که به عنوان آرگومانهایی به روشهای 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); }