WorkManager完全解析

前言

之前用IntentService寫了一個輪詢框架,但是並不是很好,後面一直想找個其他方式來改寫一下,找了好多資料發現了WorkManager,WorkManager是google提供的一個非常優秀的後臺任務管理框架,對於提交給WorkManager的任務可以立即執行也可以在適當的時候執行,可以執行一次也可以根據條件循環執行多次,並且對於多個任務WorkManager可以管理任務的執行路徑先後執行順序等。本文先介紹WorkManager的用法,然後使用WorkManager來重構之前的輪詢框架。

什麼是Workmanager

WorkManager是google提供的異步執行任務的管理框架,會根據手機的API版本和應用程序的狀態來選擇適當的方式執行任務。當應用在運行的時候會在應用的進程中開一條線程來執行任務,當退出應用時,WorkManager會選擇根據設備的API版本使用適合的算法調用JobScheduler或者Firebase JobDispatcher,或者AlarmManager來執行任務。如下圖:
在這裏插入圖片描述
由上圖可以看出WorkManager管理任務執行時底層還是調用了JobScheduler,JobDispatcher,AlarmManager,不過WorkManager會根據Android系統的API和應用的運行狀態來選擇合適的方式執行,並不用我們自己去考慮應用複雜的運行狀態來進行選擇使用JobScheduler還是JobDispatcher或者AlarmManager調用規則如下圖:
在這裏插入圖片描述

WorkManager在項目中配置

使用WorkManager需要gradle依賴,進行一下簡單配置就可以使用了。找到項目中的app/build.gradle目錄下在上面加上下面的依賴。

dependencies {
    // 其他依賴配置
    def work_version = "1.0.0-beta02"
    implementation "android.arch.work:work-runtime:$versions.work"
}

可以在https://developer.android.com/topic/libraries/architecture/adding-components#workmanager獲取當前的work-runtime版本並且設置正確的版本號。

WorkManager主要類及使用

如下圖給出了WorkManager中主要的類以及關係圖,黃色區域是最主要的三個類,構成了WorkManager的基本框架,紅色部分和綠色部分是關聯的黃色部分的具體實現或者類裏面包含一些規則或數據。
在這裏插入圖片描述
1、Worker處理要執行的任務的具體邏輯。

2、WorkerRequest代表一個獨立的可以執行的任務,以及任務執行時的條件和規則,比如說任務執行一次還是多次以及任務的觸發條件是什麼任務有什麼約束等。

3、WorkManager提供隊列將要執行的WorkerRequest放到隊列中管理和執行。
如下圖,三個主要類的關係:
在這裏插入圖片描述
下面分別介紹三個類的作用和使用方法。

Worker

Worker是一個抽象類,當有一個要執行的任務的時候可以繼承Worker類,重寫doWork()方法在doWork()方法中實現具體任務的邏輯。

public class MyWorker extends Worker {
    public MyWorker(
            @NonNull Context appContext,
            @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);
    }
    @NonNull
    @Override
    public Worker.Result doWork() {

        Context applicationContext = getApplicationContext();

        try {

            Bitmap picture = BitmapFactory.decodeResource(
                    applicationContext.getResources(),
                    R.drawable.test);
                    
            return Worker.Result.SUCCESS;
            
        } catch (Throwable throwable) {
        
            return Worker.Result.FAILURE;
            
        }
    }
}

在上面的MyWorker實例中,繼承了Worker 並且重寫了doWork()方法,需要注意的是doWork()方法是有返回值Worker.Result的,可以在任務執行成功是返回Worker.Result.SUCCESS,在任務執行出現異常時返回Worker.Result.FAILURE
doWork()方法的返回值主要有三種
1、Worker.Result.SUCCESS 表示任務執行成功

2、Worker.Result.FAILURE 表示任務執行失敗

3、Worker.Result.RETRY 通知WorkManager之後再嘗試執行該任務

WorkRequest

WorkRequest要指定執行任務的Worker,也可以給WorkRequest加一些規則,比如說什麼時候執行任務,任務執行一次還是多次,每一個WorkRequest都有一個自動產生的唯一ID,可以根據唯一ID獲取對應任務的狀態以及是否取消對應的任務。如下圖WorkRequest有兩個實現類如下圖:
在這裏插入圖片描述
1、OneTimeWorkRequest 任務只執行一次

OneTimeWorkRequest myWorkRequest =
        new OneTimeWorkRequest.Builder(MyWorker.class)
    .build();
    //將上面定義的MyWorker加入到OneTimeRequest.Builder方法中
WorkManager.getInstance().enqueue(myWorkRequest);//獲取WorkManager實例並將WorkRequest進隊

2、PeriodicWorkRequest
PeriodicWorkRequest重複執行任務,直到被取消才停止。首次執行是任務提交後立即執行或者滿足所給的 Constraints條件。以後執行都會根據所給的時間間隔來執行。注意任務的執行可能會有延時,因爲WorkManager會根據OS的電量進行優化。
假如設置的Periodic Work是24小時執行一次,有可能根據電池優化策略執行的過程如下:

     1     | Jan 01, 06:00 AM
     2     | Jan 02, 06:24 AM
     3     | Jan 03, 07:15 AM
     4     | Jan 04, 08:00 AM
     5     | Jan 05, 08:00 AM
     6     | Jan 06, 08:02 AM

由上面的執行時間可以看出,PeriodicWorkRequest並不是準確的按照24小時來執行,會有一定的時間延遲。因此如果需要準確的間隔時間來執行任務的話不能使用PeriodicWorkRequest。

Constraints constraints = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();
PeriodicWorkRequest build = new PeriodicWorkRequest.Builder(MyWorker.class, 25, TimeUnit.MILLISECONDS)
           .addTag(TAG)
           .setConstraints(constraints)
           .build();

WorkManager instance = WorkManager.getInstance();
if (instance != null) {
          instance.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, build);
}

Constraints

可以給任務加一些運行的Constraints條件,比如說當設備空閒時或者正在充電或者連接WiFi時執行任務。

Constraints myConstraints = new Constraints.Builder()
    .setRequiresDeviceIdle(true)
    .setRequiresCharging(true)
    // Many other constraints are available, see the
    // Constraints.Builder reference
     .build();
OneTimeWorkRequest myWork =
                new OneTimeWorkRequest.Builder(CompressWorker.class)
     .setConstraints(myConstraints)
     .build();

WorkManager

WorkManager管理WorkRequest隊列。並根據設備和其他條件選擇執行的具體方式。在大部分情況在如果沒有給隊列加Contraints,WorkManager會立即執行任務。

WorkManager.getInstance().enqueue(myWork);

如果要檢查任務的執行狀態可以通過獲取WorkInfo,WorkInfo在WorkManager裏面的LiveData<WorkInfo>中。下面是判斷任務是否結束的方式。

WorkManager.getInstance().getWorkInfoByIdLiveData(myWork.getId())
    .observe(lifecycleOwner, workInfo -> {
        // Do something with the status
        if (workInfo != null && workInfo.getState().isFinished()) {
            // ...
        }
    });

取消任務執行

通過任務的ID可以獲取任務從而取消任務。任務ID可以從WorkRequest中獲取。

UUID compressionWorkId = compressionWork.getId();
WorkManager.getInstance().cancelWorkById(compressionWorkId);

注意並不是所有的任務都可以取消,當任務正在執行時是不能取消的,當然任務執行完成了,取消也是意義的,也就是說當任務加入到ManagerWork的隊列中但是還沒有執行時纔可以取消。

WorkManager多任務調度

有時候可能有很多任務需要執行,並且這些任務之前可能有先後順序或者某些依賴關係,WorkManager提供了很好的方式。
1、先後順序執行單個任務
比如說有三個任務workA,workB,workC,並且執行順序只能時workA---->workB---->workC可以用如下的方式處理。

WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)  instance
    .then(workC)
    .enqueue();

上面的workA,workB,workC,都是WorkRequest的子類實現對象。WorkManager會根據上面的先後順序來執行workA,workB,workC,,但是如果執行過程中三個任務中有一個失敗,整個執行都會結束。並且返回Result.failure()
2、先後順序執行多個任務列
有時候可能要先執行一組任務,然後再執行下一組任務,可以使用下面的方式來完成。

WorkManager.getInstance()
    // First, run all the A tasks (in parallel):
    .beginWith(Arrays.asList(workA1, workA2, workA3))
    // ...when all A tasks are finished, run the single B task:
    .then(workB)
    // ...then run the C tasks (in any order):
    .then(Arrays.asList(workC1, workC2))
    .enqueue();

3、多路徑先後執行
上面兩種方式都是單路徑執行,可以實現更加複雜的多路徑執行方式,使用WorkContinuation.combine(List<OneTimeWorkRequest>)如下圖要實現的執行方式:
在這裏插入圖片描述

WorkContinuation chain1 = WorkManager.getInstance()
    .beginWith(workA)
    .then(workB);
WorkContinuation chain2 = WorkManager.getInstance()
    .beginWith(workC)
    .then(workD);
WorkContinuation chain3 = WorkContinuation
    .combine(Arrays.asList(chain1, chain2))
    .then(workE);
chain3.enqueue();

未完待續…

參考文獻

1、https://developer.android.com/topic/libraries/architecture/workmanager/
2、https://codelabs.developers.google.com/codelabs/android-workmanager/index.html?index=..%2F..index#3

創作不容易呀,花了一個週末才弄成轉載請標明出處:https://blog.csdn.net/u013309870/article/details/86553531

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章