Android Jetpack架構組件之WorkManger

一、前言

最近簡單看了下google推出的框架Jetpack,感受此框架的內容能夠對平時的開發有很大的幫助,也能夠解決不少開發中的問題,對代碼的邏輯和UI界面實現深層解耦,打造數據驅動型UI界面。java

Android Architecture組件是Android Jetpack的一部分,它們是一組庫,旨在幫助開發者設計健壯、可測試和可維護的應用程序,包含一下組件:android

上述時Android Architecture所提供的架構組件,本文將詳細介紹下WorkManger的使用bash

二、WorkManger簡介

WorkManger是Android Jetpack提供執行後臺任務管理的組件,它適用於須要保證系統即便應用程序退出也會運行的任務,WorkManager API能夠輕鬆指定可延遲的異步任務以及什麼時候運行它們,這些API容許您建立任務並將其交給WorkManager當即運行或在適當的時間運行。網絡

WorkManager根據設備API級別和應用程序狀態等因素選擇適當的方式來運行任務。若是WorkManager在應用程序運行時執行您的任務之一,WorkManager能夠在您應用程序進程的新線程中運行您的任務。若是您的應用程序未運行,WorkManager會選擇一種合適的方式來安排後臺任務 - 具體取決於設備API級別和包含的依賴項,WorkManager可能會使用 JobScheduler,Firebase JobDispatcher或AlarmManager架構

三、WorkManager基礎知識

3.一、使用WorkManager以前需先了解幾個類:框架

  • Worker:指定須要執行的任務,能夠繼承抽象的Worker類,在實現的方法中編寫執行的邏輯
  • WorkRequest:執行一項單一任務
  1. WorkRequest對象必須指定Work執行的任務
  2. WorkRequest都有一個自動生成的惟一ID; 您可使用ID執行取消排隊任務或獲取任務狀態等操做
  3. WorkRequest是一個抽象的類;系統默認實現子類 OneTimeWorkRequest或PeriodicWorkRequest(循環執行)
  4. WorkRequest.Builder建立WorkRequest對象;相應的子類:OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder
  5. Constraints:指定對任務運行時間的限制(任務約束);使用Constraints.Builder建立Constraints對象 ,並傳遞給WorkRequest.Builder
  • WorkManager:排隊和管理工做請求;將WorkRequest 對象傳遞WorkManager的任務隊列
  1. 若是您未指定任何約束, WorkManager當即運行任務
  • WorkStatus:包含有關特定任務的信息;可使用LiveData保存 WorkStatus對象,監放任務狀態;如LiveData<WorkStatus>

3.二、工做流程異步

  • 添加WorkManger的工做依賴
def work_version = "1.0.0-alpha10"
implementation "android.arch.work:work-runtime:$work_version" // use -ktx for Kotlin
androidTestImplementation "android.arch.work:work-testing:$work_version"複製代碼
  • 定義Worker類,並覆蓋其 doWork()方法
class TestWorker(context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {

    override fun doWork(): Result {
        Log.e("TestWorker", "執行了 doWork() 操做!")
        return Result.SUCCESS
    }
}複製代碼
  • 使用 OneTimeWorkRequest.Builder 建立對象Worker,而後將任務排入WorkManager隊列
  1. OneTimeWorkRequest.Builder建立一個單次執行的WorkQuest
val workRequest = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
WorkManager.getInstance().enqueue(workRequest)複製代碼
  • 執行結果:

  • WorkStatus
  1. 調用WorkManger的getStatusByIdLiveData()返回任務執行的狀態LiveData<WorkStatus>
  2. 添加Observe()執行觀察回調
WorkManager.getInstance().getStatusByIdLiveData(workRequest.id)  // 返回LiveData
    .observe(this, Observer {
        Log.e("TestWorker", it?.state?.name)
        if (it?.state!!.isFinished) {
            Log.e("TestWorker", "Finish")
        }
    })複製代碼

從上面執行過程當中看出,回調了Work的三個運行狀態RUNNING、SUCCESSESD、FINISHide

  • 任務約束
  1. 使用Constraints.Builder()建立並配置Constraints對象
  2. 能夠指定任務運行時間的約束,例如,能夠指定該任務僅在設備空閒並鏈接到電源時運行
// 在鏈接網絡、插入電源且設備處於空閒時運行
val myConstraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED) 
    .setRequiresCharging(true)
    .setRequiresDeviceIdle(true)
    .build()複製代碼

除了上面設置的約束外,WorkManger還提供瞭如下的約束做爲Work執行的條件:post

  1. setRequiredNetworkType:網絡鏈接設置
  2. setRequiresBatteryNotLow:是否爲低電量時運行 默認false
  3. setRequiresCharging:是否要插入設備(接入電源),默認false
  4. setRequiresDeviceIdle:設備是否爲空閒,默認false
  5. setRequiresStorageNotLow:設備可用存儲是否不低於臨界閾值

配置好Constraints後,建立Work對象性能

val compressionWork = OneTimeWorkRequest.Builder(TestWorker::class.java)
        .setConstraints(myConstraints)
        .build()複製代碼

執行結果:在爲鏈接網絡時,Work處於阻塞狀態當鏈接網絡後Work當即執行變爲SUCCESSED狀態

  • 取消任務
  1. 從WorkRequest()獲取Worker的ID
  2. 根據ID取消任務
WorkManager.getInstance().cancelWorkById(workRequest.id)複製代碼
  • 添加TAG
  1. 經過爲WorkRequest對象分配標記字符串來對任務進行分組
  2. WorkManager.getStatusesByTag() 返回WorkStatus具備該標記的全部任務的全部任務的列表
  3. WorkManager.cancelAllWorkByTag() 取消具備特定標記的全部任務
OneTimeWorkRequestBuilder<MyCacheCleanupWorker>()
    .addTag("cleanup")
    .build()

   /./ 使用:
 WorkManager.getInstance().getStatusesByTag("TAG")
 WorkManager.getInstance().cancelAllWorkByTag("TAG")複製代碼
  • 重複的任務
  1. 前面建立了單次任務使用OneTimeWorkRequest,對於重複任務使用 PeriodicWorkRequest.Builder該類建立PeriodicWorkRequest對象
  2. 而後將任務添加到WorkManager的任務隊列
val timesRequest = PeriodicWorkRequest.Builder(TestWorker::class.java,10,TimeUnit.SECONDS)
   ......
    .build()複製代碼

四、Workmanger的高級用法

  • 鏈式任務
  1. WorkManager容許您建立和排隊指定多個任務的工做序列,以及它們應運行的順序
  2. 使用該WorkManager.beginWith() 方法建立一個序列 ,並傳遞第一個OneTimeWorkRequest對象; 該方法返回一個WorkContinuation對象
  3. 使用 WorkContinuation.then()添加剩餘的對象
  4. 最後調用WorkContinuation.enqueue()將整個序列排入隊列
  5. 若是任何任務返回 Worker.Result.FAILURE,則整個序列結束
  6. Example:
WorkManager.getInstance()
    .beginWith(workA)
    .then(workB)   
    .then(workC)
    .enqueue()複製代碼

執行結果:任務會按照設置的順序依次執行A、B、C

有一點就是WorkManger在執行過程當中,當遇到一個WOrk不成功,則會中止執行,現修改WorkB返回FAILURE狀態,再次運行程序結果以下:

override fun doWork(): Result {
    Log.e("Worker", "WorkerB 執行了 doWork() 操做!")
    return Result.FAILURE
}複製代碼

代碼執行到WorkB就已經結束了,WorkC並未執行。

  • beginWith() 和 then()傳遞多個對象 : 同一方法傳遞的對象將會並行
WorkManager.getInstance()
    .beginWith(workA1, workA2, workA3)   // 三個對象將並行
    .then(workB)                                            // 執行完3個A後,在執行B
    .then(workC1, workC2)                          //...
    .enqueue()複製代碼
  • WorkContinuation.combine()
  1. 使用 WorkContinuation.combine() 方法鏈接多個鏈來建立更復雜的序列,如:

// ......繼續建立WorkD 和 WorkE 及相應的Request
val configA_B = WorkManager.getInstance().beginWith(workRequest)
     .then(workRequestB)

val configC_D = WorkManager.getInstance().beginWith(workRequestC)
     .then(workRequestD)

WorkContinuation.combine(configA_B,configC_D)
     .then(workRequestE)
     .enqueue()複製代碼

執行結果與上面一致:開始時執行A/C,其他爲阻塞狀態,A/C執行結束後執行B/D,最後執行WoekerE
若是此時把WorkB中返回FAILURE,執行結果也一致執行到WorkerB就結束了,WorkerE不會執行

  • 特定的工做方式
  1. 經過調用 beginUniqueWork() 來建立惟一的工做序列,被標記的Work在執行過程當中只能出現一次
WorkManager.getInstance().beginUniqueWork("worker", ExistingWorkPolicy.APPEND, workRequest)
//參數:一、工做序列的名稱
             二、當有相同名稱序列時採起的策略方式
             三、須要執行的Worker複製代碼
  1. 每一個獨特的工做序列都有一個名稱; 同一時間只容許執行一個使用該名稱工做序列
  2. ExistingWorkPolicy提供如下策略:

* ExistingWorkPolicy.REPLACE:取消現有序列並將其替換爲新序列
* ExistingWorkPolicy.KEEP:保留現有序列並忽略您的新請求
* ExistingWorkPolicy.APPEND:將新序列附加到現有序列,在現有序列的最後一個任務完成後運行新序列的第一個任務

以ExistingWorkPolicy.REPLACE爲例,讓WorkA睡眠3秒,模擬同時運行的狀態:

override fun doWork(): Result {
    Log.e("Worker", "WorkerA begin Sleep()!")
    Thread.sleep(3000)
    Log.e("Worker", "WorkerA 執行了 doWork() 操做!")
    return Result.SUCCESS
}複製代碼

Append:兩個都會執行

WorkManager.getInstance().beginUniqueWork("worker", ExistingWorkPolicy.APPEND, workRequest)
    .enqueue()
WorkManager.getInstance().beginUniqueWork("worker", ExistingWorkPolicy.APPEND, workRequestB)
    .enqueue()複製代碼

運行結果:

REPLACE:只會執行WorkerB

KEEP:只會執行WorkerA

  • 輸入參數和返回值
  1. 將參數傳遞給任務並讓任務返回結果。傳遞和返回的值是鍵值對
  2. 使用 Data.Builder建立 Data 對象,保存參數的鍵值對
  3. 在建立WorkQuest以前調用WorkRequest.Builder.setInputData()傳遞Data的實例
  4. 調用 Worker.getInputData()獲取參數值
  5. 調用Worker.setOutputData()設置返回數據

修改WorkerA以下

const val MIN_NUMBER = "minNumber"
const val MAX_NUMBER = "maxNumber"
const val RESULT_CODE = "Result"

class WorkerA(context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {

    private var minNumber = 0
    private var maxNumber = 0

    override fun doWork(): Result {
        minNumber = inputData.getInt(MIN_NUMBER, 0) // 使用InputData獲取傳入的參數
        maxNumber = inputData.getInt(MAX_NUMBER, 0)

        val result = maxNumber - minNumber  // 計算結果
        val outData: Data = Data.Builder().putAll(mapOf(RESULT_CODE to result)).build() // 建立返回的數據Data
        outputData = outData   // 設置返回的數據Data

        return Result.SUCCESS
    }
}複製代碼

建立Worker並傳遞參數

val map = mapOf(MIN_NUMBER to 5, MAX_NUMBER to 15)   
val data = Data.Builder().putAll(map).build()   // 建立輸入參數Data

val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
        .setInputData(data)   // 傳遞參數
        .build()複製代碼

觀察任務WorkStatus獲取返回結果

WorkManager.getInstance().getStatusByIdLiveData(workRequest.id)
    .observe(this, Observer {
        if (it?.state!!.isFinished) {
            Log.e("WorkerA", "${it.outputData.getInt(RESULT_CODE, 0)}")  // 獲取執行結果
        }
    })複製代碼
  • 鏈式調用
  1. 對於鏈式調用的Work,使用方法和單個Work一直,只是前一個的結果會做爲後一個傳入的參數,現添加WorkB接收WorkA傳入的差值並添加平方處理,而後返回計算結果,在Activity中添加兩個Work的工做監聽:
const val NUMBER = "NUMBER"
const val RESULT_B = "RESULT_B"
class WorkerB(context: Context,workerParameters: WorkerParameters) : Worker(context,workerParameters){

    override fun doWork(): Result {
       val input = inputData.getInt(RESULT_CODE,5) // 獲取第一個Worker返回的結果
        Log.e("Worker", "WorkerB 接受數據 input = $input ")
        val map = mapOf(RESULT_B to input*input)
        outputData = Data.Builder().putAll(map).build()  // 設置返回數據
        return Result.SUCCESS
    }
}

// Activity中監聽WorkerB的執行結果
WorkManager.getInstance().getStatusByIdLiveData(workRequestB.id)
    .observe(this, Observer {
        if (it?.state!!.isFinished) {
            Log.e("WorkerB", "${it.outputData.getInt(RESULT_B, 0)}")
        }
    })複製代碼

執行結果:對於WorkA依次傳入5和15,在WorkA中計算差值15-5 = 10,因此WorkA中輸出10,那麼鏈式調用的WorkB中接收的就是10,加平方後輸出爲100,輸出Log與分析一致,

到此Android Jetpack組件就介紹完了,從第一篇開始到如今有一個月了,中間斷斷續續總算分析完了,經過對組件的學習發現Android Jetpack組件的推出不只加速了咱們開發的速度,並且避免了程序運行中的一些生命週期、內存問題以及一些性能問題,但願能夠經過這幾篇文章對想學習組件的同窗有所幫助,後續將會使用全部的組件編寫一個客戶端,將全部組件綜合使用。

相關文章
相關標籤/搜索