WorkManager-Guide&Tips
WorkManager
為了方便執行一些不著急的
、非同步的
的後臺
任務而誕生. 大部分情況下, 只需要定義好自己想做的任務, 交給WorkManager
去執行, 剩下就不用管了.
注意一下, 同樣是後臺執行緒,WorkManager
的重點在於保證就算 App 關掉之後後臺任務也能夠被執行
. 而那種可以隨著 App 退出而關閉的後臺任務, 還是更適合使用ThreadPools
.
以前的實現方案
-
Service: 這是最常見的需要後臺執行的方案了. 對比來說, Service 有以下幾個問題:
- 可能會由於開發者的設定而瘋狂執行, 這會導致手機電量被瘋狂消耗. 對比之下 WorkManager 的同一個週期任務的最小間隔時間是15分鐘.
-
targetSdkVersion
為 26 及以上的時候, 在不被允許建立後臺服務的情況下,startService()
會丟擲IllegalStateException
. 對比之下 WorkManager 會按照設定選擇合適的時間執行.
-
JobScheduler: 這個最關鍵的就是隻有 Android 5.0 以上才能使用, 其實 WorkManager 在 5.0 以上也是用這個實現的.
- AlarmManager + BroadcastReceiver. 這個方案也是可以的, WorkManager 在 5.0 以下也是這樣實現的, 只是封裝了更好用的 API .
如果對更好用的 WorkManager 感興趣, 就可以繼續往下看了. 大概的介紹順序是:
- 匯入
- 一次性任務的使用
- 週期性任務的使用
- 任務如何取消
- 給任務加上約束條件
- 多個任務以特定順序執行
- 相同任務的重複處理策略
- 任務的輸入和輸出
- 一些需要注意的點
以下程式碼都可以在Demo 中找到.
匯入
和其他 JetPack 的元件一樣, 在project
的build.gradle
檔案中新增google()
源:
allprojects { repositories { google() jcenter() } }
然後在module
的build.gradle
中新增WorkManager
的依賴:
dependencies { def work_version = "1.0.0-beta05" // Java 依賴版本 implementation "android.arch.work:work-runtime:$work_version" // Kotlin 依賴版本, 和上面的依賴二選一即可 implementation "android.arch.work:work-runtime-ktx:$work_version" // 可選 RxJava2 支援 implementation "android.arch.work:work-rxjava2:$work_version" // 可選 測試支援 androidTestImplementation "android.arch.work:work-testing:$work_version" }
基本使用
使用起來就如上面所說, 首先你需要建立一個任務 (Worker)
, 然後丟給WorkManager
.
Kotlin class TestWorker(context: Context, params: WorkerParameters) : Worker(context, params) { override fun doWork(): Result { // 這裡已經是後臺執行緒了, 只需要實現自己的業務邏輯就好了 // return Result.retry(); 重試 // return Result.failure(); 不再重試 return Result.success() } }
Java public class TestWorker extends Worker { public TestWorker(Context context, WorkerParameters params) { super(context, params); } @Override public Result doWork() { // 這裡已經是後臺執行緒了, 只需要實現自己的業務邏輯就好了 // return Result.retry(); 重試 // return Result.failure(); 不再重試 return Result.success(); } }
而Worker
裡面只宣告要實現的任務, 其他的約束條件要在WorkRequest
中設定, 把Worker
變成WorkRequest
. 再交給WorkManager
去執行就好了.
一次性任務
Kotlin val oneTimeWorker = OneTimeWorkRequest.Builder(TestWorker::class.java).build() WorkManager.getInstance().enqueue(oneTimeWorker)
Java OneTimeWorkRequest oneTimeWorker = new OneTimeWorkRequest.Builder(TestWorker.class).build(); WorkManager.getInstance().enqueue(oneTimeWorker);
就是這麼簡單, 接下來 Worker 就會在後臺執行緒運行了.
週期性任務
週期性任務需要更加慎重一點. 開啟之後如果不注意, 大部分情況下就會一直執行, 這可能帶來很不好的使用者體驗.
設定週期性任務的時候, 需要設定repeatInterval(重複區間)
和flexInterval(彈性區間)
引數, 配合註釋說明:
[彈性區間外|彈性區間內 (flex Interval) ][彈性區間外|彈性區間內 (flex Interval) ]... [任務不執行|任務可執行][任務不執行|任務可執行]... \_________________________________________/\________________________________________/... 第一個區間 (repeat Interval)第二個區間 (repeat Interval)...(repeat)
repeatInterval
最小值是15分鐘, 而flexInterval
的最小值是5分鐘, 如果flexInterval
大於repeatInterval
, 也會被修改到和repeatInterval
一樣的值.
取消任務
但是從 API 可以看到, WorkManager 是將這個 Worker 入隊了, 那既然是以佇列維護的非同步操作, 肯定會有重複的問題. WorkManager 預設的操作是遇到一樣的 Worker 時, 新 Worker 會等舊 Worker 執行完再執行, 即順序執行.
不過大部分情況下這都不是我們想要的模式, 所以在執行前最好取消相同的任務. 每個Worker
都有一個唯一標識 UUID, 同時在構建WorkRequest
的時候還可以新增任意個 Tag, 通過這兩個標識都可以取消任務.
Kotlin // UUID 方式 val workId: UUID = oneTimeWorker.getId() WorkManager.getInstance().cancelWorkById(workId) // Tag 方式 val oneTimeWorker = OneTimeWorkRequest.Builder(TestWorker::class.java) .addTag("myTag") .build() WorkManager.getInstance().cancelAllWorkByTag("myTag")
Java // UUID 方式 UUID workId = oneTimeWorker.getId(); WorkManager.getInstance().cancelWorkById(workId); // Tag 方式 OneTimeWorkRequest myTask = new OneTimeWorkRequest.Builder(TestWorker.class) .addTag("myTag") .build() WorkManager.getInstance().cancelAllWorkByTag("myTag")
取消相同的任務已經避免了系統資源不必要的消耗, 不過為了防止 API 的濫用, 還推薦給任務加上一些約束條件, 方便任務在系統資源沒那麼緊張的時候再執行:
加上約束
所有的約束Constraints
都是由Constraints.Builder()
來建立的, Builder 提供了以下的約束方式:
Kotlin // 設定網路型別 setRequiredNetworkType(networkType: NetworkType) // 是否執行時電量不要太低 setRequiresBatteryNotLow(requiresBatteryNotLow: Boolean) // 是否在充電時才執行 setRequiresCharging(requiresCharging: Boolean) // 是否不太剩餘儲存空間過低時執行 setRequiresStorageNotLow(requiresStorageNotLow: Boolean) // 是否在裝置空閒時執行, 這個最低版本是 23 setRequiresDeviceIdle(requiresDeviceIdle: Boolean) // 監聽一個本地的 Uri, 第二個引數是否監聽 Uri 的子節點. 在 Uri 的內容改變時執行任務, 最低版本是 24 addContentUriTrigger(uri: Uri, triggerForDescendants: Boolean)
Java // 設定網路型別 setRequiredNetworkType(NetworkType networkType) // 是否執行時電量不要太低 setRequiresBatteryNotLow(boolean requiresBatteryNotLow) // 是否在充電時才執行 setRequiresStorageNotLow(boolean requiresStorageNotLow) // 是否不太剩餘儲存空間過低時執行 setRequiresStorageNotLow(requiresStorageNotLow: Boolean) // 是否在裝置空閒時執行, 這個最低版本是 23 setRequiresDeviceIdle(boolean requiresDeviceIdle) // 監聽一個本地的 Uri, 第二個引數是否監聽 Uri 的子節點. 在 Uri 的內容改變時執行任務, 最低版本是 24 addContentUriTrigger(Uri uri, boolean triggerForDescendants)
多個任務的執行順序
WorkManager 提供了相應的 API 使任務可以使一個或多個OneTimeWorkerRequest
按某個順序執行.
// A, B, C 就會按順序執行, 如果全部返回成功或者某一個返回失敗, 那該任務鏈就會結束. WorkManager.getInstance() .beginWith(workA) .then(workB) .then(workC) .enqueue() // A, B 一起執行, 雖然這2個的開始順序不定, 但是 C 一定是在這2個執行後才執行. WorkManager.getInstance() .beginWith(Arrays.asList(workA, workB)) .then(workC) .enqueue() // B 一定會在 A 後面執行, D 也一定會在 C 後面執行, 但是 AB 與 CD 這兩條鏈的執行順序不定, 但是 E 一定是在 B 和 D 都結束後才執行. val chain1 = WorkManager.getInstance() .beginWith(workA) .then(workB) val chain2 = WorkManager.getInstance() .beginWith(workC) .then(workD) val chain3 = WorkContinuation .combine(Arrays.asList(chain1, chain2)) .then(workE) chain3.enqueue()
// A, B, C 就會按順序執行, 如果全部返回成功或者某一個返回失敗, 那該任務鏈就會結束. WorkManager.getInstance() .beginWith(workA) .then(workB) .then(workC) .enqueue(); // A, B 一起執行, 雖然這2個的開始順序不定, 但是 C 一定是在這2個執行後才執行. WorkManager.getInstance() .beginWith(Arrays.asList(workA, workB)) .then(workC) .enqueue(); // B 一定會在 A 後面執行, D 也一定會在 C 後面執行, 但是 AB 與 CD 這兩條鏈的執行順序不定, 但是 E 一定是在 B 和 D 都結束後才執行. 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();
相同任務的重複策略
前面提到對於 Worker 來說, 可以通過 UUID 和 Tag 來保證其唯一性, 這樣在需要的時候就可以避免任務重複執行. 但對於連續的任務鏈, 如果任務多了, 這樣的方式會很繁瑣. 於是, WorkerManager 也提供了相應的 API 來保證其唯一性.
Kotlin beginUniqueWork(uniqueWorkName: String, existingWorkPolicy: ExistingWorkPolicy, work: OneTimeWorkRequest): WorkContinuation beginUniqueWork(uniqueWorkName: String, existingWorkPolicy: ExistingWorkPolicy, work: List<OneTimeWorkRequest>): WorkContinuation
Java WorkContinuation beginUniqueWork(String uniqueWorkName, ExistingWorkPolicy existingWorkPolicy, OneTimeWorkRequest work) WorkContinuation beginUniqueWork(String uniqueWorkName, ExistingWorkPolicy existingWorkPolicy, List<OneTimeWorkRequest> work)
第一個引數就是這一個或者一系列 worker 的名字, 第二個引數就是重複時的操作, 有以下幾種模式:
- ExistingWorkPolicy.APPEND : 如果上一個任務處於等待或者未完成的狀態, 則把當前任務新增到其任務鏈的後面. 這樣它就在上一個任務執行完後執行.
- ExistingWorkPolicy.KEEP : 如果上一個任務處於等待或者未完成的狀態, 什麼都不做(繼續等上一個任務執行).
- ExistingWorkPolicy.REPLACE : 如果上一個任務處於等待或者未完成的狀態, 取消並刪除上一個, 執行新的.
輸入和輸出
Worker 的輸入輸出是用Map<String, Object>
來儲存的, 用Data
類封裝了一層. 輸出用LiveData
來監聽.
Kotlin // 建立輸入 val inputData = Data.Builder() .putInt("KEY_FIRST", firstNumber) .putInt("KEY_SECOND", secondNumber) .build() val worker = OneTimeWorkRequestBuilder<MathWorker>() .setInputData(inputData) .build() WorkManager.getInstance().enqueue(worker) // Worker 類: class PlusWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { val first = inputData.getInt("KEY_FIRST", 0) val second = inputData.getInt("KEY_SECOND", 0) val result = first + second // 1 + 2 = 3 val output = Data.Builder() .putInt("KEY_RESULT", result) .build() return Result.success(output) } } // 監聽返回 WorkManager.getInstance().getWorkInfoByIdLiveData(worker.id) .observe(this, Observer { info -> if (info != null && info.state.isFinished) { // 獲取返回結果, 應該是3 val result = info.outputData.getInt("KEY_RESULT", 0) } })
Java // 建立輸入 Data inputData = new Data.Builder() .putInt("KEY_FIRST", 1) .putInt("KEY_SECOND", 2) .build(); OneTimeWorkRequest worker = new OneTimeWorkRequest.Builder(PlusWorker.class) .setInputData(inputData) .build(); WorkManager.getInstance().enqueue(worker); // Worker 類: public class PlusWorker extends Worker { public PlusWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { int first = getInputData().getInt("KEY_FIRST", 0); int second = getInputData().getInt("KEY_SECOND", 0); int result = first + second; // 1 + 2 = 3 Data output = new Data.Builder() .putInt("KEY_RESULT", result) .build(); return Result.success(output); } } // 監聽返回 WorkManager.getInstance().getWorkInfoByIdLiveData(worker.getId()) .observe(lifecycleOwner, info -> { if (info != null && info.getState().isFinished()) { // 獲取返回結果, 應該是3 int result = info.getOutputData().getInt(KEY_RESULT, 0)); } });
一些需要注意的地方
-
WorkManager
雖然在設計的時候是為了在 App 沒執行的時候也能執行 Worker, 但是目前從 Google Issue Tracker 上的資訊來看, 以下幾種情況殺掉後任務的存活情況是這樣的:- 從工作管理員(最近使用)關掉: 原生的 Android 上 Worker 仍然會執行, 但是在某些把這種操作當做強制停止的廠商 或一些中國廠商 的機型上, Worker 要等到下次開啟 App 才會執行.
- 重啟手機 (Worker 執行中的狀態): 重啟後 Worker 會繼續執行.
- App 資訊 -> 強制關閉: Worker 會再下次開啟 App 的時候執行.
- 重啟手機 (App 被強制關閉了): Worker 會再下次開啟 App 的時候執行.