前言
最近在學(xué)習(xí) Google 推出的框架Jetpack,雖然目前網(wǎng)上的資料已經(jīng)很多了黔寇,但為了加深印象和邊動(dòng)手練習(xí)跟著學(xué)習(xí),所以站在了下面的巨人的肩膀上,并根據(jù)當(dāng)前最新的API 和編寫實(shí)際Demo,記錄下一些學(xué)習(xí)筆記眨攘,大部分是參考巨人們的匿垄,整理和休整,加入自己理解和更新吧虎忌,學(xué)習(xí)領(lǐng)略了Android Jetpack組件的一點(diǎn)魅力
目前學(xué)習(xí)筆記系列為:
- Android Jetpack 之 Lifecycles ---入門使用
- Android Jetpack 之 LiveData---入門使用
- Android Jetpack 之 Room ---入門使用
- Android Jetpack 之 WorkManger---入門使用
- ....待續(xù)
日常鳴謝巨人
正題
WorkManger 初識(shí)
WorkManger是Android Jetpack提供執(zhí)行后臺(tái)任務(wù)管理的組件
適用于需要保證系統(tǒng)即使應(yīng)用程序退出也會(huì)運(yùn)行的任務(wù)
WorkManager API可以輕松指定可延遲的異步任務(wù)以及何時(shí)運(yùn)行它們泡徙,這些API允許您創(chuàng)建任務(wù)并將其交給WorkManager立即運(yùn)行或在適當(dāng)?shù)臅r(shí)間運(yùn)行
WorkManager根據(jù)設(shè)備API級(jí)別和應(yīng)用程序狀態(tài)等因素選擇適當(dāng)?shù)姆绞絹磉\(yùn)行任務(wù)。如果WorkManager在應(yīng)用程序運(yùn)行時(shí)執(zhí)行您的任務(wù)之一膜蠢,WorkManager可以在您應(yīng)用程序進(jìn)程的新線程中運(yùn)行您的任務(wù)
如果您的應(yīng)用程序未運(yùn)行堪藐,WorkManager會(huì)選擇一種合適的方式來安排后臺(tái)任務(wù) - 具體取決于設(shè)備API級(jí)別和包含的依賴項(xiàng),WorkManager可能會(huì)使用 JobScheduler挑围,F(xiàn)irebase JobDispatcher或AlarmManager
WorkManager 相關(guān)
- Worker
指定需要執(zhí)行的任務(wù)礁竞,可以繼承抽象的Worker類,在實(shí)現(xiàn)的 doWork 方法中編寫執(zhí)行的邏輯
- WorkRequest
執(zhí)行一項(xiàng)單一任務(wù)
- WorkRequest對(duì)象必須指定Work執(zhí)行的任務(wù)
- WorkRequest都有一個(gè)自動(dòng)生成的唯一ID; 您可以使用ID執(zhí)行取消排隊(duì)任務(wù)或獲取任務(wù)狀態(tài)等操作
- WorkRequest是一個(gè)抽象的類贪惹;系統(tǒng)默認(rèn)實(shí)現(xiàn)子類 OneTimeWorkRequest或PeriodicWorkRequest(循環(huán)執(zhí)行)
- WorkRequest.Builder創(chuàng)建WorkRequest對(duì)象苏章;相應(yīng)的子類:OneTimeWorkRequest.Builder或PeriodicWorkRequest.Builder
- Constraints:指定對(duì)任務(wù)運(yùn)行時(shí)間的限制(任務(wù)約束);使用Constraints.Builder創(chuàng)建Constraints對(duì)象 奏瞬,并傳遞給WorkRequest.Builder
- WorkManager
排隊(duì)和管理工作請(qǐng)求、將WorkRequest 對(duì)象傳遞WorkManager的任務(wù)隊(duì)列
如果未指定任何約束泉孩, WorkManager立即運(yùn)行任務(wù)
- WorkStatus
包含有關(guān)特定任務(wù)的信息硼端;可以使用LiveData保存 WorkStatus對(duì)象,監(jiān)聽任務(wù)狀態(tài)寓搬;如LiveData<WorkStatus>
WorkManager 入門使用
- 添加依賴
def work_version = "1.0.0-alpha11"
implementation "android.arch.work:work-runtime:$work_version"
// optional - Firebase JobDispatcher support
implementation "android.arch.work:work-firebase:$work_version"
// optional - Test helpers
androidTestImplementation "android.arch.work:work-testing:$work_version"
但這里注意珍昨,如果項(xiàng)目做搭配了其他 jetpack 組件的依賴使用
可能會(huì)在運(yùn)行時(shí),報(bào)錯(cuò)
Program type already present: com.google.common.util.concurrent.ListenableFuture
是因?yàn)橹貜?fù)依賴包沖突了句喷,在導(dǎo)入上述依賴時(shí)镣典,可忽略掉上面的 ListenableFuture
def work_version = "1.0.0-alpha11"
implementation("android.arch.work:work-runtime:$work_version"){
exclude group:'com.google.guava', module:'listenablefuture'
}
// optional - Firebase JobDispatcher support
implementation ("android.arch.work:work-firebase:$work_version"){
exclude group:'com.google.guava', module:'listenablefuture'
}
// optional - Test helpers
androidTestImplementation "android.arch.work:work-testing:$work_version"
- 定義Worker類,并重寫其 doWork()方法
class MyWorkA(context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {
override fun doWork(): Result {
Log.i(TAG,"doWork A !")
Thread.sleep(1000)
// 模擬任務(wù)執(zhí)行成功
return Result.SUCCESS
}
}
- 使用 OneTimeWorkRequest.Builder 創(chuàng)建對(duì)象Worker唾琼,然后將任務(wù)排入WorkManager隊(duì)列
val workRequestA = OneTimeWorkRequest.Builder(MyWorkA::class.java).build()
WorkManager.getInstance().enqueue(workRequestA)
- 執(zhí)行結(jié)果
I MyWorkA : doWork A !
- WorkStatus
- 調(diào)用WorkManger的getStatusByIdLiveData()返回任務(wù)執(zhí)行的狀態(tài)LiveData<WorkStatus>
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA.id)
.observe(this, Observer {
Log.i(TAG, it?.state?.name)
if (it?.state!!.isFinished) {
Log.i(TAG, "Finish !")
}
})
- 添加Observe()執(zhí)行觀察回調(diào)
I MyWorkActivity: ENQUEUED
I MyWorkA: doWork A !
I MyWorkActivity: RUNNING
I MyWorkActivity: SUCCEEDED
I MyWorkActivity: Finish !
可以看出兄春,回調(diào)了Work的三個(gè)運(yùn)行狀態(tài)ENQUEUED 、RUNNING锡溯、SUCCESSESD
但實(shí)際上赶舆,源碼中定義的狀態(tài)如下
/**
* The current state of a unit of work.
*/
public enum State {
/**
* The state for work that is enqueued (hasn't completed and isn't running)
*/
ENQUEUED,
/**
* The state for work that is currently being executed
*/
RUNNING,
/**
* The state for work that has completed successfully
*/
SUCCEEDED,
/**
* The state for work that has completed in a failure state
*/
FAILED,
/**
* The state for work that is currently blocked because its prerequisites haven't finished
* successfully
*/
BLOCKED,
/**
* The state for work that has been cancelled and will not execute
*/
CANCELLED;
/**
* Returns {@code true} if this State is considered finished.
*
* @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and
* {@link #CANCELLED} states
*/
public boolean isFinished() {
return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
}
}
清晰一目了然...
- 任務(wù)約束
- 使用Constraints.Builder()創(chuàng)建并配置Constraints對(duì)象
val myConstrains = Constraints.Builder()
.setRequiresCharging(true)
.setRequiresDeviceIdle(true)
.build()
- 可以指定任務(wù)運(yùn)行時(shí)間的約束哑姚,例如,可以指定該任務(wù)僅在設(shè)備空閑并連接到電源時(shí)運(yùn)行芜茵,具體源碼中目前支持的條件為
- setRequiredNetworkType:網(wǎng)絡(luò)連接設(shè)置
- setRequiresBatteryNotLow:是否為低電量時(shí)運(yùn)行 默認(rèn)false
- setRequiresCharging:是否要插入設(shè)備(接入電源)叙量,默認(rèn)false
- setRequiresDeviceIdle:設(shè)備是否為空閑,默認(rèn)false
- setRequiresStorageNotLow:設(shè)備可用存儲(chǔ)是否不低于臨界閾值
- 配置好Constraints后九串,利用它創(chuàng)建Work對(duì)象绞佩,并安排進(jìn)隊(duì)列
val compressionWork = OneTimeWorkRequest.Builder(MyWorkA::class.java)
.setConstraints(myConstrains)
.build()
WorkManager.getInstance().enqueue(compressionWork)
- 取消任務(wù)
根據(jù)ID取消任務(wù),從WorkRequest()獲取Worker的ID
WorkManager.getInstance().cancelWorkById(compressionWork.id)
- 添加TAG
- 通過為WorkRequest對(duì)象分配標(biāo)記字符串來對(duì)任務(wù)進(jìn)行分組
val workRequestWithTag = OneTimeWorkRequest.Builder(MyWorkA::class.java)
.addTag("MyTAG")
.build()
- WorkManager.getWorkInfosByTag() 返回WorkStatus具有該標(biāo)記的所有任務(wù)的所有任務(wù)的列表
val list = WorkManager.getInstance().getWorkInfosByTag("MyTAG")
- WorkManager.cancelAllWorkByTag() 取消具有特定標(biāo)記的所有任務(wù)
WorkManager.getInstance().cancelAllWorkByTag("MyTAG")
- 重復(fù)的任務(wù)
創(chuàng)建單次任務(wù)使用 OneTimeWorkRequest
創(chuàng)建重復(fù)任務(wù)使用 PeriodicWorkRequest.Builder創(chuàng)建PeriodicWorkRequest對(duì)象猪钮,然后將任務(wù)添加到WorkManager的任務(wù)隊(duì)列
val repeatRequest = PeriodicWorkRequest.Builder(MyWorkA::class.java,10,TimeUnit.SECONDS).build()
然后其他的設(shè)置條件品山,Tag 都喝單次任務(wù)一致
Workmanger的高級(jí)用法
- 鏈?zhǔn)饺蝿?wù)
- WorkManager允許您創(chuàng)建和排隊(duì)指定多個(gè)任務(wù)的工作序列,以及它們應(yīng)運(yùn)行的順序
- 使用該WorkManager.beginWith() 方法創(chuàng)建一個(gè)序列 躬贡,并傳遞第一個(gè)OneTimeWorkRequest對(duì)象; 該方法返回一個(gè)WorkContinuation對(duì)象
- 使用 WorkContinuation.then()添加剩余的對(duì)象
- 最后調(diào)用WorkContinuation.enqueue()將整個(gè)序列排入隊(duì)列
- 如果任何任務(wù)返回 Worker.Result.FAILURE谆奥,則整個(gè)序列結(jié)束
val workRequestA = OneTimeWorkRequest.Builder(MyWorkA::class.java).build()
val workRequestB = OneTimeWorkRequest.Builder(MyWorkB::class.java).build()
val workRequestC = OneTimeWorkRequest.Builder(MyWorkC::class.java).build()
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA.id)
.observe(this, Observer {
Log.i("MyWorkA", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkA", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestB.id)
.observe(this, Observer {
Log.i("MyWorkB", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkB", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestC.id)
.observe(this, Observer {
Log.i("MyWorkC", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkC", "Finish !")
}
})
WorkManager.getInstance()
.beginWith(workRequestA)
.then(workRequestB)
.then(workRequestC)
.enqueue()
執(zhí)行結(jié)果 任務(wù)會(huì)按照設(shè)置的順序依次執(zhí)行A、B拂玻、C
MyWorkA: doWork A !
MyWorkA: ENQUEUED
MyWorkB: BLOCKED
MyWorkC: BLOCKED
MyWorkA: RUNNING
MyWorkA: SUCCEEDED
MyWorkA: Finish !
MyWorkB: ENQUEUED
MyWorkB: doWork B !
MyWorkB: RUNNING
MyWorkB: SUCCEEDED
MyWorkB: Finish !
MyWorkC: doWork C !
MyWorkC: ENQUEUED
MyWorkC: RUNNING
MyWorkC: SUCCEEDED
MyWorkC: Finish !
一個(gè)任務(wù)執(zhí)行時(shí)酸些,從Log 已經(jīng)可以清晰看到其他順序任務(wù)的狀態(tài)...
WorkManger在執(zhí)行過程中,當(dāng)遇到一個(gè)WOrk不成功檐蚜,則會(huì)停止執(zhí)行
修改WorkB返回FAILURE狀態(tài)
override fun doWork(): Result {
Log.i(TAG,"doWork B !")
Thread.sleep(1000)
// 模擬任務(wù)執(zhí)行失敗
return Result.FAILURE
}
執(zhí)行結(jié)果
MyWorkB: BLOCKED
MyWorkC: BLOCKED
MyWorkA: doWork A !
MyWorkA: ENQUEUED
MyWorkA: RUNNING
MyWorkA: SUCCEEDED
MyWorkA: Finish !
MyWorkB: ENQUEUED
MyWorkB: ENQUEUED
MyWorkB: doWork B !
MyWorkB: RUNNING
MyWorkB: FAILED
MyWorkB: Finish !
MyWorkC: FAILED
MyWorkC: Finish !
代碼執(zhí)行到WorkB就已經(jīng)結(jié)束了魄懂,WorkC并未執(zhí)行
但,細(xì)心點(diǎn)可以發(fā)現(xiàn)
雖然 workC 未執(zhí)行闯第,也就是說 C 的 doWork 方法未被執(zhí)行市栗,但是這里最后判斷 workC 的 isFinished ,也是通過的咳短,所以才會(huì)出現(xiàn) MyWorkC: Finish ! 打印 . 所以要注意 isFinished 方法只是判斷這個(gè)work 是否已經(jīng)結(jié)束了填帽,并不關(guān)心執(zhí)行的結(jié)果
- beginWith() 和 then()傳遞請(qǐng)求數(shù)組 List<OneTimeWorkRequest>, 同一方法傳遞的對(duì)象將會(huì)并行執(zhí)行
val requestList = arrayListOf<OneTimeWorkRequest>(workRequestA, workRequestB)
WorkManager.getInstance()
.beginWith(requestList)
.then(workRequestC)
.enqueue()
直觀點(diǎn)咙好,直接看Log ...
MyWorkA: ENQUEUED
MyWorkB: ENQUEUED
MyWorkA: doWork A !
MyWorkC: BLOCKED
MyWorkA: RUNNING
MyWorkC: BLOCKED
MyWorkB: doWork B !
MyWorkB: RUNNING
MyWorkA: SUCCEEDED
MyWorkA: Finish !
MyWorkC: doWork C !
MyWorkB: SUCCEEDED
MyWorkB: Finish !
MyWorkC: ENQUEUED
MyWorkC: RUNNING
MyWorkC: SUCCEEDED
MyWorkC: Finish !
- WorkContinuation.combine
- 使用 WorkContinuation.combine() 方法連接多個(gè)鏈來創(chuàng)建更復(fù)雜的序列
graph TB
A1-->B1
A2-->B2
B1-->C
B2-->C
預(yù)測執(zhí)行流程應(yīng)該是
開始
執(zhí)行A1/A2篡腌、其余為阻塞狀態(tài)
A1/A2執(zhí)行結(jié)束后執(zhí)行B1/B2
最后執(zhí)行C
編寫代碼
val workRequestA1 = OneTimeWorkRequest.Builder(MyWorkA::class.java).build()
val workRequestA2 = OneTimeWorkRequest.Builder(MyWorkA2::class.java).build()
val workRequestB1 = OneTimeWorkRequest.Builder(MyWorkB::class.java).build()
val workRequestB2 = OneTimeWorkRequest.Builder(MyWorkB2::class.java).build()
val workRequestC = OneTimeWorkRequest.Builder(MyWorkC::class.java).build()
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA1.id)
.observe(this, Observer {
Log.i("MyWorkA1", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkA1", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestA2.id)
.observe(this, Observer {
Log.i("MyWorkA2", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkA2", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestB1.id)
.observe(this, Observer {
Log.i("MyWorkB1", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkB1", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestB2.id)
.observe(this, Observer {
Log.i("MyWorkB2", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkB2", "Finish !")
}
})
WorkManager.getInstance().getWorkInfoByIdLiveData(workRequestC.id)
.observe(this, Observer {
Log.i("MyWorkC", it?.state?.name)
if (it?.state!!.isFinished) {
Log.i("MyWorkC", "Finish !")
}
})
val requestA1_B1 = WorkManager.getInstance().beginWith(workRequestA1)
.then(workRequestB1)
val requestA2_B2 = WorkManager.getInstance().beginWith(workRequestA2)
.then(workRequestB2)
WorkContinuation.combine(requestA1_B1, requestA2_B2)
.then(workRequestC)
.enqueue()
實(shí)際運(yùn)行結(jié)果,一目了然...
MyWorkB2: BLOCKED
MyWorkC: BLOCKED
MyWorkA1: doWork A1 !
MyWorkA1: ENQUEUED
MyWorkA2: doWork A2 !
MyWorkA2: ENQUEUED
MyWorkB1: BLOCKED
MyWorkA1: RUNNING
MyWorkA2: RUNNING
MyWorkA1: SUCCEEDED
MyWorkA1: Finish !
MyWorkB1: ENQUEUED
MyWorkA1: SUCCEEDED
MyWorkA1: Finish !
MyWorkA2: SUCCEEDED
MyWorkA2: Finish !
MyWorkB1: ENQUEUED
MyWorkB2: ENQUEUED
MyWorkB1: doWork B1 !
MyWorkB2: doWork B2 !
MyWorkB1: RUNNING
MyWorkB2: RUNNING
MyWorkB1: SUCCEEDED
MyWorkB1: Finish !
MyWorkB1: SUCCEEDED
MyWorkB1: Finish !
MyWorkB2: SUCCEEDED
MyWorkB2: Finish !
MyWorkC: ENQUEUED
MyWorkC: doWork C !
MyWorkC: RUNNING
MyWorkC: SUCCEEDED
MyWorkC: Finish !
同上勾效,中途任意一環(huán)的 work 執(zhí)行失敗嘹悼,返回FAILURE,后續(xù)的emm.....
- 特定的工作方式
- 通過調(diào)用 beginUniqueWork() 來創(chuàng)建唯一的工作序列层宫,被標(biāo)記的Work在執(zhí)行過程中只能出現(xiàn)一次
WorkManager.getInstance().beginUniqueWork("MyUniqueWork",
ExistingWorkPolicy.APPEND, workRequestA1).enqueue()
- 每個(gè)獨(dú)特的工作序列都有一個(gè)名稱
- 同一時(shí)間只允許執(zhí)行一個(gè)使用該名稱工作序列
- beginUniqueWork 的參數(shù)二 ExistingWorkPolicy 的策略有:
/**
* An enum that determines what to do with existing {@link OneTimeWorkRequest}s with the same unique
* name in case of a collision.
*/
public enum ExistingWorkPolicy {
/**
* If there is existing pending work with the same unique name, cancel and delete it. Then,
* insert the newly-specified work.
*/
REPLACE,
/**
* If there is existing pending work with the same unique name, do nothing. Otherwise, insert
* the newly-specified work.
*/
KEEP,
/**
* If there is existing pending work with the same unique name, append the newly-specified work
* as a child of all the leaves of that work sequence. Otherwise, insert the newly-specified
* work as the start of a new sequence.
*/
APPEND
}
注釋寫的很詳細(xì)了
ExistingWorkPolicy.REPLACE:取消現(xiàn)有序列并將其替換為新序列
ExistingWorkPolicy.KEEP:保留現(xiàn)有序列并忽略您的新請(qǐng)求
ExistingWorkPolicy.APPEND:將新序列附加到現(xiàn)有序列杨伙,在現(xiàn)有序列的最后一個(gè)任務(wù)完成后運(yùn)行新序列的第一個(gè)任務(wù)
- 輸入?yún)?shù)和返回值
- 將參數(shù)傳遞給任務(wù)并讓任務(wù)返回結(jié)果。傳遞和返回的值是鍵值對(duì)
- 使用 Data.Builder創(chuàng)建 Data 對(duì)象萌腿,保存參數(shù)的鍵值對(duì)
- 在創(chuàng)建WorkQuest之前調(diào)用WorkRequest.Builder.setInputData()傳遞Data的實(shí)例
- 調(diào)用 Worker.getInputData()獲取參數(shù)值
- 調(diào)用Worker.setOutputData()設(shè)置返回?cái)?shù)據(jù)
修改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 {
// 使用InputData獲取傳入的參數(shù)
minNumber = inputData.getInt(MIN_NUMBER, 0)
maxNumber = inputData.getInt(MAX_NUMBER, 0)
val result = maxNumber - minNumber
// 創(chuàng)建返回的數(shù)據(jù)Data
val outData: Data = Data.Builder().putAll(mapOf(RESULT_CODE to result)).build()
// 設(shè)置返回的數(shù)據(jù)Data
outputData = outData
return Result.SUCCESS
}
}
創(chuàng)建Worker并傳遞參數(shù)
val map = mapOf(MIN_NUMBER to 5, MAX_NUMBER to 15)
// 創(chuàng)建輸入?yún)?shù)Data
val data = Data.Builder().putAll(map).build()
// 傳遞參數(shù)
val mathWork = OneTimeWorkRequestBuilder<MathWorker>()
.setInputData(data)
.build()
觀察任務(wù)WorkStatus獲取返回結(jié)果
WorkManager.getInstance().getStatusByIdLiveData(workRequest.id)
.observe(this, Observer {
if (it?.state!!.isFinished) {
// 獲取執(zhí)行結(jié)果
Log.e("WorkerA", "${it.outputData.getInt(RESULT_CODE, 0)}")
}
})