1. 介紹
關(guān)于workmanager 的介紹 官網(wǎng)上是這么說的: 使用 WorkManager API 可以輕松地調(diào)度即使在應用退出或設備重啟時仍應運行的可延遲異步任務婚瓜。 重點是后面的幾個字,運行可延遲的異步任務, 在退出或者重啟的時候,我們在之前要實現(xiàn)這種任務可能需要 BroadcastReceiver 或者 AlarmManager,現(xiàn)在的話可以使用WorkManager,這個框架最高兼容至Android api 14
- Android 23以上是采用JobScheduler
- Android 14-22 采用的是BroadcastReceiver 和 AlarmManager
2. 優(yōu)點
- 向下兼容至api 14
- 可以添加任務執(zhí)行的約束條件,比如說 延遲執(zhí)行,是否在低電量模式下執(zhí)行,是否在充電模式下執(zhí)行,是否在設備空閑時執(zhí)行等等
- 調(diào)度一次性或周期性異步任務
- 監(jiān)管任務,可以隨時取消任務
- 將任務鏈接起來,比如說執(zhí)行可以指定多個任務的執(zhí)行順序
以上傳圖片為例:我們分解一下任務:1.圖片添加濾鏡 -> 圖片壓縮 -> 圖片上傳,這個是有執(zhí)行順序的,采用workmanager 我們可以很方便的實現(xiàn)
- 確保任務執(zhí)行宝鼓,即使應用或設備重啟也同樣執(zhí)行任務
3. 使用
3.1 為項目添加依賴
//根據(jù)項目需要自行添加依賴,不需要全部添加
dependencies {
def work_version = "2.3.1"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"http://java 語言選這個
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"http://kotlin 選這個
// optional - RxJava2 support
implementation "androidx.work:work-rxjava2:$work_version"http://配合rxjava2 使用
}
3.2 創(chuàng)建一個后臺任務
代理示例如下(以上傳圖片為例):
class UploadPicWork(
private val context: Context,
private val workerParameters: WorkerParameters
) :
Worker(context, workerParameters) {
override fun doWork(): Result {
uploadPic()//具體上傳圖片的邏輯
return Result.success()
}
}
3.3 創(chuàng)建一個workrequest
//此處的 UploadPicWork 就是之前創(chuàng)建的任務
val uploadPicWork = OneTimeWorkRequestBuilder<UploadPicWork>()
.setConstraints(triggerContentMaxDelay).build()
3.4 執(zhí)行任務
//此處的 uploadPicWork 就是前一步創(chuàng)建的 workrequest
WorkManager.getInstance(myContext).enqueue(uploadPicWork)
到這里一個簡單的任務就可以執(zhí)行了,但往往我們在開發(fā)的過程中,可能滿足不了我們的需求,再繼續(xù)往下看!
4. 創(chuàng)建一個"復雜的任務"
4.1 創(chuàng)建任務執(zhí)行的約束條件
//注意 以下條件都是 && 的關(guān)系
val triggerContentMaxDelay =
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)//網(wǎng)絡鏈接的時候使用,避免各種網(wǎng)絡判斷,省時省力
.setRequiresDeviceIdle(false)//是否在設備空閑的時候執(zhí)行
.setRequiresBatteryNotLow(true)//是否在低電量的時候執(zhí)行
.setRequiresStorageNotLow(true)//是否在內(nèi)存不足的時候執(zhí)行
.setRequiresCharging(true)//是否時充電的時候執(zhí)行
.setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS)//延遲執(zhí)行
.build()
//目前就提供這幾種約束條件,大家可以在使用的過程中酌情添加
4.2 為任務添加約束條件
val uploadPicWork =
OneTimeWorkRequestBuilder<UploadPicWork>()
.setConstraints(triggerContentMaxDelay)//約束條件
.build()
WorkManager.getInstance(myContext).enqueue(uploadPicWork)//執(zhí)行
4.3 為worker 傳遞參數(shù)
這個可能比較重要,我們一般在執(zhí)行任務的時候需要把參數(shù)傳遞到Worker中,可以采用構(gòu)造或者 寫一個set方法給傳遞進去,但是這種方式在這可能不適用
//可以采用這種方式傳遞參數(shù)
val UploadPicWork =
OneTimeWorkRequestBuilder<UploadPicWork>()
//此處set input data 需要的參數(shù) 是一個Data對象,注意只可以添加一次,如果有多個參數(shù)需要傳遞,可以封裝成一個data 數(shù)據(jù)類
.setInputData(workDataOf("params_tag" to "params"))
.setConstraints(triggerContentMaxDelay).build()
...
/**
* Converts a list of pairs to a [Data] object.
*
* If multiple pairs have the same key, the resulting map will contain the value
* from the last of those pairs.
*
* Entries of the map are iterated in the order they were specified.
*/
inline fun workDataOf(vararg pairs: Pair<String, Any?>): Data {
val dataBuilder = Data.Builder()
for (pair in pairs) {
dataBuilder.put(pair.first, pair.second)
}
return dataBuilder.build()
}
可以看看這個 workDataOf 函數(shù),就是將一個 Pair對象轉(zhuǎn)成一個Data對象,將
pair.first 作為 key ,pair.second 作為 value 構(gòu)建了一個Data 對象. 有的同學可能對 "params_tag" to "params" 這種寫法比較差異,解釋一下,這是構(gòu)造了一個 Pair 對象,
不了解 Pair 這個類的同學,可以看下Pair,參數(shù)的類可以是基本數(shù)據(jù)類型,也可以引用數(shù)據(jù)類型
4.4 獲取參數(shù)
class UploadPicWork(
private val context: Context,
private val workerParameters: WorkerParameters
) :
Worker(context, workerParameters) {
override fun doWork(): Result {
val params = inputData.getString("params_tag")//獲取傳遞的參數(shù)
uploadPic()//上傳圖片
return Result.success()
}
}
inputData 是 Worker 類 父類的一個函數(shù),在Java 中 可以 類似 this.getInputData() 返回的是一個 Data 對象,就可以獲取傳遞的參數(shù)了.
5. Worker 的狀態(tài)
在之前的創(chuàng)健的過程中,在 doWork 函數(shù)中,我們返回的 Result.success(); 我們默認 ,任務 uploadPic 函數(shù)順利的執(zhí)行完成了,所以返回了 success 狀態(tài),但是在實際開發(fā)過程中 可以能因為各種各樣的問題會導致 失敗,這時候就不能返回success了,類似這樣:
class UploadPicWork(
private val context: Context,
private val workerParameters: WorkerParameters
) :
Worker(context, workerParameters) {
override fun doWork(): Result {
val params = inputData.getString("params_tag")//獲取傳遞的參數(shù)
try {
uploadPic()//上傳圖片
} catch (e: Exception) {
return Result.failure(Data())//執(zhí)行失敗了
}
return Result.success()
}
}
Result.failure(Data()),這個函數(shù)可以什么都不傳,但是如果關(guān)注失敗的原因的話,可以把封裝一個Data對象,傳遞出去!!,至于怎么觀察任務的執(zhí)行結(jié)果,以及拿到執(zhí)行失敗傳遞的參數(shù),后面會講!
5.1 Worker 的各種狀態(tài)說明
在Worker 生命周期內(nèi),會經(jīng)歷不同的 State
- 如果有尚未完成的前提性工作茂附,則工作處于 BLOCKED State督弓。
- 如果工作能夠在滿足 約束條件 和時機條件后立即運行愚隧,則被視為處于 ENQUEUED 狀態(tài)。
- 當 Worker 在活躍地執(zhí)行時录煤,其處于 RUNNING State荞胡。
- 如果 Worker 返回 Result.success(),則被視為處于 SUCCEEDED 狀態(tài)廊营。這是一種終止 State露筒;只有 OneTimeWorkRequest 可以進入這種 State。
- 如果 Worker 返回 Result.failure()伶氢,則被視為處于 FAILED 狀態(tài)瘪吏。這也是一個終止 State;只有 OneTimeWorkRequest 可以進入這種 State劣砍。所有依賴工作也會被標記為 FAILED扇救,并且不會運行迅腔。
- 當取消尚未終止的 WorkRequest 時,它會進入 CANCELLED State掠兄。所有依賴工作也會被標記為 CANCELLED锌雀,并且不會運行。
6. 觀察Worker 的狀態(tài)(需要搭配 LiveData組件使用)
將工作加入隊列后婿牍,可以通過 WorkManager 檢查其狀態(tài)等脂。相關(guān)信息在 WorkInfo 對象中獲取撑蚌,包括 Worker 的 id、tag粉楚、當前 State 和返回數(shù)據(jù)。
6.1 獲取 WorkInfo
- 聽過 id 獲取,可以聽過 WorkManager.getWorkInfoById(UUID) 或 WorkManager.getWorkInfoByIdLiveData(UUID) 來通過 WorkRequest id 來獲取 WorkInfo伟骨。
WorkManager.getInstance(this)
.getWorkInfoByIdLiveData(UploadPicWork.id)// 通過id 獲取
.observe(this, Observer { //it:WorkInfo
it?.apply {
when (this.state) {
WorkInfo.State.BLOCKED -> println("BLOCKED")
WorkInfo.State.CANCELLED -> println("CANCELLED")
WorkInfo.State.RUNNING -> println("RUNNING")
WorkInfo.State.ENQUEUED -> println("ENQUEUED")
WorkInfo.State.FAILED -> println("FAILED")
WorkInfo.State.SUCCEEDED -> println("SUCCEEDED")
else -> println("else status ${this.state}")
}
}
})
- 通過 tag 獲取,可以利用 WorkManager.getWorkInfosByTag(String) 或 WorkManager.getWorkInfosByTagLiveData(String) 來通過 WorkRequest 的 WorkInfo 對象。
//要通過 tag 獲取,則需要先設置 tag
val UploadPicWork =
OneTimeWorkRequestBuilder<UploadPicWork>()
.setInputData(workDataOf("params_tag" to "params"))//傳遞參數(shù)
.setConstraints(triggerContentMaxDelay)//設置約束條件
.addTag("tag")//設置tag
.build()
//獲取 workInfo
WorkManager.getInstance(this)
.getWorkInfosByTagLiveData("tag")
.observe(this, Observer {it:List<WorkInfo>//此處返回的是一個集合,作為示例代碼,默認只取 0 index
it?.apply {
when (this[0].state) {
WorkInfo.State.BLOCKED -> println("BLOCKED")
WorkInfo.State.CANCELLED -> println("CANCELLED")
WorkInfo.State.RUNNING -> println("RUNNING")
WorkInfo.State.ENQUEUED -> println("ENQUEUED")
WorkInfo.State.FAILED -> println("FAILED")
WorkInfo.State.SUCCEEDED -> println("SUCCEEDED")
else -> println("else status ${this[0]}")
}
}
})
- 對于 唯一工作名稱 的一個 Worker ,可以利用 WorkManager.getWorkInfosForUniqueWork(String) 或 WorkManager.getWorkInfosForUniqueWorkLiveData(String) 檢索所有匹配的 WorkRequest 的 WorkInfo 對象。此處估計不太好理解,唯一工作是一個概念性非常強的術(shù)語鲫剿,可確保一次只有一個具有特定名稱的工作鏈稻轨。與 id 不同的是,唯一名稱是人類可讀的政冻,由開發(fā)者指定线欲,而不是由 WorkManager 自動生成李丰。與標記不同,唯一名稱僅與“一個”工作鏈關(guān)聯(lián)舟舒。您可以通過調(diào)用 WorkManager.enqueueUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) 或 WorkManager.enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest) 創(chuàng)建唯一工作序列嗜憔。
可以參考官網(wǎng)的說法 唯一工作名稱
WorkManager.getInstance(this)
.getWorkInfosForUniqueWorkLiveData("UploadPicWork")//唯一工作名稱
.observe(this, Observer {it:List<WorkInfo> //此處返回的是一個集合,作為示例代碼,默認只取 0
it?.apply {
when (this[0].state) {
WorkInfo.State.BLOCKED -> println("BLOCKED")
WorkInfo.State.CANCELLED -> println("CANCELLED")
WorkInfo.State.RUNNING -> println("RUNNING")
WorkInfo.State.ENQUEUED -> println("ENQUEUED")
WorkInfo.State.FAILED -> println("FAILED")
WorkInfo.State.SUCCEEDED -> println("SUCCEEDED")
else -> println("else status ${this[0]}")
}
}
})
注意如采用這種方式獲取 workinfo ,在執(zhí)行 worker 的時候與之前不一樣,需要采用 WorkManager.enqueueUniqueWork(String, ExistingWorkPolicy, OneTimeWorkRequest) 或 WorkManager.enqueueUniquePeriodicWork(String, ExistingPeriodicWorkPolicy, PeriodicWorkRequest) 來執(zhí)行
//全部代碼如下
//創(chuàng)建約束條件
val triggerContentMaxDelay =
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
// .setRequiresDeviceIdle(false)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
.setRequiresCharging(true)
.setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS)
.build()
// 創(chuàng)建workrequest
val UploadPicWork =
OneTimeWorkRequestBuilder<UploadPicWork>()
.setInputData(workDataOf("params_tag" to "params"))
.setConstraints(triggerContentMaxDelay)
.build()
//注意!!!,此處區(qū)別與之前的 WorkManager.getInstance(this).enqueue(UploadPicWork)
// "UploadPicWork" 需要與下面代碼 getWorkInfosForUniqueWorkLiveData("UploadPicWork") 中字符串對應
// ExistingWorkPolicy.APPEND 一個枚舉值,worker 執(zhí)行的策略,想要了解的同學,可以看下面的鏈接
WorkManager.getInstance(this).enqueueUniqueWork("UploadPicWork",ExistingWorkPolicy.APPEND,UploadPicWork)
WorkManager.getInstance(this)
.getWorkInfosForUniqueWorkLiveData("UploadPicWork")
.observe(this, Observer {
it?.apply {
when (this[0].state) {
WorkInfo.State.BLOCKED -> println("BLOCKED")
WorkInfo.State.CANCELLED -> println("CANCELLED")
WorkInfo.State.RUNNING -> println("RUNNING")
WorkInfo.State.ENQUEUED -> println("ENQUEUED")
WorkInfo.State.FAILED -> println("FAILED")
WorkInfo.State.SUCCEEDED -> println("SUCCEEDED")
else -> println("else status ${this[0]}")
}
}
7. 多個Worker 的順序執(zhí)行
在我們實際開發(fā)中可能遇到如下場景.一個任務可能依賴與其他的任務,并且每個任務之間有先后順序,以前面簡介中,圖片上傳為例:上傳圖片之前 需要 先壓縮,壓縮之前需要先剪裁, 流程如下 濾鏡--> 壓縮 --> 上傳圖片,對于這部分內(nèi)容,官網(wǎng)上介紹的已經(jīng)足夠清清楚了! 鏈接工作
- 您可以使用 WorkManager 創(chuàng)建工作鏈并為其排隊。工作鏈用于指定多個關(guān)聯(lián)任務并定義這些任務的運行順序帚稠。當您需要以特定的順序運行多個任務時滋早,這尤其有用。
要創(chuàng)建工作鏈搁进,您可以使用 WorkManager.beginWith(OneTimeWorkRequest) 或 WorkManager.beginWith(List<OneTimeWorkRequest>),這會返回 WorkContinuation 實例影兽。
然后莱革,可以通過 WorkContinuation 使用 WorkContinuation.then(OneTimeWorkRequest) 或 WorkContinuation.then(List<OneTimeWorkRequest>) 來添加從屬 OneTimeWorkRequest盅视。
每次調(diào)用 WorkContinuation.then(...) 都會返回一個新的 WorkContinuation 實例。如果添加了 OneTimeWorkRequest 的 List镶蹋,這些請求可能會并行運行赏半。
最后,您可以使用 WorkContinuation.enqueue() 方法為 WorkContinuation 鏈排隊牧氮。
讓我們看一個示例:某個應用對3個不同的圖像執(zhí)行圖像濾鏡(可能會并行執(zhí)行)瑰枫,然后將這些圖像壓縮在一起光坝,再上傳它們。
WorkManager.getInstance(myContext)
// Candidates to run in parallel
//濾鏡1 濾鏡2 濾鏡3 ...
.beginWith(listOf(filter1, filter2, filter3))
// Dependent work (only runs after all previous work in chain)
//壓縮
.then(compress)
//上傳
.then(upload)
// Don't forget to enqueue()
.enqueue()//執(zhí)行
8. 執(zhí)行重復任務
很好理解,就是在給定的時間間隔內(nèi)定期執(zhí)行任務,比如說 每個一個小時,上報位置信息,每個3個小時備份一個日志等等... 代碼示例如下:
val triggerContentMaxDelay =
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
// .setRequiresDeviceIdle(false)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
.setRequiresCharging(true)
.setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS)
.build()
// val UploadPicWork =
// OneTimeWorkRequestBuilder<UploadPicWork>()
// .setInputData(workDataOf("params_tag" to "params"))
// .setConstraints(triggerContentMaxDelay)
// .addTag("tag")
// .build()
//
val build = PeriodicWorkRequestBuilder<UploadPicWork>(
1000 * 60 *15,
TimeUnit.MICROSECONDS
).setConstraints(triggerContentMaxDelay).build()
WorkManager.getInstance(this).enqueue(build)
注意!!!這個時間間隔不可低于15分鐘
9.任務執(zhí)行失敗重試
這個場景在實際開發(fā)中也經(jīng)常遇到,比如在任務執(zhí)行的過程中可能由于一些別的原因?qū)е氯蝿請?zhí)行失敗,但是我們希望過一段時間可以重試 代碼示例如下:
//約束條件
val triggerContentMaxDelay =
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
// .setRequiresDeviceIdle(false)
.setRequiresBatteryNotLow(true)
.setRequiresStorageNotLow(true)
.setRequiresCharging(true)
.setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS)
.build()
val UploadPicWork =
OneTimeWorkRequestBuilder<UploadPicWork>()
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10 * 1000, TimeUnit.MICROSECONDS)//10s 后失敗重試
.setInputData(workDataOf("params_tag" to "params"))
.setConstraints(triggerContentMaxDelay)
.addTag("tag")
.build()
WorkManager.getInstance(this).enqueue(UploadPicWork)
...
class UploadPicWork(
private val context: Context,
private val workerParameters: WorkerParameters
) :
Worker(context, workerParameters) {
private var count: Int = 1
override fun doWork(): Result {
uploadPic()
return Result.retry()//失敗重試狀態(tài)
}
private fun uploadPic() {
SystemClock.sleep(1000 * 3)//模擬圖片上傳
}
}