Android 執(zhí)行 Sync Adpater

2018-08-02 17:44 更新

編寫:jdneo - 原文:http://developer.android.com/training/sync-adapters/running-sync-adapter.html

在本節(jié)課之前,我們已經(jīng)學(xué)習(xí)了如何創(chuàng)建一個(gè)封裝了數(shù)據(jù)傳輸代碼的 Sync Adapter 組件,以及如何添加其它的組件,使得我們可以將 Sync Adapter 集成到系統(tǒng)當(dāng)中?,F(xiàn)在我們已經(jīng)擁有了所有部件,來(lái)安裝一個(gè)包含有 Sync Adapter 的應(yīng)用了,但是這里還沒(méi)有任何代碼是負(fù)責(zé)去運(yùn)行 Sync Adapter。

執(zhí)行 Sync Adapter 的時(shí)機(jī),一般應(yīng)該基于某個(gè)計(jì)劃任務(wù)或者一些事件的間接結(jié)果。例如,我們可能希望 Sync Adapter 以一個(gè)定期計(jì)劃任務(wù)的形式運(yùn)行(比如每隔一段時(shí)間或者在每天的一個(gè)固定時(shí)間運(yùn)行)?;蛘咭部赡芟M?dāng)設(shè)備上的數(shù)據(jù)發(fā)生變化后,執(zhí)行 Sync Adapter。我們應(yīng)該避免將運(yùn)行 Sync Adapter 作為用戶某個(gè)行為的直接結(jié)果,因?yàn)檫@樣做的話我們就無(wú)法利用 Sync Adapter 框架可以按計(jì)劃調(diào)度的特性。例如,我們應(yīng)該在 UI 中避免使用刷新按鈕。

下列情況可以作為運(yùn)行 Sync Adapter 的時(shí)機(jī):

當(dāng)服務(wù)端數(shù)據(jù)變更時(shí):

當(dāng)服務(wù)端發(fā)送消息告知服務(wù)端數(shù)據(jù)發(fā)生變化時(shí),運(yùn)行 Sync Adapter 以響應(yīng)這一來(lái)自服務(wù)端的消息。這一選項(xiàng)允許從服務(wù)器更新數(shù)據(jù)到設(shè)備上,該方法可以避免由于輪詢服務(wù)器所造成的執(zhí)行效率下降,或者電量損耗。

當(dāng)設(shè)備的數(shù)據(jù)變更時(shí):

當(dāng)設(shè)備上的數(shù)據(jù)發(fā)生變化時(shí),運(yùn)行 Sync Adapter。這一選項(xiàng)允許我們將修改后的數(shù)據(jù)從設(shè)備發(fā)送給服務(wù)器。如果需要保證服務(wù)器端一直擁有設(shè)備上最新的數(shù)據(jù),那么這一選項(xiàng)非常有用。如果我們將數(shù)據(jù)存儲(chǔ)于 Content Provider,那么這一選項(xiàng)的實(shí)現(xiàn)將會(huì)非常直接。如果使用的是一個(gè) Stub Content Provider,檢測(cè)數(shù)據(jù)的變化可能會(huì)比較困難。

當(dāng)系統(tǒng)發(fā)送了一個(gè)網(wǎng)絡(luò)消息:

當(dāng) Android 系統(tǒng)發(fā)送了一個(gè)網(wǎng)絡(luò)消息來(lái)保持 TCP/IP 連接開啟時(shí),運(yùn)行 Sync Adapter。這個(gè)消息是網(wǎng)絡(luò)框架(Networking Framework)的一個(gè)基本部分??梢詫⑦@一選項(xiàng)作為自動(dòng)運(yùn)行 Sync Adapter 的一個(gè)方法。另外還可以考慮將它和基于時(shí)間間隔運(yùn)行 Sync Adapter 的策略結(jié)合起來(lái)使用。

每隔一定時(shí)間:

可以每隔一段指定的時(shí)間間隔后,運(yùn)行 Sync Adapter,或者在每天的固定時(shí)間運(yùn)行它。

根據(jù)需求:

運(yùn)行 Sync Adapter 以響應(yīng)用戶的行為。然而,為了提供最佳的用戶體驗(yàn),我們應(yīng)該主要依賴那些更加自動(dòng)式的選項(xiàng)。使用自動(dòng)式的選項(xiàng),可以節(jié)省大量的電量以及網(wǎng)絡(luò)資源。

本課程的后續(xù)部分會(huì)詳細(xì)介紹每個(gè)選項(xiàng)。

當(dāng)服務(wù)器數(shù)據(jù)變化時(shí),運(yùn)行 Sync Adapter

如果我們的應(yīng)用從服務(wù)器傳輸數(shù)據(jù),且服務(wù)器的數(shù)據(jù)會(huì)頻繁地發(fā)生變化,那么可以使用一個(gè) Sync Adapter 通過(guò)下載數(shù)據(jù)來(lái)響應(yīng)服務(wù)端數(shù)據(jù)的變化。要運(yùn)行 Sync Adapter,我們需要讓服務(wù)端向應(yīng)用的 BroadcastReceiver 發(fā)送一條特殊的消息。為了響應(yīng)這條消息,可以調(diào)用 ContentResolver.requestSync() 方法,向 Sync Adapter 框架發(fā)出信號(hào),讓它運(yùn)行 Sync Adapter。

谷歌云消息(Google Cloud Messaging,GCM)提供了我們需要的服務(wù)端組件和設(shè)備端組件,來(lái)讓上述消息系統(tǒng)能夠運(yùn)行。使用 GCM 觸發(fā)數(shù)據(jù)傳輸比通過(guò)向服務(wù)器輪詢的方式要更加可靠,也更加有效。因?yàn)檩喸冃枰粋€(gè)一直處于活躍狀態(tài)的 Service,而 GCM 使用的 BroadcastReceiver 僅在消息到達(dá)時(shí)會(huì)被激活。另外,即使沒(méi)有更新的內(nèi)容,定期的輪詢也會(huì)消耗大量的電池電量,而 GCM 僅在需要時(shí)才會(huì)發(fā)出消息。

Note:如果我們使用 GCM,將廣播消息發(fā)送到所有安裝了我們的應(yīng)用的設(shè)備,來(lái)激活 Sync Adapter。要記住他們會(huì)在同一時(shí)間(粗略地)收到我們的消息。這會(huì)導(dǎo)致在同一時(shí)段內(nèi)有多個(gè) Sync Adapter 的實(shí)例在運(yùn)行,進(jìn)而導(dǎo)致服務(wù)器和網(wǎng)絡(luò)的負(fù)載過(guò)重。要避免這一情況,我們應(yīng)該考慮為不同的設(shè)備設(shè)定不同的 Sync Adapter 來(lái)延遲啟動(dòng)時(shí)間。

下面的代碼展示了如何通過(guò) requestSync() 響應(yīng)一個(gè)接收到的 GCM 消息:

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(ACCOUNT, AUTHORITY, null);
            ...
        }
        ...
    }
    ...
}

當(dāng) Content Provider 的數(shù)據(jù)變化時(shí),運(yùn)行 Sync Adapter

如果我們的應(yīng)用在一個(gè) Content Provider 中收集數(shù)據(jù),并且希望當(dāng)我們更新了 Content Provider 的時(shí)候,同時(shí)更新服務(wù)器的數(shù)據(jù),我們可以配置 Sync Adapter 來(lái)讓它自動(dòng)運(yùn)行。要做到這一點(diǎn),首先應(yīng)該為 Content Provider 注冊(cè)一個(gè) Observer。當(dāng) Content Provider 的數(shù)據(jù)發(fā)生了變化之后,Content Provider 框架會(huì)調(diào)用 Observer。在 Observer 中,調(diào)用 requestSync() 來(lái)告訴框架現(xiàn)在應(yīng)該運(yùn)行 Sync Adapter 了。

Note:如果我們使用的是一個(gè) Stub Content Provider,那么在 Content Provider 中不會(huì)有任何數(shù)據(jù),并且不會(huì)調(diào)用 onChange() 方法。在這種情況下,我們不得不提供自己的某種機(jī)制來(lái)檢測(cè)設(shè)備數(shù)據(jù)的變化。這一機(jī)制還要負(fù)責(zé)在數(shù)據(jù)發(fā)生變化時(shí)調(diào)用 requestSync()。

為了給 Content Provider 創(chuàng)建一個(gè) Observer,繼承 ContentObserver 類,并且實(shí)現(xiàn) onChange() 方法的兩種形式。在 onChange() 中,調(diào)用 requestSync() 來(lái)啟動(dòng) Sync Adapter。

要注冊(cè) Observer,需要將它作為參數(shù)傳遞給 registerContentObserver()。在該方法中,我們還要傳遞一個(gè)我們想要監(jiān)視的 Content URI。Content Provider 框架會(huì)將這個(gè)需要監(jiān)視的 URI 與其它一些 Content URIs 進(jìn)行比較,這些其它的 Content URIs 來(lái)自于 ContentResolver 中那些可以修改 Provider 的方法(如 ContentResolver.insert())所傳入的參數(shù)。如果出現(xiàn)了變化,那么我們所實(shí)現(xiàn)的 ContentObserver.onChange() 將會(huì)被調(diào)用。

下面的代碼片段展示了如何定義一個(gè) ContentObserver,它在表數(shù)據(jù)發(fā)生變化后調(diào)用 requestSync()

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 mUri;
    // 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(ACCOUNT, 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
        mUri = 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(mUri, true, observer);
        ...
    }
    ...
}

在一個(gè)網(wǎng)絡(luò)消息之后,運(yùn)行 Sync Adapter

當(dāng)可以獲得一個(gè)網(wǎng)絡(luò)連接時(shí),Android 系統(tǒng)會(huì)每隔幾秒發(fā)送一條消息來(lái)保持 TCP/IP 連接處于開啟狀態(tài)。這一消息也會(huì)傳遞到每個(gè)應(yīng)用的 ContentResolver 中。通過(guò)調(diào)用 setSyncAutomatically(),我們可以在 ContentResolver 收到消息后,運(yùn)行 Sync Adapter。

每當(dāng)網(wǎng)絡(luò)消息被發(fā)送后運(yùn)行 Sync Adapter,通過(guò)這樣的調(diào)度方式可以保證每次運(yùn)行 Sync Adapter 時(shí)都可以訪問(wèn)網(wǎng)絡(luò)。如果不是每次數(shù)據(jù)變化時(shí)就要以數(shù)據(jù)傳輸來(lái)響應(yīng),但是又希望自己的數(shù)據(jù)會(huì)被定期地更新,那么我們可以用這一選項(xiàng)。類似地,如果我們不想要定期執(zhí)行 Sync Adapter,但希望經(jīng)常運(yùn)行它,我們也可以使用這一選項(xiàng)。

由于 setSyncAutomatically() 方法不會(huì)禁用 addPeriodicSync(),所以 Sync Adapter 可能會(huì)在一小段時(shí)間內(nèi)重復(fù)地被觸發(fā)激活。如果我們想要定期地運(yùn)行 Sync Adapter,應(yīng)該禁用 setSyncAutomatically()。

下面的代碼片段展示如何配置 ContentResolver,利用它來(lái)響應(yīng)網(wǎng)絡(luò)消息,從而運(yùn)行 Sync Adapter:

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";
    // 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 automatic syncing for the default account and authority
        mResolver.setSyncAutomatically(ACCOUNT, AUTHORITY, true);
        ...
    }
    ...
}

定期地運(yùn)行Sync Adapter

我們可以設(shè)置一個(gè)在運(yùn)行之間的時(shí)間間隔來(lái)定期運(yùn)行 Sync Adapter,或者在每天的固定時(shí)間運(yùn)行它,還可以兩種策略同時(shí)使用。定期地運(yùn)行 Sync Adapter 可以讓服務(wù)器的更新間隔大致保持一致。

同樣地,當(dāng)服務(wù)器相對(duì)來(lái)說(shuō)比較空閑時(shí),我們可以通過(guò)在夜間定期調(diào)用 Sync Adapter,把設(shè)備上的數(shù)據(jù)上傳到服務(wù)器。大多數(shù)用戶在晚上不會(huì)關(guān)機(jī),并為手機(jī)充電,所以這一方法是可行的。而且,通常來(lái)說(shuō),設(shè)備不會(huì)在深夜運(yùn)行除了 Sync Adapter 之外的其他的任務(wù)。然而,如果我們使用這個(gè)方法的話,我們需要注意讓每臺(tái)設(shè)備在略微不同的時(shí)間觸發(fā)數(shù)據(jù)傳輸。如果所有設(shè)備在同一時(shí)間運(yùn)行我們的 Sync Adapter,那么我們的服務(wù)器和移動(dòng)運(yùn)營(yíng)商的網(wǎng)絡(luò)將很有可能負(fù)載過(guò)重。

一般來(lái)說(shuō),當(dāng)我們的用戶不需要實(shí)時(shí)更新,而希望定期更新時(shí),使用定期運(yùn)行的策咯會(huì)很有用。如果我們希望在數(shù)據(jù)的實(shí)時(shí)性和 Sync Adapter 的資源消耗之間進(jìn)行一個(gè)平衡,那么定期執(zhí)行是一個(gè)不錯(cuò)的選擇。

要定期運(yùn)行我們的 Sync Adapter,調(diào)用 addPeriodicSync()。這樣每隔一段時(shí)間,Sync Adapter 就會(huì)運(yùn)行。由于 Sync Adapter 框架會(huì)考慮其他 Sync Adapter 的執(zhí)行,并嘗試最大化電池效率,所以間隔時(shí)間會(huì)動(dòng)態(tài)地進(jìn)行細(xì)微調(diào)整。同時(shí),如果當(dāng)前無(wú)法獲得網(wǎng)絡(luò)連接,框架不會(huì)運(yùn)行我們的 Sync Adapter。

注意,addPeriodicSync() 方法不會(huì)讓 Sync Adapter 每天在某個(gè)時(shí)間自動(dòng)運(yùn)行。要讓我們的 Sync Adapter 在每天的某個(gè)時(shí)刻自動(dòng)執(zhí)行,可以使用一個(gè)重復(fù)計(jì)時(shí)器作為觸發(fā)器。重復(fù)計(jì)時(shí)器的更多細(xì)節(jié)可以閱讀:AlarmManager。如果我們使用 setInexactRepeating() 方法設(shè)置了一個(gè)每天的觸發(fā)時(shí)刻會(huì)有粗略變化的觸發(fā)器,我們?nèi)匀粦?yīng)該將不同設(shè)備 Sync Adapter 的運(yùn)行時(shí)間隨機(jī)化,使得它們的執(zhí)行交錯(cuò)開來(lái)。

addPeriodicSync() 方法不會(huì)禁用 setSyncAutomatically(),所以我們可能會(huì)在一小段時(shí)間內(nèi)產(chǎn)生多個(gè) Sync Adapter 的運(yùn)行實(shí)例。另外,僅有一部分 Sync Adapter 的控制標(biāo)識(shí)可以在調(diào)用 addPeriodicSync() 時(shí)使用。不被允許的標(biāo)識(shí)在該方法的文檔中可以查看。

下面的代碼樣例展示了如何定期執(zhí)行 Sync Adapter:

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(
                ACCOUNT,
                AUTHORITY,
                Bundle.EMPTY,
                SYNC_INTERVAL);
        ...
    }
    ...
}

按需求執(zhí)行 Sync Adapter

以響應(yīng)用戶請(qǐng)求的方式運(yùn)行 Sync Adapter 是最不推薦的策略。要知道,該框架是被特別設(shè)計(jì)的,它可以讓 Sync Adapter 在根據(jù)某個(gè)調(diào)度規(guī)則運(yùn)行時(shí),能夠盡量最高效地使用手機(jī)電量。顯然,在數(shù)據(jù)改變的時(shí)候執(zhí)行同步可以更有效的使用手機(jī)電量,因?yàn)殡娏慷枷脑诹烁滦碌臄?shù)據(jù)上。

相比之下,允許用戶按照自己的需求運(yùn)行 Sync Adapter 意味著 Sync Adapter 會(huì)自己運(yùn)行,這將無(wú)法有效地使用電量和網(wǎng)絡(luò)資源。如果根據(jù)需求執(zhí)行同步,會(huì)誘導(dǎo)用戶即便沒(méi)有證據(jù)表明數(shù)據(jù)發(fā)生了變化也請(qǐng)求一個(gè)更新,這些無(wú)用的更新會(huì)導(dǎo)致對(duì)電量的低效率使用。一般來(lái)說(shuō),我們的應(yīng)用應(yīng)該使用其它信號(hào)來(lái)觸發(fā)一個(gè)同步更新或者讓它們定期地去執(zhí)行,而不是依賴于用戶的輸入。

不過(guò),如果我們?nèi)匀幌胍凑招枨筮\(yùn)行 Sync Adapter,可以將 Sync Adapter 的配置標(biāo)識(shí)設(shè)置為手動(dòng)執(zhí)行,之后調(diào)用 ContentResolver.requestSync() 來(lái)觸發(fā)一次更新。

通過(guò)下列標(biāo)識(shí)來(lái)執(zhí)行按需求的數(shù)據(jù)傳輸:

SYNC_EXTRAS_MANUAL

強(qiáng)制執(zhí)行手動(dòng)的同步更新。Sync Adapter 框架會(huì)忽略當(dāng)前的設(shè)置,比如通過(guò) setSyncAutomatically() 方法設(shè)置的標(biāo)識(shí)。

SYNC_EXTRAS_EXPEDITED

強(qiáng)制同步立即執(zhí)行。如果我們不設(shè)置此項(xiàng),系統(tǒng)可能會(huì)在運(yùn)行同步請(qǐng)求之前等待一小段時(shí)間,因?yàn)樗鼤?huì)嘗試將一小段時(shí)間內(nèi)的多個(gè)請(qǐng)求集中在一起調(diào)度,目的是為了優(yōu)化電量的使用。

下面的代碼片段將展示如何調(diào)用 requestSync() 來(lái)響應(yīng)一個(gè)按鈕點(diǎn)擊事件:

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 dummy 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);
    }


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)