WorkManager徹底解析+重構輪詢系統

花兩個週末寫完的,創做不容易啊,轉載請標明出處哈:html

csdn:blog.csdn.net/u013309870/… 掘金:juejin.im/post/5c4472…java

前言

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

什麼是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目錄下在上面加上下面的依賴。app

dependencies {
    // 其餘依賴配置
    def work_version = "1.0.0-beta02"
    implementation "android.arch.work:work-runtime:$work_version"
}
複製代碼

能夠在developer.android.com/topic/libra…獲取當前的work-runtime版本而且設置正確的版本號。框架

WorkManager主要類及使用

以下圖給出了WorkManager中主要的類以及關係圖,黃色區域是最主要的三個類,構成了WorkManager的基本框架,紅色部分和綠色部分是關聯的黃色部分的具體實現或者類裏面包含一些規則或數據。 異步

在這裏插入圖片描述
一、Worker處理要執行的任務的具體邏輯。

二、WorkerRequest表明一個獨立的能夠執行的任務,以及任務執行時的條件和規則,好比說任務執行一次仍是屢次以及任務的觸發條件是什麼任務有什麼約束等。ide

三、WorkManager提供隊列將要執行的WorkerRequest放到隊列中管理和執行。 以下圖,三個主要類的關係: oop

在這裏插入圖片描述
下面分別介紹三個類的做用和使用方法。

Worker

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

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()方法的返回值主要有三種 一、Worker.Result.SUCCESS 表示任務執行成功

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

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

WorkRequest

WorkRequest要指定執行任務的Worker,也能夠給WorkRequest加一些規則,好比說何時執行任務,任務執行一次仍是屢次,每個WorkRequest都有一個自動產生的惟一ID,能夠根據惟一ID獲取對應任務的狀態以及是否取消對應的任務。以下圖WorkRequest有兩個實現類以下圖:

在這裏插入圖片描述
一、OneTimeWorkRequest 任務只執行一次

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

二、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提供了很好的方式。 一、前後順序執行單個任務 好比說有三個任務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()二、前後順序執行多個任務列 有時候可能要先執行一組任務,而後再執行下一組任務,可使用下面的方式來完成。

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();
複製代碼

三、多路徑前後執行 上面兩種方式都是單路徑執行,能夠實現更加複雜的多路徑執行方式,使用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();
複製代碼

使用WorkManager遇到的問題

一、使用PeriodicWorkRequest只執行一次,並不重複執行。

WorkManager instance= new PeriodicWorkRequest.Builder(PollingWorker.class, 10, TimeUnit.MINUTES)
                .build();
複製代碼

緣由:PeriodicWorkRequest默認的時間間隔是15分鐘若是設置的時間小於15分鐘,就會出現問題。

解決方法:設置的默認時間必須大於或等於15分鐘。另外要注意,就算設置的時間爲15分鐘也不必定間隔15分鐘就執行。因此要精確的間隔時間執行,通常不用WorkManager,可使用AlarmManager來實現。

二、在doWork()方法中更新UI致使崩潰。 緣由:doWork()方法是在WorkManager管理的後臺線程中執行的,更新UI操做只能在主線程中進行。

解決方法:當doWork()耗時方法執行完以後,將更新UI操做拋到主線中執行,能夠用handle來實現,以下:

/** * Created time 15:32. * * @author huhanjun * @since 2019/1/23 */
public class PollingWorker extends Worker {
    public static final String TAG = "PollingWorker";

    @NonNull
    @Override
    public Result doWork() {
        Log.d(TAG, "doWork");
        try {
            polling();
            runOnUIThread(new Runnable() {
                @Override
                public void run() {
                    //更新UI操做
                }
            });
            return Result.SUCCESS;
        } catch (Exception e) {
            Log.d(TAG, "failure");
            return Result.FAILURE;
        }
    }

    private void polling() {
        Log.d(TAG, "Polling");
    }
//拋到主線程中執行
    private void runOnUIThread(Runnable runnable) {
        new Handler(Looper.getMainLooper()).post(runnable);
    }
}
複製代碼

在執行完Polling方法後,將更新操做拋給了runOnUIThread()方法,這樣就能夠在上面代碼註釋的地方執行更新操做。 未完待續........下週末來重構以前的輪詢框架:juejin.im/post/5c2e0e…

參考文獻

一、developer.android.com/topic/libra…

二、codelabs.developers.google.com/codelabs/an…

個人csdn地址

blog.csdn.net/u013309870

相關文章
相關標籤/搜索