Android Jetpack之WorkManager保活福音
WorkManager 初探
WorkManager API可以很容易的指定一個可延遲的非同步任務何時執行。這些api允許你建立一個任務並將其交給WorkManager以立即或在適當的時間執行。比如說,一個APP可能需要時不時的從伺服器下載新的資源,使用這些類,我們可以建立一個任務,為它選擇合適的執行環境(比如“只有當裝置在充電和線上的時候”),然後交給WorkManager使其能在滿足條件時執行。即使APP強制退出或裝置重新啟動,任務仍然保證能夠執行。
WorkManager用於那些需要保證即使APP退出了系統依然可以執行的任務,比如將應用資料上傳到伺服器。不要用於如果APP被殺程序,可以安全終止的後臺任務;對於這種情況,建議使用ThreadPools。
WorkManager會根據裝置的系統版本和APP的狀態等因素選擇適當的方式來執行任務。如果APP正在執行,WorkManager會在APP程序中起一個新執行緒來執行任務;如果APP沒有執行,WorkManager會選擇一個合適的方式來排程後臺任務--根據系統級別和APP狀態,WorkManager可能會使用JobScheduler,FireBase JobDispatcher或者AlarmManager。我們不需要編寫邏輯程式碼來確定裝置具備什麼功能選擇什麼樣的API,WorkManager會自動選擇最佳方案。
此外,WorkManager還提供了幾個高階特性。例如,你可以設定一個任務鏈,當一個任務結束之後,WorkManager會自動執行下一個在鏈中排隊的任務。我們還可以通過觀察任務的LiveData來獲取它的狀態和返回值,從而可以設定一個顯示任務狀態的UI。
本文概述了最重要的WorkManager特性。當然,還有好多可用的特性,若想了解全部的細節,可以檢視 WorkManager reference documentation
關於如何將WorkManager庫匯入到Android專案中,可以檢視 Adding Components to your Project 這篇文章
類和概念
WorkManager API使用幾個不同的類。在某些情況下,您需要對其中一個API類進行子類化。
比較重要的類有:
- Worker
指定需要執行的任務。WorkManager api包含一個抽象的Worker類。我們需要繼承並實現這個類 - WorkRequest
表示一個獨立的任務。一個WorkRequest物件需要至少指定一個執行該任務的Worker類。當然我們也可以新增更多的細節,比如指定任務應該執行的環境等。每一個WorkRequest都有一個自動生成唯一ID,我們可以使用這個ID來執行諸如取消排隊任務或者獲取任務狀態等操作。WorkRequest是一個抽象類,我們可以使用系統提供的子類- OneTimeWorkRequest 和 PeriodicWorkRequest- WorkRequest.Builder :
建立WorkRequest物件的幫助類。同樣,我們也需要用系統提供的子類:OneTimeWorkRequest.Builder 或者 PeriodicWorkRequest.Builder。 - Constraints
指定任務執行的限制條件(例如,“僅當連線到網路時”)。使用Constraint.Builder來建立Constraints,並在建立WorkRequest之前把Constraints傳給WorkRequest.Builder。
- WorkRequest.Builder :
- WorkManager
對工作請求進行管理。我們需要把WorkRequest物件傳給WorkManager以便將任務編入佇列。WorkManager以這樣的方式排程任務,以分散系統資源的負載,同時滿足我們指定的約束條件。 - WorkStatus
包含特定任務的資訊。WorkManager為每個WorkRequest物件提供一個LiveData。LiveData持有一個WorkStatus物件;通過觀察這個LiveData,我們可以確定任務的當前狀態,並在任務完成後獲得返回值。
典型的工作流程
假設我們正在編寫一個圖片庫的APP,該APP需要定期壓縮儲存的影象。我們使用WorkManager來排程影象壓縮的任務。在這種情況下,我們並不關心壓縮任務發生的時間,我們只需要設定一個任務,然後其他都不關心了。
首先,需要定義一個Worker類並重寫doWork()方法。worker類指定了如何執行操作,但是沒有任何關於任務應該何時執行的資訊。
public class CompressWorker extends Worker { @Override public Worker.WorkerResult doWork() { // Do the work here--in this case, compress the stored images. // In this example no parameters are passed; the task is // assumed to be "compress the whole library." myCompress(); // Indicate success or failure with your return value: return WorkerResult.SUCCESS; // (Returning RETRY tells WorkManager to try this task again // later; FAILURE says not to try again.) } }
接下來,基於Worker建立一個OneTimeWorkRequest物件,然後使用WorkManager將任務放入佇列中:
OneTimeWorkRequest compressionWork = new OneTimeWorkRequest.Builder(CompressWorker.class) .build(); WorkManager.getInstance().enqueue(compressionWork);
WorkManager選擇適當的時間來執行任務,平衡諸如系統上的負載、裝置是否正在充電等方面的考慮。在大多數情況下,如果不指定任何約束,WorkManager會立即執行任務。如果您需要獲取任務狀態,您可以通過獲取適當的LiveData<WorkStatus>來獲得一個WorkStatus物件。例如,如果您想檢查任務是否完成,可以使用如下程式碼:
WorkManager.getInstance().getStatusById(compressionWork.getId()) .observe(lifecycleOwner, workStatus -> { // Do something with the status if (workStatus != null && workStatus.getState().isFinished()) { // ... } });
任務約束
我們可以通過約束條件來指定任務何時執行。例如,我們可能希望指定該任務只應在裝置空閒並連線電源時執行。在這種情況下,我們需要去建立OneTimeWorkRequest.Builder物件,然後使用這個Builder去建立OneTimeWorkRequest:
// Create a Constraints that defines when the task should run Constraints myConstraints = new Constraints.Builder() .setRequiresDeviceIdle(true) .setRequiresCharging(true) // Many other constraints are available, see the // Constraints.Builder reference .build(); // ...then create a OneTimeWorkRequest that uses those constraints OneTimeWorkRequest compressionWork = new OneTimeWorkRequest.Builder(CompressWorker.class) .setConstraints(myConstraints) .build();
然後把這個OneTimeWorkRequest物件傳給WorkManager.enqueue(),WorkManager在找到執行任務的時間時會考慮這個約束的。
取消任務
UUID compressionWorkId = compressionWork.getId(); WorkManager.getInstance().cancelWorkById(compressionWorkId);
WorkManager會盡最大努力的取消任務,但這並不靠譜——當我們試圖取消任務時,任務可能已經在執行中或者已經完成了。WorkManager還提供了方法來取消唯一工作序列中的所有任務,或使用指定標記的所有任務,當然同樣不靠譜。
高階特性
WorkManager API的核心功能能夠建立簡單的、即發即忘的任務。除此之外,API還提供了一些高階特性,來設定更詳細的請求。
迴圈任務
我們都會碰到需要重複執行的任務。比如,一個照片管理應用不會只壓縮一次圖片,更有可能的是,它會時不時的檢查一下共享的照片,看看是否有新的或者修改過的圖片需要壓縮。我們可以選擇一個重複執行的任務來壓縮圖片,當然,也可以啟動一個新任務。
new PeriodicWorkRequest.Builder photoCheckBuilder = new PeriodicWorkRequest.Builder(PhotoCheckWorker.class, 12, TimeUnit.HOURS); // ...if you want, you can apply constraints to the builder here... // Create the actual work object: PeriodicWorkRequest photoCheckWork = photoCheckBuilder.build(); // Then enqueue the recurring task: WorkManager.getInstance().enqueue(photoCheckWork);
WorkManager會試圖在請求的時間間隔執行該任務,這取決於我們強加的約束和它的其他需求了。
任務鏈
應用程式可能需要按特定的順序執行多個任務。WorkManager支援建立一個工作序列,該序列指定多個任務以及它們應該執行的順序。
WorkManager.getInstance() .beginWith(workA) // Note: WorkManager.beginWith() returns a // WorkContinuation object; the following calls are // to WorkContinuation methods .then(workB)// FYI, then() returns a new WorkContinuation instance .then(workC) .enqueue();
WorkManager根據每個任務指定的約束,按所請求的順序執行任務。如果有任意任務返回“Worker.WorkerResult.FAILURE”,則整個工作序列都將結束。
我們還可以將多個OneTimeWorkRequest物件傳遞給beginWith()和then()呼叫。如果我們將多個OneTimeWorkRequest物件傳遞給單個方法呼叫,那麼WorkManager在執行其餘的序列之前就會執行所有這些任務(並行)。比如:
WorkManager.getInstance() // First, run all the A tasks (in parallel): .beginWith(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(workC1, workC2) .enqueue();
通過將多個鏈與WorkContinuation.combine()方法連線起來,可以建立更復雜的序列。
打個比方,假設我們現在想執行這樣一個工作序列

WorkContinuation chain1 = WorkManager.getInstance() .beginWith(workA) .then(workB); WorkContinuation chain2 = WorkManager.getInstance() .beginWith(workC) .then(workD); WorkContinuation chain3 = WorkContinuation .combine(chain1, chain2) .then(workE); chain3.enqueue();
在這種情況下,WorkManager在workB之前執行workA。它在workD之前執行workc。在WordB和workD結束後,WorkManager執行workE。
注意看黑板!
雖然WorkManager按順序執行每個子鏈,但是chain1中的任務與chain2中的任務順序是不相關的。
例如,workB可能在workC之前或之後執行,或者它們可能同時執行。
唯一可以保證的是,每個子鏈中的任務將按順序執行;也就是說,workB會等到workA完成後才開始。
WorkContinuation還有許多變體,為一些特定情況提供了現成的方法,具體可以參考 WorkContinuation
唯一的工作序列
用beginUniqueWork()代替beginWith()就可以建立一個唯一的工作序列。每一個唯一工作序列都有一個名字;WorkManager每次只允許有一個使用該名稱的工作序列。當我們建立一個新的惟一的工作序列時,如果已經有一個同名的未完成序列,可以指定WorkManager應該做什麼:
- 取消原有的序列並用新的來代替它
- 保留原有的序列並忽略新的請求
- 把新的工作序列拼到原有序列的後邊,當原有的序列的最後一個任務執行完之後,接著執行新的序列的第一個任務。
如果有一個不應該多次排隊的任務,那麼唯一的工作序列就很有用了。例如,如果應用程式需要將其資料同步到網路中,我們可以將一個名為“sync”的序列編入佇列,並指定如果已經有一個具有該名稱的序列,則應該忽略新任務。如果需要逐步構建一個長長的任務鏈,那麼唯一的工作序列也很有用。例如,一個照片編輯應用程式可以讓使用者撤消一系列的操作。每個撤銷操作都可能需要一段時間,但是它們必須按照正確的順序執行。在這種情況下,應用程式可以建立一個“撤消”鏈,並根據需要將每個“撤消”操作附加到鏈上。
任務標籤
可以通過為WorkRequest物件分配一個標籤來對任務進行分組。
OneTimeWorkRequest cacheCleanupTask = new OneTimeWorkRequest.Builder(MyCacheCleanupWorker.class) .setConstraints(myConstraints) .addTag("cleanup") .build();
WorkManager類提供了一些實用方法,可以使用特定的標記對所有任務進行操作。例如,WorkManager.cancelAllWorkByTag()用一個特定的標記取消所有任務,而WorkManager.getStatusesByTag()返回一個列表,其中列出了所有帶有該標記的任務的所有工作狀態。
入參和返回值
為了更好的靈活性,我們還可以向任務傳遞引數並讓任務返回結果,傳遞的值和返回的值是鍵值對形式。要將一個引數傳遞給一個任務,需要在建立WorkRequest物件之前呼叫WorkRequest.Builder.setInputData()方法,該方法接收一個Data.Builder建立的Data物件。Worker類可以通過呼叫Worker.getInputData()訪問這些引數。任務需要呼叫Worker.setOutputData()來輸出返回值,同樣接收一個Data物件,我們可以通過觀察任務的LiveData<WorkStatus>來獲得返回值。
假設我們有一個執行耗時操作的Worker:
// Define the Worker class: public class MathWorker extends Worker { // Define the parameter keys: public static final String KEY_X_ARG = "X"; public static final String KEY_Y_ARG = "Y"; public static final String KEY_Z_ARG = "Z"; // ...and the result key: public static final String KEY_RESULT = "result"; @Override public Worker.WorkerResult doWork() { // Fetch the arguments (and specify default values): int x = getInputData().getInt(KEY_X_ARG, 0); int y = getInputData().getInt(KEY_Y_ARG, 0); int z = getInputData().getInt(KEY_Z_ARG, 0); // ...do the math... int result = myCrazyMathFunction(x, y, z); //...set the output, and we're done! Data output = new Data.Builder() .putInt(KEY_RESULT, result) .build(); setOutputData(output); return WorkerResult.SUCCESS; } }
建立一個任務並傳遞引數:
// Create the Data object: Data myData = new Data.Builder() // We need to pass three integers: X, Y, and Z .putInt(KEY_X_ARG, 42) .putInt(KEY_Y_ARG, 421) .putInt(KEY_Z_ARG, 8675309) // ... and build the actual Data object: .build(); // ...then create and enqueue a OneTimeWorkRequest that uses those arguments OneTimeWorkRequest mathWork = new OneTimeWorkRequest.Builder(MathWorker.class) .setInputData(myData) .build(); WorkManager.getInstance().enqueue(mathWork);
通過WorkStatus獲取返回值:
WorkManager.getInstance().getStatusById(mathWork.getId()) .observe(lifecycleOwner, status -> { if (status != null && status.getState().isFinished()) { int myResult = status.getOutputData().getInt(KEY_RESULT, myDefaultValue)); // ... do something with the result ... } });
如果是一個任務鏈,那麼一個任務的返回值可以作為下一個任務的引數。如果是一個簡單的任務鏈,一個OneTimeWorkRequest後面跟著另一個OneTimeWorkRequest,第一個任務通過呼叫setOutputData()返回結果,下一個任務通過呼叫getInputData()來獲取結果。如果是一個更復雜的任務鏈,比如說,有幾個任務都將返回值傳遞給下一個任務,我們可以通過OneTimeWorkRequest.Builder上定義一個InputMerger,指定如果不同的任務返回一個具有相同鍵的輸出時應該怎麼做。
【附錄】

資料圖
需要資料的朋友可以加入Android架構交流QQ群聊:513088520
點選連結加入群聊【Android移動架構總群】: 加入群聊
獲取免費學習視訊,學習大綱另外還有像高階UI、效能優化、架構師課程、NDK、混合式開發(ReactNative+Weex)等Android高階開發資料免費分享。