Android Support WorkManager使用詳解


 

 

使用WorkManager調度任務

 

WorkManager是一個庫, 用以將工作入隊, 當該工作的約束條件得到滿足之后, WorkManager保證它的執行. WorkManager允許觀測工作的狀態, 並且擁有能力創建復雜的工作鏈.

WorkManager使用基礎的作業分發服務, 僅當如下條件可用時:

  • 當API 23+時, 使用JobScheduler;
  • 當API 14~22時, 如果應用使用了Firebase JobDispatcher和其它的Firebase依賴時, 使用Firebase JobDispatcher; 否則的話, 則使用自定義的AlarmManager + BroadcastReceiver實現.

所有的工作必須有相應的Worker來實現計算. 工作在后台線程執行. WorkManager支持兩種類型的工作: OneTimeWorkRequest和PeriodicWorkRequest.

默認情況下, WorkManager在后台線程執行它的操作. 如果你已經在后台線程運行且需要同步調用WorkManager, 使用synchronous()方法訪問這些方法.

 

WorkManager API可以輕易的指定可暫緩的異步任務, 以及這些任務何時運行. 這些API能夠讓你創建一個任務並把該任務推給WorkManager即刻或者在恰當的時刻去運行. 比如, 某個應用可能時不時地需要從網絡下載最新的資源. 使用這些類, 你可以設置一個任務, 選擇該任務運行時的恰當條件(例如"只有在設備充電且聯網的時候"), 然后把任務推給WorkManager, 當指定的條件滿足時, WorkManager便會運行該任務. 即使你的應用被強制退出或者設備進行了重啟, 這些任務依然會被保障執行. 

備注: 例如, 向服務器上傳應用數據, 即使應用退出, 系統依然保障運行這些任務. WorkManager即是為這些任務而設計的. 而對於進程內的后台工作, 在應用的進程結束的時候, 則會被安全地終止, 這些任務並不是WorkManager設計的目的. 而這些這種情況, 推薦使用ThreadPools.

 

WorkManager基於設備的API和應用的狀態來選擇恰當的方式來執行任務. 如果WorkManager在應用正在運行的時候執行了你的某條任務, 那么WorkManager會在你的應用進程中開啟新的線程來執行這條任務. 而如果你的應用沒有在運行, WorkManager則會選擇恰當的方式來調度后台任務-根據設備API水平和依賴庫, WorkManager可能會選擇JobScheduler, Firebase JobDispatcher或者AlarmManager. 你不必自己寫邏輯找出設備有哪些能力以找出恰當的API; 正相反, 你只需把任務遞交給WorkManager讓它選擇最佳選項.

此外, WorkManager還提供了些許高級特性. 比如, 你可以設置任務鏈; 在一些任務完成之后, WorkManager將下一個任務添加進隊列以形成鏈. 你也可以通過觀測任務的LiveData來查看它的狀態和返回值. 如果你想通過展示UI來顯示任務狀態, 這也許會有用.

同時, WorkManager已經存在於androidx.arch包中, 但它依然依賴於Android Support Library 27.1並與Arch構件的版本有關聯. 擁有AndroidX依賴的WorkManager版本將會在未來發布.

WorkManager的依賴添加方式如下:

 1 dependencies {
 2     def work_version = "1.0.0-alpha04"
 3 
 4     implementation "android.arch.work:work-runtime:$work_version" // use -ktx for Kotlin
 5 
 6     // optional - Firebase JobDispatcher support
 7     implementation "android.arch.work:work-firebase:$work_version"
 8 
 9     // optional - Test helpers
10     androidTestImplementation "android.arch.work:work-testing:$work_version"
11 }

 

相關類和概念

 

WorkManager API使用了幾個不同的類. 某些情況下, 你需要繼承某些類.

重要的一些類有: 

  • Worker: 指定了你需要執行什么樣的任務. WorkManager包含了抽象類Worker. 你需要繼承它並在里面執行工作.
  • WorkRequest: 表示一個單獨的任務. 最少, WorkRequest對象指定了哪個Worker執行該任務. 然后, 你也可以向WorkRequest添加一些細節, 指定諸如任務在什么條件下執行等這些事情. 每一個WorkRequest都擁有一個自動生成的獨一無二的ID. 你可以使用該ID來做一些事情, 例如, 取消已經入隊的任務, 或者, 獲取任務的狀態. WorkRequest是個抽象類, 使用時, 應該使用它的直接子類, OneTimeWorkReqeust或者PeriodicWorkRequest.
  • WorkRequest.Builder: 幫助類, 用來創建WorkRequest對象. 再說一遍, 你需要使用它的子類, OneTimeWorkRequest.Builder或者PeriodicWorkRequest.Builder.
  • Constraints: 指定任務運行時機的約束條件(例如"只有在連接到網絡的時候"). 你可以使用Constraints.Builder來創建Constraints對象, 並且在創建WorkRequest對象之前, 把Constraints傳遞給WorkerRequest.Builder.
  • WorkerManager: 入隊並管理工作請求. 你把WorkRequest對象傳遞給WorkManager以入隊任務.  WorkManager以這種方式調度任務來分散系統資源的加載, 同時尊重了你指定的約束條件.
  • WorkStatus: 包含了特定任務的信息. WorkManager為每一個WorkRequest對象提供了LiveData. LiveData持有WorkStatus對象; 通過觀測LiveData, 你能夠判定任務的當前狀態, 以及在任務結束后, 獲取返回值.

 

經典工作流

 

假設你正在寫一個圖片庫應用, 該應用需要定期壓縮存儲的圖片. 你想要使用WorkManager API來執行圖片壓縮. 在這種情況下, 你不必特別關注壓縮在什么時候執行; 你想要設置好任務, 然后不再關注它.

首先, 你需要定義自己的Worker類, 然后覆蓋doWork方法. 你的Worker類詳述了操作該怎么執行, 卻沒有任何信息表明任務何時運行.

 1 class CompressWorker : Worker()  {
 2 
 3     override fun doWork(): Result {
 4 
 5         // Do the work here--in this case, compress the stored images.
 6         // In this example no parameters are passed; the task is
 7         // assumed to be "compress the whole library."
 8         myCompress()
 9 
10         // Indicate success or failure with your return value:
11         return Result.SUCCESS
12 
13         // (Returning RETRY tells WorkManager to try this task again
14         // later; FAILURE says not to try again.)
15 
16     }
17 
18 }

然后, 基於上述Worker, 你創建了OneTimeWorkRequest對象, 然后通過WorkManager將該任務入隊.

 1 val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>().build() 2 WorkManager.getInstance().enqueue(compressionWork) 

WorkManager選擇恰當的時刻運行該任務, 在平衡了諸如系統加載, 設備插拔等等, 這些考慮之后. 在多數情況下, 你不需要指定任務約束, WorkManager即刻運行你的任務. 如果你需要檢測任務狀態, 可能通過對正確的LiveData<WorkStatus>的處理, 獲取WorkStatus對象. 比如, 你想知道任務是否已經完成, 你可以使用這樣的代碼: 

1 WorkManager.getInstance().getStatusById(compressionWork.id)
2                 .observe(lifecycleOwner, Observer { workStatus ->
3                     // Do something with the status
4                     if (workStatus != null && workStatus.state.isFinished) {
5                         // ...
6                     }
7                 })

 

任務約束

 

如果你想, 你可以指定任務何時應該運行的約束條件. 比如, 你可以指定任務只有在設備空閑且連接充電時才可以運行. 在這種情況下, 你需要創建一個OneTimeWorkReqeust.Builder對象, 然后可以使用這個Builder創建一個真正的OneTimeWorkRequest.

 1 // Create a Constraints that defines when the task should run
 2 val myConstraints = Constraints.Builder()
 3         .setRequiresDeviceIdle(true)
 4         .setRequiresCharging(true)
 5         // Many other constraints are available, see the
 6         // Constraints.Builder reference
 7         .build()
 8 
 9 val compressionWork = OneTimeWorkRequestBuilder<CompressWorker>()
10         .setConstraints(myConstraints)
11         .build()

然后, 像之前一樣, 將這個OneTimeWorkRequest對象傳遞給WorkManager.enqueue(). WorkManager會考慮你的約束條件, 直到一個時刻可以運行你的任務.

 

取消任務

 

在一個任務入隊后, 你依然可以取消它. 要取消一個任務, 你需要它的工作ID, 這個ID可以從WorkRequest對象中獲取到. 比如, 下面的代碼取消了之前的compressionWork請求: 

 1 val compressionWorkId:UUID = compressionWork.getId() 2 WorkManager.getInstance().cancelWorkById(compressionWorkId) 

WorkManager會盡最大努力來取消該任務, 但這是天性不確定的--因為在你嘗試取消該任務時, 它可能已經在運行或者已經結束了. WorkManager也提供了方法取消在一個單獨工作序列中的所有任務, 或者擁有特定tag的所有任務, 當然, 也是盡力而為.

 

高級功能

 

WorkManager API的核心功能合使你能夠創建簡單且"即發即棄"的任務. 除此之外, API提供了高級功能讓你能夠設置更多復雜的任務.

 

任務重現

 

你也許擁有一個需要反復執行的任務. 比如, 照片管理應用不會只執行一次壓縮圖片. 更可能的情況是, 它會想非常頻繁地檢查共享的照片, 來看是否有新的或者修改過的照片需要壓縮. 這個再現任務能夠壓縮它找到的照片, 或者, 它可以接二連三地新建"壓縮該圖片"任務.

要創建一個重現任務, 使用PeriodicWorkRequest.Builder類創建一個PeriodicWorkRequest對象, 然后用入隊OneTimeWorkRequest對象一樣的方式, 入隊PeriodicWorkRequest對象. 比如, 假設我們定義了PhotoCheckWorker類來識別需要壓縮的圖片. 如果我們想每12小時運行一次這個清單任務, 我們需要像如下一樣創建一個PeriodicWorkRequest對象:

1 val photoCheckBuilder =
2         PeriodicWorkRequestBuilder<PhotoCheckWorker>(12, TimeUnit.HOURS)
3 // ...if you want, you can apply constraints to the builder here...
4 
5 // Create the actual work object:
6 val photoCheckWork = photoCheckBuilder.build()
7 // Then enqueue the recurring task:
8 WorkManager.getInstance().enqueue(photoCheckWork)

WorkManager嘗試運行你的任務, 在你請求的間隔, 並且服從你設置的約束條件和其它需求.

 

任務入鏈

 

你的應用可能需要以特定的順序執行幾個任務. WorkManager允許你創建並入隊一個工作序列, 這個工作序列指定了多個任務, 以及它們運行的順序. 

比如, 假設你的應用有三個OneTimeWorkRequest對象: workA, workB和workC. 這些任務必須以A->B->C的順序執行. 要反三個任務入隊, 使用WorkManager.beginWith(), 以第一個OneTimeWorkRequest對象workA為參數, 創建工作序列. 這個方法返回了WorkContinuation對象, 該對象定義了一個任務序列. 然后, 按順序, 用WorkContinuation.then方法添加剩下的OneTimeWorkRequest對象. 最后用WorkContinuation.enqueue方法將該工作序列入隊.

1 WorkManager.getInstance()
2     .beginWith(workA)
3         // Note: WorkManager.beginWith() returns a
4         // WorkContinuation object; the following calls are
5         // to WorkContinuation methods
6     .then(workB)    // FYI, then() returns a new WorkContinuation instance
7     .then(workC)
8     .enqueue()

WorkManager用請求的順序, 根據每一個任務特定的約束, 運行任務. 如果任何一個任務返回了Worker.Result.FAILURE, 則整個序列終止.

你也可以傳遞多個OneTimeWorkRequest對象給beginWith()/then()方法, 如果你對單個方法調用傳遞了多個OneTimeWorkRequest對象, WorkManager將會並行運行該方法里面的所有任務, 在該方法里面的所有任務都完成之后, 才會執行方法序列的余下部分. 比如: 

1 WorkManager.getInstance()
2     // First, run all the A tasks (in parallel):
3     .beginWith(workA1, workA2, workA3)
4     // ...when all A tasks are finished, run the single B task:
5     .then(workB)
6     // ...then run the C tasks (in any order):
7     .then(workC1, workC2)
8     .enqueue()

你可以通過WorkContinuation.combine()方法將多個鏈聯合在一起, 來創建更加復雜的任務鏈. 比如你想創建如下一個任務鏈:

要創建這個序列, 需要創建兩個單獨的序列, 然后把它們結合在一起生成第三個:

 1 val chain1 = WorkManager.getInstance()
 2     .beginWith(workA)
 3     .then(workB)
 4 val chain2 = WorkManager.getInstance()
 5     .beginWith(workC)
 6     .then(workD)
 7 val chain3 = WorkContinuation
 8     .combine(chain1, chain2)
 9     .then(workE)
10 chain3.enqueue()

在這個實例下, WorkManager先運行workA, 然后運行workB; 也是先運行workC, 然后運行workD. 在workB和workD都完成之后, 再運行workE.

備注: 盡管在每一個子鏈中WorkManager有序運行任務, 但卻無法保證chains1中的任務如何與chains2中的任務重疊. 比如上面例子中的workB可以在workD之前或者之后運行, 也可以同時運行. 唯一的承諾是每一個子鏈中的多個任務有序運行; 也就是, workB直到workA運行結束都會開始執行.

 

有大量的WorkContinuation方法的變體提供了特定場景下的速記. 比如WorkContinuation.combine(OneTimeWorkRequest, WorkContinuation...)命令WorkManager先完成所有指定的WorkContinuation鏈, 然后以執行完OneTimeWorkRequest結束. 

 

獨立工作序列(Unique work sequences)

 

通過調用beginUniqueWork()而非beginWith()來開始一個序列, 就能夠創建一個獨立工作序列. 每一個獨立工作序列都有一個名字; 一個名字, WorkManager一次只允許開啟一個工作序列. 當你創建一個新的獨立工作序列時, 如果已經存在了一個未完成的同名工作序列時, 你應該詳述WorkManager此時應該做什么:

  • 取消已存在序列並用新的序列替代它;
  • 保留已存在序列並忽略新序列;
  • 將新的序列附加到已存在序列末尾, 當已存在序列最后一個任務完成后, 執行新序列中的第一個任務.

當你擁有一個任務不該入隊多次時, 獨立工作序列會非常有用. 比如, 你的應用需要同步數據到網絡時, 你可能會入隊一個名叫"sync"的獨立工作序列, 並且指明如果已經存在一個"sync"序列時, 新的任務應該被忽略. 

當你需要逐步構建一個長的任務鏈時, 獨立工作序列也會非常有用. 比如照片編輯應用可能會讓用戶撤銷一長鏈的操作, 每一個操作的撤銷可能花費很短的時間, 但是它們必須以正確的順序執行. 在這種情形下, 應用可以創建一個"撤銷"鏈並將每個撤銷操作按需添加到鏈中.

 

工作打標

 

對於每一個WorkRequest對象, 你都可以給它賦值一個tag字符串, 然后從邏輯上將任務分組. 要設置tag的話, 調用WorkRequest.Builder.addTag(), 如下:

1 val cacheCleanupTask =
2         OneTimeWorkRequestBuilder<MyCacheCleanupWorker>()
3     .setConstraints(myConstraints)
4     .addTag("cleanup")
5     .build()

類WorkManager提供了幾個工具方法, 允許你用特定的tag操作所有的任務. 比如, WorkManager.cancelAllWorkByTag()使用特定的tag取消所有的任務; WorkManager.getStatusesByTag()返回特定tag標記任務的WorkStatus列表.

 

輸入參數和返回值

 

為了獲取更大的靈活性, 你可以給任務傳遞參數, 也可以使任務返回結果. 被傳遞和返回的值是鍵-值對. 要給任務傳參數, 在創建WorkRequest對象之前調用WorkRequest.Builder.setInputData(). 該方法的參數是Data.Builder創建的Data對象. Worker類可以通過Worker.getInputData()方法訪問傳入的參數. 要輸出返回值, 任務調用Worker.setOutputData(). 該方法的參數也是個Data對象. 你可能通過觀測任務的LiveData<WorkStatus>獲取輸出.

比如, 假設你有一個Worker類執行耗時的計算. 下面是這個Worker的示例:

 1 // Define the parameter keys:
 2 const val KEY_X_ARG = "X"
 3 const val KEY_Y_ARG = "Y"
 4 const val KEY_Z_ARG = "Z"
 5 
 6 // ...and the result key:
 7 const val KEY_RESULT = "result"
 8 
 9 // Define the Worker class:
10 class MathWorker : Worker()  {
11 
12     override fun doWork(): Result {
13         val x = inputData.getInt(KEY_X_ARG, 0)
14         val y = inputData.getInt(KEY_Y_ARG, 0)
15         val z = inputData.getInt(KEY_Z_ARG, 0)
16 
17         // ...do the math...
18         val result = myCrazyMathFunction(x, y, z);
19 
20         //...set the output, and we're done!
21         val output: Data = mapOf(KEY_RESULT to result).toWorkData()
22         setOutputData(output)
23 
24         return Result.SUCCESS
25     }
26 }

要創建這個工作且傳參, 代碼應該如下:

 1 val myData: Data = mapOf("KEY_X_ARG" to 42,
 2                        "KEY_Y_ARG" to 421,
 3                        "KEY_Z_ARG" to 8675309)
 4                      .toWorkData()
 5 
 6 // ...then create and enqueue a OneTimeWorkRequest that uses those arguments
 7 val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
 8         .setInputData(myData)
 9         .build()
10 WorkManager.getInstance().enqueue(mathWork)

返回值將會在任務的WorkStatus中可用:

1 WorkManager.getInstance().getStatusById(mathWork.id)
2         .observe(this, Observer { status ->
3             if (status != null && status.state.isFinished) {
4                 val myResult = status.outputData.getInt(KEY_RESULT,
5                       myDefaultValue)
6                 // ... do something with the result ...
7             }
8         })

如果你將任務入鏈, 一個任務的輸出是可以作為鏈中下一個任務的輸入的. 如果是個很簡單的鏈, 單OneTimeWorkRequest緊接着另一個單OneTimeWorkRequest, 第一個任務通過調用setOutputData返回的值, 在第二個任務中通過調用getInputData可以獲取得到. 如果工作鏈比較復雜, 比如多個任務都發送輸出到單個任務--你可以在OneTimeWorkRequest.Builder中定義一個InputMerger, 以指定當不同任務返回的輸出有相同的鍵時, 應該怎么處理. 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM