W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
編寫: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)。
如果我們的應(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);
...
}
...
}
...
}
如果我們的應(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);
...
}
...
}
當(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);
...
}
...
}
我們可以設(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);
...
}
...
}
以響應(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ù)傳輸:
強(qiáng)制執(zhí)行手動(dòng)的同步更新。Sync Adapter 框架會(huì)忽略當(dāng)前的設(shè)置,比如通過(guò) setSyncAutomatically() 方法設(shè)置的標(biāo)識(shí)。
強(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);
}
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: