一掷匠、前言:
WorkManager 是適合用于持久性工作的推薦解決方案拧揽。如果工作始終要通過應用重啟和系統(tǒng)重新啟動來調(diào)度袍辞,便是持久性的工作扇苞。由于大多數(shù)后臺處理操作都是通過持久性工作完成的植榕,因此 WorkManager 是適用于后臺處理操作的主要推薦 API吮螺。
1提鸟、WorkManager 可處理三種類型的持久性工作:
- 立即執(zhí)行:必須立即開始且很快就完成的任務宾濒,可以加急腿短。
- 長時間運行:運行時間可能較長(有可能超過 10 分鐘)的任務。
- 可延期執(zhí)行:延期開始并且可以定期運行的預定任務绘梦。
類型 | 周期 | 使用方式 |
---|---|---|
立即 | 一次性 | OneTimeWorkRequest 和 Worker橘忱。如需處理加急工作,請對 OneTimeWorkRequest 調(diào)用 setExpedited()卸奉。 |
長期運行 | 一次性或定期 | 任意 WorkRequest 或 Worker钝诚。在工作器中調(diào)用 setForeground() 來處理通知。 |
可延期 | 一次性或定期 | PeriodicWorkRequest 和 Worker榄棵。 |
二凝颇、兼容范圍廣
WorkManager最低能兼容API Level 14,并且不需要你的設(shè)備安裝有Google Play Services疹鳄。因此拧略,你不用過于擔心兼容性問題,因為API Level 14已經(jīng)能夠兼容幾乎100%的設(shè)備了瘪弓。
WorkManager能依據(jù)設(shè)備的情況垫蛆,選擇不同的執(zhí)行方案。在API Level 23+腺怯,通過JobScheduler來完成任務袱饭,而在API Level 23以下的設(shè)備中,通過AlarmManager和Broadcast Receivers組合完成任務瓢喉。但無論采用哪種方案宁赤,任務最終都是交由Executor來完成。
WorkManager的兩個重要特點
1.針對不需要及時完成的任務
比如栓票,發(fā)送應用程序日志,同步應用程序數(shù)據(jù)愕够,備份用戶數(shù)據(jù)等走贪。站在業(yè)務的角度,這些任務都不需要立即完成惑芭,如果我們自己來管理這些任務坠狡,邏輯可能會非常復雜,若API使用不恰當遂跟,可能會消耗大量電量逃沿。
2.保證任務一定會被執(zhí)行
WorkManager能保證任務一定會被執(zhí)行婴渡,即使你的應用程序當前不在運行中,哪怕你的設(shè)備重啟凯亮,任務仍然會在適當?shù)臅r候被執(zhí)行边臼。這是因為WorkManager有自己的數(shù)據(jù)庫,關(guān)于任務的所有信息和數(shù)據(jù)都保存在這個數(shù)據(jù)庫中假消,因此柠并,只要你的任務交給了WorkManager,哪怕你的應用程序徹底退出富拗,或者設(shè)備重新啟動臼予,WorkManager依然能夠保證完成你交給的任務。
注意:WorkManager不是一種新的工作線程啃沪,它的出現(xiàn)不是為了替代其它類型的工作線程粘拾。工作線程通常立即運行,并在執(zhí)行完成后給到用戶反饋创千。而WorkManager不是即時的缰雇,它不能保證任務能立即得到執(zhí)行。
三签餐、在項目中使用WorkManager
1.在app的build.gradle中添加依賴寓涨。
dependencies {
def work_version = "2.8.1"
// (Java only)
implementation "androidx.work:work-runtime:$work_version"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
}
2.使用Worker定義任務 。
繼承Worker類氯檐,覆蓋doWork()方法戒良,所有需要在任務中執(zhí)行的代碼都在該方法中編寫。
class UploadWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
//獲取傳遞的值
val mBookId = inputData.getString("book_id")
val buyNum = inputData.getString("num")
// 執(zhí)行一些后臺任務....
//返回成功
return Result.success()
}
}
doWork()方法有三種類型的返回值:
- 執(zhí)行成功返回Result.success()
- 執(zhí)行失敗返回Result.failure()
- 需要重新執(zhí)行返回Result.retry()
3.普通任務執(zhí)行
//傳輸數(shù)據(jù)
val inputData = Data.Builder()
.putString("book_id", "1234")
.putString("num", "88")
.build()
OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
.setInitialDelay(10, TimeUnit.SECONDS)//延遲10秒執(zhí)行
.setInputData(inputData) //傳輸數(shù)據(jù)
.build();
//開始執(zhí)行任務
WorkManager.getInstance(this).enqueue(uploadWorkRequest);
設(shè)置延遲執(zhí)行任務冠摄。假設(shè)你沒有設(shè)置觸發(fā)條件糯崎,或者當你設(shè)置的觸發(fā)條件符合系統(tǒng)的執(zhí)行要求,此時河泳,系統(tǒng)有可能立刻執(zhí)行該任務沃呢,但如果你希望能夠延遲執(zhí)行,那么可以通過setInitialDelay()方法拆挥,延后任務的執(zhí)行薄霜。
4.使用WorkRequest配置任務
設(shè)置任務觸發(fā)條件。例如纸兔,我們可以設(shè)置在設(shè)備處于充電惰瓜,
網(wǎng)絡(luò)已連接,且電池電量充足的狀態(tài)下汉矿,
才出發(fā)我們設(shè)置的任務崎坊。
Constraints constraints = new Constraints.Builder()
.setRequiresCharging(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build();
//設(shè)置請求
OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
.setConstraints(constraints)//設(shè)置觸發(fā)條件
.build();
//開始執(zhí)行任務
WorkManager.getInstance(this).enqueue(uploadWorkRequest);
5.觀察任務的狀態(tài)。
任務在提交給系統(tǒng)后洲拇,通過WorkInfo獲知任務的狀態(tài)奈揍,WorkInfo包含了任務的id曲尸,tag,以及Worker對象傳遞過來的outputData男翰,以及任務當前的狀態(tài)另患。有三種方式可以得到WorkInfo對象。
WorkManager.getWorkInfosByTag()
WorkManager.getWorkInfoById()
WorkManager.getWorkInfosForUniqueWork()
如果你希望能夠?qū)崟r獲知任務的狀態(tài)奏篙。這三個方法還有對應的LiveData方法柴淘。
WorkManager.getWorkInfosByTagLiveData()
WorkManager.getWorkInfoByIdLiveData()
WorkManager.getWorkInfosForUniqueWorkLiveData()
通過LiveData,我們便可以在任務狀態(tài)發(fā)生變化的時候秘通,收到通知为严。
WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadWorkRequest.getId()).observe(MainActivity.this, new Observer<WorkInfo>()
{
@Override
public void onChanged(WorkInfo workInfo)
{
Log.d("onChanged()->", "workInfo:"+workInfo);
}
});
6.取消任務。與觀察任務類似的肺稀,我們也可以根據(jù)Id或者Tag取消某個任務第股,或者取消所有任務。
WorkManager.getInstance(MainActivity.this).cancelAllWork();
7.WorkManager和Worker之間的參數(shù)傳遞
WorkManager通過setInputData()方法向Worker傳遞數(shù)據(jù)话原。
Data inputData = new Data.Builder()
.putString("input_data", "aaaaa")
.putString("input_data2", "hahah")
.build();
OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
.setInputData(inputData)
.build();
Worker中接收數(shù)據(jù)夕吻,并在任務執(zhí)行完成后,向WorkManager傳遞數(shù)據(jù)繁仁。
@Override
public Result doWork()
{
//接收外面?zhèn)鬟f進來的數(shù)據(jù)
String inputData1 = getInputData().getString("input_data");
String inputData2 = getInputData().getString("input_data2");
// 任務執(zhí)行完成后返回數(shù)據(jù)
Data outputData = new Data.Builder()
.putString("output_data", "Task Success!")
.build();
return Result.success(outputData);
}
WorkManager通過LiveData的WorkInfo.getOutputData()涉馅,得到從Worker傳遞過來的數(shù)據(jù)。
WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadWorkRequest.getId()).observe(MainActivity.this, new Observer<WorkInfo>()
{
@Override
public void onChanged(WorkInfo workInfo)
{
if (workInfo != null && workInfo.getState() == WorkInfo.State.SUCCEEDED)
{
String outputData = workInfo.getOutputData().getString("output_data");
}
}
});
注意:Data只能用于傳遞一些小的基本類型數(shù)據(jù)黄虱,且數(shù)據(jù)最大不能超過10kb稚矿。
8.周期任務PeriodicWorkRequest。前面提到過捻浦,WorkRequest的兩種實現(xiàn)OneTimeWorkRequest和PeriodicWorkRequest晤揣,分別對應的是一次性任務和周期性任務。一次性任務朱灿,即任務在成功完成后昧识,便徹底結(jié)束。而周期性任務則會按照設(shè)定的時間定期執(zhí)行盗扒。二者使用起來沒有太大差別跪楞。
需要注意的是:周期性任務的間隔時間不能小于15分鐘。
PeriodicWorkRequest uploadWorkRequest = new PeriodicWorkRequest.Builder(UploadLogWorker.class, 15, TimeUnit.MINUTES)
.setConstraints(constraints)
.addTag(TAG)
.build();
9.任務鏈侣灶。如果你有一系列的任務需要順序執(zhí)行习霹,那么可以利用WorkManager.beginWith().then().then()...enqueue()方法。例如:我們在上傳數(shù)據(jù)之前炫隶,需要先對數(shù)據(jù)進行壓縮。
WorkManager.getInstance(this).beginWith(compressWorkRequest).then(uploadWorkRequest).enqueue();
假設(shè)在上傳數(shù)據(jù)之前阎曹,除了壓縮數(shù)據(jù)伪阶,還需要更新本地數(shù)據(jù)煞檩。壓縮與更新本地數(shù)據(jù)二者沒有順序,但與上傳數(shù)據(jù)存在先后順序栅贴。
WorkManager.getInstance(this).beginWith(compressWorkRequest, updateLocalWorkRequest).then(uploadWorkRequest).enqueue();
假設(shè)有更復雜的任務鏈斟湃,你還可以考慮使用WorkContinuation.combine()方法,將任務鏈組合起來檐薯。
WorkContinuation workContinuation1 = WorkManager.getInstance(this).beginWith(WorkRequestA).then(WorkRequestB);
WorkContinuation workContinuation2 = WorkManager.getInstance(this).beginWith(WorkRequestC).then(WorkRequestD);
List<WorkContinuation> taskList = new ArrayList<>();
taskList.add(workContinuation1);
taskList.add(workContinuation2);
WorkContinuation.combine(taskList).then(WorkRequestE).enqueue();