تشغيل محوّل مزامنة

ملاحظة: ننصح باستخدام 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);
    }