ملاحظة: ننصح باستخدام WorkManager كحلّ مقترَح لمعظم حالات استخدام المعالجة في الخلفية. يُرجى الرجوع إلى دليل المعالجة في الخلفية لمعرفة الحلّ الأنسب لك.
في الدروس السابقة من هذا الصف، تعلّمت كيفية إنشاء مكوِّن محوّل مزامنة يتضمّن رمز نقل البيانات، وكيفية إضافة المكوّنات الإضافية التي تتيح لك توصيل محوِّل المزامنة بالنظام. لديك الآن كل ما تحتاج إليه لتثبيت تطبيق يتضمن محوّل مزامنة، ولكن لا يعمل أي رمز من الرموز التي تراها فعليًا محوِّل المزامنة.
يجب أن تحاول تشغيل محول المزامنة استنادًا إلى جدول زمني أو كنتيجة غير مباشرة لحدث ما. على سبيل المثال، قد تحتاج إلى تشغيل محوّل المزامنة وفقًا لجدول زمني منتظم، سواء بعد فترة زمنية معيّنة أو في وقت معيّن من اليوم. ويمكنك أيضًا تشغيل محوّل المزامنة عند إجراء تغييرات على البيانات المخزَّنة على الجهاز. يجب تجنُّب تشغيل محوّل المزامنة كنتيجة مباشرة لإجراء المستخدم، لأنّ تنفيذ ذلك لن تحصل على الفائدة الكاملة من قدرة جدولة إطار عمل محوّل المزامنة. على سبيل المثال، عليك تجنُّب توفير زر إعادة تحميل في واجهة المستخدم.
تتوفر لك الخيارات التالية لتشغيل محوّل المزامنة:
- عند تغيير بيانات الخادم
- شغِّل محوِّل المزامنة استجابةً لرسالة من خادم تشير إلى أن البيانات المستندة إلى الخادم قد تغيّرت. يتيح لك هذا الخيار إعادة تحميل البيانات من الخادم إلى الجهاز بدون التأثير سلبًا في الأداء أو إهدار عمر البطارية من خلال استطلاع رأي الخادم.
- عند تغيير بيانات الجهاز
- يمكنك تشغيل محوِّل مزامنة عندما تتغير البيانات على الجهاز. يتيح لك هذا الخيار إرسال البيانات المعدّلة من الجهاز إلى أحد الخوادم، وهو مفيد بشكل خاص إذا كنت بحاجة إلى التأكّد من أنّ الخادم يحتوي دائمًا على أحدث بيانات الجهاز. يسهل تطبيق هذا الخيار إذا كنت تخزّن البيانات في موفّر المحتوى. إذا كنت تستخدم موفّر محتوى غير موجز، قد يكون اكتشاف التغييرات في البيانات أكثر صعوبة.
- على فترات منتظمة
- يمكنك تشغيل محوّل مزامنة بعد انتهاء مدة الفاصل الزمني الذي تختاره، أو تشغيله في وقت معيّن كل يوم.
- محتوى مسجّل
- شغِّل محوّل المزامنة استجابةً لإجراء مستخدم. ولتقديم أفضل تجربة للمستخدم، عليك الاعتماد بشكل أساسي على أحد الخيارات الأكثر مبرمَجة. وباستخدام الخيارات التلقائية، يمكنك الحفاظ على موارد البطارية والشبكة.
يصف الجزء المتبقي من هذا الدرس كل خيار بمزيد من التفصيل.
تشغيل محوّل المزامنة عند تغيير بيانات الخادم
إذا كان تطبيقك ينقل البيانات من خادم وكانت بيانات الخادم تتغير بشكل متكرر، يمكنك استخدام
محوِّل مزامنة لإجراء عمليات التنزيل استجابةً للتغييرات في البيانات. لتشغيل محوّل المزامنة، اطلب من الخادم إرسال رسالة خاصة إلى BroadcastReceiver
في تطبيقك. ردًا على هذه الرسالة، يمكنك طلب ContentResolver.requestSync()
للإشارة إلى إطار عمل محوّل المزامنة لتشغيل محوّل المزامنة.
توفر خدمة المراسلة عبر السحابة الإلكترونية من Google (GCM) كلاً من مكوّنات الخادم والجهاز التي تحتاج إليها لكي يعمل نظام المراسلة هذا. إنّ استخدام GCM لتشغيل عمليات النقل أكثر موثوقية وكفاءة من استطلاع خوادم الحالة لمعرفة الحالة. علمًا أنّ استطلاع الرأي يتطلّب Service
أن يكون نشطًا دائمًا، تستخدم خدمة GCM عنصر BroadcastReceiver
يتم تفعيله عند استلام رسالة. أثناء إجراء استطلاعات الرأي
على فترات منتظمة طاقة البطارية حتى في حال عدم توفّر تحديثات، ترسل خدمة GCM
الرسائل عند الحاجة فقط.
ملاحظة: إذا كنت تستخدم خدمة GCM لتشغيل محوّل المزامنة من خلال البث إلى جميع الأجهزة التي تم تثبيت تطبيقك عليها، تذكّر أنّ هذه الأجهزة تستقبل رسالتك في الوقت نفسه تقريبًا. يمكن أن يتسبب هذا الموقف في تشغيل نُسخ متعددة من محوّل المزامنة في الوقت نفسه، ما يؤدي إلى زيادة الحمل على الخادم والشبكة. لتجنّب حدوث هذا الموقف في حالة البث إلى جميع الأجهزة، يجب مراعاة تأجيل بدء محوّل المزامنة لفترة فريدة لكل جهاز.
يوضّح لك مقتطف الرمز التالي كيفية تشغيل requestSync()
استجابةً لرسالة GCM واردة:
Kotlin
... // 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) ... } ... } ... }
Java
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()
عند تغيير الجدول:
Kotlin
// 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) ... } ... }
Java
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()
.
يوضح مقتطف الرمز التالي كيفية جدولة عمليات تشغيل محوّل المزامنة الدورية:
Kotlin
// 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) ... } ... }
Java
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()
استجابةً لنقرة على زر:
Kotlin
// 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) }
Java
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); }