הפעלת מתאם סנכרון

הערה: אנחנו ממליצים על WorkManager הוא הפתרון המומלץ לרוב התרחישים לדוגמה של עיבוד ברקע. כדאי לעיין במדריך לעיבוד ברקע כדי ללמוד איזה פתרון הכי מתאים לכם.

בשיעורים הקודמים בכיתה הזו למדתם איך ליצור רכיב של מתאם סנכרון כולל קוד העברת נתונים, ואיך להוסיף את הרכיבים הנוספים שמאפשרים מחברים את מתאם הסנכרון למערכת. עכשיו יש לך את כל מה שצריך כדי להתקין אפליקציה כולל מתאם סנכרון, אבל אף אחד מהקודים שראית בפועל לא מפעיל את מתאם הסנכרון.

עליך לנסות להפעיל את מתאם הסנכרון על סמך לוח זמנים או כתוצאה עקיפה של אירוע. לדוגמה, ייתכן שמתאם הסנכרון יפעל בתזמון קבוע, לאחר בפרק זמן מסוים או בשעה מסוימת ביום. אולי כדאי גם להפעיל את הסנכרון כאשר יש שינויים בנתונים שמאוחסנים במכשיר. עליך להימנע מהפעלת כתוצאה ישירה מפעולה של משתמש, כי הפעולה הזו לא מקבלת את של יכולת התזמון של מסגרת מתאם הסנכרון. לדוגמה, צריך להימנע הצגת לחצן רענון בממשק המשתמש.

עומדות לרשותכם האפשרויות הבאות להפעלת מתאם הסנכרון:

כאשר נתוני השרת משתנים
להפעיל את מתאם הסנכרון בתגובה להודעה משרת, כדי לציין הנתונים השתנו. כשבוחרים באפשרות הזו ניתן לרענן את הנתונים מהשרת למכשיר מבלי לפגוע בביצועים או לבזבז את חיי הסוללה על ידי בדיקת השרת.
מה קורה כשנתוני המכשיר משתנים
להפעיל מתאם סנכרון כשהנתונים משתנים במכשיר. האפשרות הזו מאפשרת לשלוח נתונים ששונו מהמכשיר לשרת, והוא שימושי במיוחד אם צריך לוודא שלשרת יש תמיד את הנתונים העדכניים ביותר מהמכשיר. האפשרות הזאת פשוטה מאוד אם אתם בפועל מאחסנים נתונים בספק התוכן. אם משתמשים ב-stub ספק תוכן, זיהוי שינויים בנתונים עשוי להיות קשה יותר.
במרווחי זמן קבועים
ניתן להפעיל מתאם סנכרון בסיום פרק הזמן הרצוי, או להפעיל אותו במרווח זמן מסוים בכל יום.
לפי דרישה
מפעילים את מתאם הסנכרון בתגובה לפעולת משתמש. אבל כדי לספק למשתמשים מומלץ להסתמך בעיקר על אחת מהאפשרויות האוטומטיות יותר. על ידי שימוש האפשרויות האוטומטיות חוסכות במשאבי הסוללה והרשת.

בשאר השיעור נתאר בפירוט רב יותר כל אחת מהאפשרויות.

הפעלת מתאם הסנכרון כאשר נתוני השרת משתנים

אם האפליקציה שלכם מעבירה נתונים משרת ונתוני השרת משתנים לעיתים קרובות, אפשר להשתמש ב מתאם סנכרון לביצוע הורדות בתגובה לשינויים בנתונים. כדי להפעיל את מתאם הסנכרון, השרת שולח הודעה מיוחדת ל-BroadcastReceiver באפליקציה שלך. בתגובה להודעה הזו יש להתקשר למספר ContentResolver.requestSync() כדי לסמן שמסגרת מתאם הסנכרון תפעיל את למתאם הסנכרון.

העברת הודעות דרך Google Cloud (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);
            ...
        }
        ...
    }
    ...
}

הפעלת מתאם הסנכרון כשהנתונים של ספק התוכן משתנים

אם האפליקציה אוספת נתונים אצל ספק תוכן, ואתם רוצים לעדכן את השרת בכל פעם מעדכנים את הספק, אפשר להגדיר את האפליקציה כך שתפעיל את מתאם הסנכרון באופן אוטומטי. לבצע תרשמו צופה עבור ספק התוכן. כאשר הנתונים אצל ספק התוכן משתנה, ה-framework של ספק התוכן קורא לצופה. מתוך הצופה, התקשרו requestSync() כדי להורות ל-framework לפעול מתאם הסנכרון.

הערה: אם אתם משתמשים בספק תוכן stub, אין לכם נתונים ב- ספק התוכן, ו-onChange() אף פעם לא התקשרו. במקרה כזה, צריך לספק מנגנון משלכם לזיהוי שינויים הנתונים במכשיר. המנגנון הזה אחראי גם לביצוע קריאה requestSync() כשהנתונים משתנים.

כדי ליצור צופה עבור ספק התוכן, מרחיבים את הכיתה ContentObserver ולהטמיע את שתי הצורות שלו onChange(). לחשבון onChange(), שיחה requestSync() כדי להפעיל את מתאם הסנכרון.

כדי לרשום את הצופה, צריך להעביר אותו כארגומנט בקריאה אל registerContentObserver() לחשבון בשיחה הזו, צריך גם להעביר ב-URI של תוכן את הנתונים שבהם רוצים לצפות. התוכן ספק framework משווה את ה-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() לוח הזמנים הזה קובע כך שיפעל לאחר פרק זמן מסוים. מכיוון שמסגרת מתאם הסנכרון נדרש להביא בחשבון פעולות אחרות של מתאם סנכרון ומנסה למקסם את יעילות הסוללה, הזמן שחלף עשוי להשתנות בכמה שניות. כמו כן, ה-framework לא יפעיל את מתאם הסנכרון הרשת לא זמינה.

שימו לב שב-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);
    }