使用GCM網絡管理工具優化電池使用
通過 GCM網絡管理工具 我們可以注冊用于執行網絡任務的服務,其中每一個任務都是一件獨立的工作。GCM的API幫助進行任務的調度,并讓Google Play服務在系統中批量進行網絡操作。
其API還有助于簡化網絡操作模式,比如等待網絡連接、進行網絡重連與補償等。總的來說,GCM網絡管理工具通過提供一個簡潔明快的網絡請求調度API來幫助開發者在網絡相關的問題上少費心思。
電量和網絡操作
在深入GCM與其優勢之前,我們先聊一聊與網絡請求相關的電池使用問題,這有助于我們認識批量處理網絡操作的重要性。
下面是Android通訊模塊(radio)的狀態機:
這個圖還是挺清晰的。我想通過這個圖表告訴大家喚醒通訊模塊是處理網絡連接時最耗電的操作,也就是說,網絡操作是電池最大的消耗者。
雖然一個網絡操作不可能耗盡電池,但喚醒通訊模塊所發送的單個請求數目相當可觀。如果網絡請求是單獨發送的,那么設備就會被持續喚醒,通訊模塊也會保持開啟狀態。設備持續喚醒無法休眠會導致電量大幅消耗。(就如人睡不著覺一樣)
下面的示意圖說明了一個叫PhotoGallery的圖片獲取APP的單次網絡請求,這個APP也是我們的暢銷書 Android Programming Book 中的一個示例APP。
看起來單次網絡操作也沒啥。網絡模塊被喚醒了,但這只是單次請求。
而多次請求的示意圖是這樣的:
現在我們有了多次網絡請求,注意觀察網絡模塊在每次請求時都被喚醒,這會快速消耗電量,讓使用者不爽。
在這里我們可以通過批量進行網絡操作來優化電池使用,因為這可以減少啟動網絡模塊所消耗的電量。如果網絡模塊生命周期中最耗電的部分是啟動部分,那么我們可以在多次請求中只喚醒一次。如果我們所請求的數據不需要馬上獲取,而是在未來的某個時間點需要用到,那這種方式還算可行。
批量網絡請求的示意圖如下:
現在網絡模塊只會喚醒一次,省了不少電。
有時你的確馬上需要用到網絡請求的數據,如打游戲或者發短信的情況。這些情況下,還是需要發送單次網絡請求,不過要記住這對電池、網絡狀態和設備重啟并無好處。
GcmTaskService
既然我們已經看到了批量進行網絡請求對優化電池使用能起到不小的作用,現在讓我們在應用中實現GCM網絡管理工具。
首先,在build.gradle文件中添加GCM網絡管理工具的依賴。
dependencies {
...
compile 'com.google.android.gms:play-services-gcm:8.1.0'
...
}
切記只需依賴Google Play Services的GCM部分,不然你需要依賴所有的Google Play Service,而其中大部分是用不到的方法。
下一步,我們需要在AndroidManifest中聲明一個新的Service:
<service android:name=".CustomService"
android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.gcm.ACTION_TASK_READY"/>
</intent-filter>
</service>
這里Service的name屬性是繼承了GcmTaskService的類的類名,GcmTaskService是與GCM網絡管理工具交互的核心類。這個Service會處理任務的執行,這里的任務是任何網絡工作的一部分。上面代碼中action是ACTION_TASK_READY的intent-filter是用于接收GCM中的調度工具所發送的某任務可被執行的通知。
下一步,我們寫一個CustomService.java繼承GcmTaskService:
public class CustomService extends GcmTaskService {
...
}
這個類會處理所有執行中的任務。由于繼承了GcmTaskService,我們需要在CustomService中重寫onRunTask方法。
@Override
public int onRunTask(TaskParams taskParams) {
Log.i(TAG, "onRunTask");
switch (taskParams.getTag()) {
case TAG_TASK_ONEOFF_LOG:
Log.i(TAG, TAG_TASK_ONEOFF_LOG);
// 進行邏輯處理
return GcmNetworkManager.RESULT_SUCCESS;
case TAG_TASK_PERIODIC_LOG:
Log.i(TAG, TAG_TASK_PERIODIC_LOG);
// 進行邏輯處理
return GcmNetworkManager.RESULT_SUCCESS;
default:
return GcmNetworkManager.RESULT_FAILURE;
}
}
當某一個任務需要執行時,這個方法就會被調用。我們在這里要檢測作為參數傳入的TaskParams的tag,一個tag單獨映射到一個被調度的任務。一般來說我們需要在case代碼塊中添加網絡操作或邏輯操作,但在這里我只打了一個tag。
GcmNetworkManager
現在我們寫好了GcmTaskService,我們需要獲取到GcmNetworkManager對象的引用,并通過它調度新的任務讓剛才寫的Service執行。
private GcmNetworkManager mGcmNetworkManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
mGcmNetworkManager = GcmNetworkManager.getInstance(this); }
GcmNetworkManager對象是用來調度網絡任務的,所以我們可以在onCreate中獲取其引用并存為成員變量。或者,你也可以在需要進行網絡調度時再獲取實例(餓漢)。
任務調度
一個單獨的任務相當于一件等待執行的工作,可以分為兩種類型:一次性的(OneoffTask)和周期性的(PeriodicTask)。
我們將執行區間(window of execution)傳給任務,而后調度工具就會計算確切的執行時間。因為這些任務不需要立即執行,調度器會將許多網絡請求捆綁執行來節省電量。
調度器會自己判斷網絡連接情況、網絡任務與網絡負荷,如果這些因素都正常,調度工具會等待至給定區間的結束點。
下面是調度一個單次任務的代碼:
Task task = new OneoffTask.Builder()
.setService(CustomService.class)
.setExecutionWindow(0, 30)
.setTag(LogService.TAG_TASK_ONEOFF_LOG)
.setUpdateCurrent(false)
.setRequiredNetwork(Task.NETWORK_STATE_CONNECTED)
.setRequiresCharging(false)
.build();
mGcmNetworkManager.schedule(task);
通過使用Builder模式,我們給定了任務的所有參數:
- Service: 用于控制任務的確切GcmTaskService。這樣我們可以在后面停止它。
- ExecutionWindow:任務執行的時間區間。第一個參數是最低時間,第二個參數是最高時間(都是以秒為單位)。這個參數是強制的。
- Tag:這里通過tag來在onRunTask方法中識別哪一個任務正在執行。每個tag都應該是唯一的,長度上限是100位。
- UpdateCurrent:判斷該任務是否要覆蓋之前存在的有相同tag的任務。默認情況下這個參數是false,也就是不覆蓋。
- RequiredNetwork:設置一個任務執行所需的網絡狀態。在這里如果無網絡連接,任務就不會被執行。
- RequiresCharging:任務執行是否需要設備處在充電狀態。
都設置好之后,任務就被構建好了,通過GcmNetworkManager實例進行調度,然后在某個時間執行。
而調度一個周期性任務要這樣做:
Task task = new PeriodicTask.Builder()
.setService(CustomService.class)
.setPeriod(30)
.setFlex(10)
.setTag(LogService.TAG_TASK_PERIODIC_LOG)
.setPersisted(true)
.build();
mGcmNetworkManager.schedule(task);
和上面的代碼區別不大,主要的區別有這些:
- Period:指定該任務至少要每個時間區間執行一次,時間區間以秒為單位作為參數傳入。默認情況下你無法控制任務是在時間區間內的哪個具體時間點執行。這個參數是強制的。
- Flex:指定該任務需要在距離結束點多長時間之內執行。在這里Period是30,Flex是10,那么任務就會在20-30秒的區間內執行。
- Persisted:判斷該任務在重啟過程后是否保留。默認情況下周期性任務的這個參數都是true,而一次性任務沒有這個參數。如果是true則需要Receive Boot Completed權限,不然無效。
看到GCM網絡管理工具的API有多么的強大了吧?創建和調度代碼簡直不能更簡單。
取消任務
我們已經看到了如何調度任務,所以還需要看一下如何取消任務。正在執行的任務是無法被取消的,但我們可以取消還未被執行的任務。
你可以直接取消給定GcmTaskService的所有任務:
mGcmNetworkManager.cancelAllTasks(CustomService.class);
你也可以通過給出tag和GcmTaskService來取消一個具體任務:
mGcmNetworkManager.cancelTask(
CustomService.TAG_TASK_PERIODIC_LOG,
CustomService.class
);
不管怎樣,要記住正在執行的任務無法取消。
Google Play服務
使用網絡管理工具調度任務的起始點就是剛才使用到的schedule方法,而這需要Google Play服務。為了正常使用其服務,我們需要進行如下檢測:
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (resultCode == ConnectionResult.SUCCESS) {
mGcmNetworkManager.schedule(task);
} else {
// 以其他方式處理task
}
如果Google Play服務不可使用,GcmNetworkManager會靜默失效,這就是為什么需要提前檢測。
類似地,當Google Play服務或是客戶端APP被升級,所有的已調度的任務都會失效。為了避免丟失當前已調度的任務,GcmNetworkManager會調用GcmTaskService(這里是CustomService)的onInitializeTasks()方法。這個方法是用來重新調度任務的,對周期性任務尤其常見。下面是示例:
@Override
public void onInitializeTasks() {
super.onInitializeTasks();
// 重新調度已失效的任務
}
總結
我們已經深入研究了一下GCM網絡管理工具,以及如何用它來節省電量,優化網絡表現,并使用Task進行批量工作。下次你需要在某個時間進行一些網絡操作的時候,不妨考慮使用GCM網絡管理工具使網絡訪問更加精簡且不失健壯。
如果你覺得這篇文章還算因吹斯挺,打算學習更多有關Android Marshmallow或其他社區工具的特點,不妨查看下面的鏈接(英文):