注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術一般,由于喜愛安卓而產生了翻譯的念頭,純屬個人興趣愛好。
原文鏈接: http://developer.android.com/training/sync-adapters/running-sync-adapter.html
在這系列課程中之前的一些課程中,你學習了如何創建一個封裝數據傳輸代碼的同步適配器組件,以及如何添加其它的組件以允許你將同步適配器集成到系統當中。現在已已經擁有了所有你需要的東西,來安裝包含有一個同步適配器的應用,但是沒有任何代碼是去運行同步適配器的。
你應該基于計劃任務的調度或者一些事件的間接結果。例如,你可能希望你的同步適配器運行一個定期的計劃任務,每隔一段時間或每天的一個固定的時間。或者你還希望當設備上的數據發生變化后,執行你的同步適配器。你應該避免將運行同步適配器作為用戶某個行為的直接結果,因為這樣做的話你就無法利用同步適配器框架可以按計劃調度的特性。例如,你應該在UI中避免使用刷新按鈕。
你可以有下列選項來運行你的同步適配器:
當服務器數據變更時:
運行同步適配器以響應來自服務器的消息,指明服務端的數據變化了。這一選項允許從服務器刷新數據到設備上,這一方法避免降低性能,或者由于輪詢服務器所造成的電量損耗。
當設備變化變更時:
當設備上的數據發生變化時,運行同步適配器。這一選項允許你將修改后的數據從設備發送給服務器,如果你需要保證服務器端的數據一致保持最新,那么這一選項非常有用。如果你將數據存儲于你的內容提供器,那么這一選項的實現將會非常直接。如果你使用的是一個空內容提供器,檢測數據的變化可能會比較困難。
當系統發送了一個網絡消息:
當Android系統發送了一個網絡消息來保持TCP/IP連接開啟時,運行同步適配器。這個消息是網絡框架的一個基本部分。使用這一選項是自動運行同步適配器的一個方法。可以考慮配合基于時間間隔的同步適配器一起使用。
每隔固定的時間間隔后:
在你定的時間間隔過了之后,運行同步適配器,或者在每天的固定時間運行它。
按照要求:
運行同步適配器以響應用戶的行為。然而,為了提供最佳的用戶體驗,你應該主要依賴更多自動類型的選項。使用自動化的選項,你可以節省大量的電量以及網絡資源。
本課程的后續部分會詳細介紹每個選項。
一). 當服務器數據變化時,運行同步適配器
如果你的應用從服務器傳輸數據,且服務器的數據頻繁的發生變化,你可以使用一個同步適配器通過下載數據來響應服務端數據的變化。要運行同步適配器,讓服務端向你的應用的
BroadcastReceiver
發送一條特殊的消息。要響應這條消息,可以調用
ContentResolver.requestSync()
方法,來向同步適配器框架發出信號,讓它運行你的同步適配器。
谷歌云消息( Google Cloud Messaging ,GCM)提供了你需要的服務端組件和設備端組件,來讓這一消息提供能夠運行。使用GCM激活數據傳輸比通過向服務器輪詢的方式要更加可靠,也更加有效。因為輪詢需要一個一直處于活躍狀態的 Service ,而GCM使用的 BroadcastReceiver 僅在消息到達時會激活。另外,即使沒有更新的內容,定期的輪詢也會消耗大量的電池電量,而GCM僅在需要時才會發出消息。
Note:
如果你使用GCM,通過一個到所有安裝了你的應用的設備的廣播,來激活你的同步適配器,要記住他們會在同一時間(粗略地)收到你的消息。這回導致在同一時間有多個同步適配器的實例在運行,進而導致服務器和網絡的負載過重。要避免這一情況,你應該考慮讓每個設備的同步適配器啟動的事件有所差異。
下面的代碼展示了如何運行 requestSync() 以響應一個接收到的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 ); ... } ... } ... }
二). 當內容提供器的數據變化時,運行同步適配器
如果你的應用在一個內容提供器中收集數據,并且你希望當你更新提供器的時候一起更新服務器的數據,你可以配置你的同步適配器來讓它自動運行。要做到這一點,你首先應該為內容提供器注冊一個觀察器。當你的內容提供器的數據發生了變化以后,內容提供器框架會調用觀察器。在觀察器中,調用 requestSync() 來告訴框架運行你的同步適配器。
Note:
如果你使用的是一個空的內容提供器,那么你在內容提供器中沒有任何數據,并且 onChange() 方法從來沒有被調用。在這種情況下,你不得不提供你自己的機制來檢測設備數據的變化。這一機制還要負責當數據發生變化時調用 requestSync() 。
為了給你的內容提供器創建一個觀察器,繼承
ContentObserver
類,并且實現
onChange()
方法的幾種形式。在
onChange()
中,調用
requestSync()
來啟動同步適配器。
要注冊觀察器,將它作為參數傳遞給 registerContentObserver() 。在這個調用中,你還要傳遞一個你先要監視的內容URI。內容提供器框架會將這個監視的URI和通過 ContentResolver 方法(如 ContentResolver.insert() )所傳遞過來的修改了你的提供器的URI進行對比,如果匹配上了,那么你所實現的 ContentObserver.onChange() 將會被調用。
下面的代碼片段展示了如何定義一個 ContentObserver ,當表發生變化時調用 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); ... } ... }
三). 在一個網絡消息之后,運行同步適配器
當一個網絡連接可獲得時,Android系統會每隔幾秒發送一條消息來保持TCP/IP連接打開。這一消息也會傳遞到每個應用的 ContentResolver 中。通過調用 setSyncAutomatically() ,你可以在 ContentResolver 收到消息后,運行同步適配器。
當網絡可獲得的時候,通過調度你的同步適配器運行,來保證你的同步適配器在可以獲得網絡時都會被調度。如果不是每次數據變化時就要以數據傳輸來響應,但是又希望自己的數據會被定期地更新,那么可以用這一選項。類似地,如果你不想要給你的同步適配器配置一個定期調度,但你希望經常運行它,你也可以使用這一選項。
由于 setSyncAutomatically() 方法不會禁用 addPeriodicSync() ,你的同步適配器可能會在一小段時間內重復地被激活。如果你想要定期地運行你的同步適配器,你應該禁用 setSyncAutomatically() 。
下面的代碼片段向你展示如何配置你的 ContentResolver 來運行你的同步適配器,響應網絡消息:
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 ); ... } ... }
四). 定期地運行同步適配器
你可以設置一個每次運行期間的間隔時間來定期運行你的同步適配器,或者在每天的固定時間運行,或者兩者都有。定期地運行你的同步適配器可以允許你粗略地觀察你的服務器更新間隔。
同樣地,當你的服務器相對來說比較空閑時,你可以從設備更新數據,方法可以是在夜間定期調用同步適配器。大多數用戶晚上會不關機并對收集充電,所以這一方法是可行的。而且,那個時間設備不會運行其他的任務除了你的同步適配器。如果你使用這個方法的話,你需要注意每臺設備會在略微不同的時間激活數據傳輸。如果所有設備在同一時間運行你的同步適配器,那么你的服務器將很有可能負載過重。
一般來說,如果你的用戶不需要實時更新,但希望定期更新,定期運行會很有用。如果你希望在獲取實時數據和一個更小的同步適配器的效率及不過度使用用戶設備資源這兩者之間進行一個平衡,那么定期執行是一個不錯的選擇。
要定期運行你的同步適配器,調用
addPeriodicSync()
。這樣每隔一段時間,同步適配器就會運行。由于同步適配器框架會考慮其他同步適配器的執行,并嘗試最大化電池效率,間隔時間會動態做出細微調整。同時,如果網絡不可獲得,框架不會運行你的同步適配器。
注意, addPeriodicSync() 方法不會每天某個時間自動運行。要讓你的同步適配器每天某個時間內自動執行,使用一個重復計時器作為激發器。重復計時器的更多細節可以閱讀: AlarmManager 。如果你使用 setInexactRepeating() 方法來設置每天激活的時間具有一些變化,你仍然應該將不同設備的同步適配器的運行時間隨機化,使得它們的執行交錯開來。
addPeriodicSync()
方法不會禁用
setSyncAutomatically()
,所以你可能會在一小段時間內獲取多個同步執行。同樣,僅有一些同步適配器的控制標識會在
addPeriodicSync()
方法中被允許。不允許的標識在該方法的
文檔
中可以查看。
下面的代碼樣例展示了如何定期執行同步適配器:
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 MILLISECONDS_PER_SECOND = 1000L ; 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 * MILLISECONDS_PER_SECOND; // 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, null , SYNC_INTERVAL); ... } ... }
五). 按需求執行同步適配器
運行你的同步適配器來響應一個用戶需求是運行一個同步適配器最不推薦的策略。框架是被特別設計成根據計劃運行同步適配器時最大化保留電量。在數據變化時響應一個同步適配器的同步選項應該有效地使用電量,因為電量是用來提供新數據的。
相比之下,允許用戶按照需求運行同步適配器意味著同步適配器會自己運行,這對電量和網絡來說會導致使用效率的下降。同時,向用戶提供同步,會讓用戶甚至沒有證據表明數據發生變化了以后也請求一個更新,這會導致對電量的低效率使用,一般來說,你的應用應該使用其它信號來激活一個同步更新或者定期地去做它們,而不是依賴于用戶的輸入。
然而,如果你仍然想要按照需求運行同步適配器,將同步適配器標識設置為人為運行的同步適配器,之后調用 ContentResolver.requestSync() 。
使用下列標識來執行按需求的數據傳輸:
強制執行人為的同步更新。同步適配器框架會忽略當前的設置,如被 setSyncAutomatically() 方法設置的標識。
強制同步立即執行。如果你不設置此項,系統可能會在運行同步需求之前等待一小段時間,因為它會嘗試通過將多個請求在一小段時間內調度來嘗試優化電量。
下面的代碼片段將向你展示如何調用 requestSync() 來響應一個按鈕的點擊:
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); }
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
