Android進(jìn)階之后臺(tái)任務(wù)和定時(shí)服務(wù),放棄AlarmManager全面擁抱WorkManager
本文轉(zhuǎn)載自微信公眾號(hào)「Android開(kāi)發(fā)編程」,作者Android開(kāi)發(fā)編程。轉(zhuǎn)載本文請(qǐng)聯(lián)系A(chǔ)ndroid開(kāi)發(fā)編程公眾號(hào)。
前言
WorkManager是google提供的異步執(zhí)行任務(wù)的管理框架,會(huì)根據(jù)手機(jī)的API版本和應(yīng)用程序的狀態(tài)來(lái)選擇適當(dāng)?shù)姆绞綀?zhí)行任務(wù);
當(dāng)應(yīng)用在運(yùn)行的時(shí)候會(huì)在應(yīng)用的進(jìn)程中開(kāi)一條線程來(lái)執(zhí)行任務(wù),當(dāng)退出應(yīng)用時(shí),WorkManager會(huì)選擇根據(jù)設(shè)備的API版本使用適合的算法調(diào)用JobScheduler或者Firebase JobDispatcher,或者AlarmManager來(lái)執(zhí)行任務(wù);
今天我們來(lái)介紹下;
一、WorkManager介紹
1、什么是WorkManager
- WorkManager 是一個(gè) API,使您可以輕松調(diào)度那些即使在退出應(yīng)用或重啟設(shè)備時(shí)仍應(yīng)運(yùn)行的可延期異步任務(wù)。WorkManager API 是一個(gè)針對(duì)先前的 Android 后臺(tái)調(diào)度 API(包括 FirebaseJobDispatcher、GcmNetworkManager 和 JobScheduler)的合適的建議替換組件。WorkManager 在新版一致性 API 中整合了其前身的功能,該 API 支持 API 級(jí)別 14,同時(shí)可保證電池續(xù)航時(shí)間;
- WorkManager 適用于可延期工作,即不需要立即運(yùn)行但需要可靠運(yùn)行的工作,即使用戶退出或設(shè)備重啟也不受影響。例如:向后端服務(wù)發(fā)送日志或分析數(shù)據(jù) 定期將應(yīng)用數(shù)據(jù)與服務(wù)器同步;
- WorkManager 不適用于應(yīng)用進(jìn)程結(jié)束時(shí)能夠安全終止的運(yùn)行中后臺(tái)工作,也不適用于需要立即執(zhí)行的工作;
2、優(yōu)點(diǎn)
- 向下兼容至api 14;
- 可以添加任務(wù)執(zhí)行的約束條件,比如說(shuō) 延遲執(zhí)行,是否在低電量模式下執(zhí)行,是否在充電模式下執(zhí)行,是否在設(shè)備空閑時(shí)執(zhí)行等等;
- 調(diào)度一次性或周期性異步任務(wù);
- 監(jiān)管任務(wù),可以隨時(shí)取消任務(wù);
- 將任務(wù)鏈接起來(lái),比如說(shuō)執(zhí)行可以指定多個(gè)任務(wù)的執(zhí)行順序;
- 確保任務(wù)執(zhí)行,即使應(yīng)用或設(shè)備重啟也同樣執(zhí)行任務(wù);
二、WorkManager使用
1、添加依賴(lài)
- //根據(jù)項(xiàng)目需要自行添加依賴(lài),不需要全部添加
- dependencies {
- def work_version = "2.3.1"
- // (Java only)
- implementation "androidx.work:work-runtime:$work_version"//java 語(yǔ)言選這個(gè)
- // Kotlin + coroutines
- implementation "androidx.work:work-runtime-ktx:$work_version"//kotlin 選這個(gè)
- }
2、 創(chuàng)建一個(gè)后臺(tái)任務(wù)
上傳圖片:
- class UploadPicWork(
- private val context: Context,
- private val workerParameters: WorkerParameters
- ) :
- Worker(context, workerParameters) {
- override fun doWork(): Result {
- uploadPic()//具體上傳圖片的邏輯
- return Result.success()
- }
- }
2.1 創(chuàng)建一個(gè)workrequest
- //此處的 UploadPicWork 就是之前創(chuàng)建的任務(wù)
- val uploadPicWork = OneTimeWorkRequestBuilder<UploadPicWork>()
- .setConstraints(triggerContentMaxDelay).build()
2.2執(zhí)行任務(wù)
- //此處的 uploadPicWork 就是前一步創(chuàng)建的 workrequest
- WorkManager.getInstance(myContext).enqueue(uploadPicWork)
3、復(fù)雜的任務(wù)處理
3.1 創(chuàng)建任務(wù)執(zhí)行的約束條件
- //注意 以下條件都是 && 的關(guān)系
- val triggerContentMaxDelay =
- Constraints.Builder()
- .setRequiredNetworkType(NetworkType.CONNECTED)//網(wǎng)絡(luò)鏈接的時(shí)候使用,避免各種網(wǎng)絡(luò)判斷,省時(shí)省力
- .setRequiresDeviceIdle(false)//是否在設(shè)備空閑的時(shí)候執(zhí)行
- .setRequiresBatteryNotLow(true)//是否在低電量的時(shí)候執(zhí)行
- .setRequiresStorageNotLow(true)//是否在內(nèi)存不足的時(shí)候執(zhí)行
- .setRequiresCharging(true)//是否時(shí)充電的時(shí)候執(zhí)行
- .setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS)//延遲執(zhí)行
- .build()
3.2 為任務(wù)添加約束條件
- val uploadPicWork =
- OneTimeWorkRequestBuilder<UploadPicWork>()
- .setConstraints(triggerContentMaxDelay)//約束條件
- .build()
- WorkManager.getInstance(myContext).enqueue(uploadPicWork)//執(zhí)行
3.3為worker 傳遞參數(shù)
- //可以采用這種方式傳遞參數(shù)
- val UploadPicWork =
- OneTimeWorkRequestBuilder<UploadPicWork>()
- //此處set input data 需要的參數(shù) 是一個(gè)Data對(duì)象,注意只可以添加一次,如果有多個(gè)參數(shù)需要傳遞,可以封裝成一個(gè)data 數(shù)據(jù)類(lèi)
- .setInputData(workDataOf("params_tag" to "params"))
- .setConstraints(triggerContentMaxDelay).build()
3.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()
- }
- }
4、Worker 的狀態(tài)
在 doWork 函數(shù)中,我們返回的 Result.success(); 我們默認(rèn) ,任務(wù) uploadPic 函數(shù)順利的執(zhí)行完成了,所以返回了 success 狀態(tài),但是在實(shí)際開(kāi)發(fā)過(guò)程中 可以能因?yàn)楦鞣N各樣的問(wèn)題會(huì)導(dǎo)致 失敗,這時(shí)候就不能返回success了;
Worker 的各種狀態(tài)說(shuō)明
- 如果有尚未完成的前提性工作,則工作處于 BLOCKED State;
- 如果工作能夠在滿足 約束條件 和時(shí)機(jī)條件后立即運(yùn)行,則被視為處于 ENQUEUED 狀態(tài);
- 當(dāng) Worker 在活躍地執(zhí)行時(shí),其處于 RUNNING State;
- 如果 Worker 返回 Result.success(),則被視為處于 SUCCEEDED 狀態(tài)。這是一種終止 State;只有 OneTimeWorkRequest 可以進(jìn)入這種 State;
- 如果 Worker 返回 Result.failure(),則被視為處于 FAILED 狀態(tài)。這也是一個(gè)終止 State;只有 OneTimeWorkRequest 可以進(jìn)入這種 State。所有依賴(lài)工作也會(huì)被標(biāo)記為 FAILED,并且不會(huì)運(yùn)行;
- 當(dāng)取消尚未終止的 WorkRequest 時(shí),它會(huì)進(jìn)入 CANCELLED State。所有依賴(lài)工作也會(huì)被標(biāo)記為 CANCELLED,并且不會(huì)運(yùn)行;
5、觀察Worker 的狀態(tài)
獲取 WorkInfo
聽(tīng)過(guò) id 獲取,可以聽(tīng)過(guò) WorkManager.getWorkInfoById(UUID) 或 WorkManager.getWorkInfoByIdLiveData(UUID) 來(lái)通過(guò) WorkRequest id 來(lái)獲取 WorkInfo;
- WorkManager.getInstance(this)
- .getWorkInfoByIdLiveData(UploadPicWork.id)// 通過(guò)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}")
- }
- }
- })
通過(guò) tag 獲取,可以利用 WorkManager.getWorkInfosByTag(String) 或 WorkManager.getWorkInfosByTagLiveData(String) 來(lái)通過(guò) WorkRequest 的 WorkInfo 對(duì)象;
- //要通過(guò) tag 獲取,則需要先設(shè)置 tag
- val UploadPicWork =
- OneTimeWorkRequestBuilder<UploadPicWork>()
- .setInputData(workDataOf("params_tag" to "params"))//傳遞參數(shù)
- .setConstraints(triggerContentMaxDelay)//設(shè)置約束條件
- .addTag("tag")//設(shè)置tag
- .build()
- //獲取 workInfo
- WorkManager.getInstance(this)
- .getWorkInfosByTagLiveData("tag")
- .observe(this, Observer {it:List<WorkInfo>//此處返回的是一個(gè)集合,作為示例代碼,默認(rèn)只取 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]}")
- }
- }
- })
6、多個(gè)Worker 的順序執(zhí)行
您可以使用 WorkManager 創(chuàng)建工作鏈并為其排隊(duì)。工作鏈用于指定多個(gè)關(guān)聯(lián)任務(wù)并定義這些任務(wù)的運(yùn)行順序。當(dāng)您需要以特定的順序運(yùn)行多個(gè)任務(wù)時(shí),這尤其有用;
6.1先后順序執(zhí)行單個(gè)任務(wù)
比如說(shuō)有三個(gè)任務(wù)workA,workB,workC,并且執(zhí)行順序只能時(shí)workA---->workB---->workC可以用如下的方式處理;
- WorkManager.getInstance()
- .beginWith(workA)
- .then(workB) instance
- .then(workC)
- .enqueue();
上面的workA,workB,workC,都是WorkRequest的子類(lèi)實(shí)現(xiàn)對(duì)象。WorkManager會(huì)根據(jù)上面的先后順序來(lái)執(zhí)行workA,workB,workC,,但是如果執(zhí)行過(guò)程中三個(gè)任務(wù)中有一個(gè)失敗,整個(gè)執(zhí)行都會(huì)結(jié)束。并且返回Result.failure()
6.2先后順序執(zhí)行多個(gè)任務(wù)列
有時(shí)候可能要先執(zhí)行一組任務(wù),然后再執(zhí)行下一組任務(wù),可以使用下面的方式來(lái)完成。
- WorkManager.getInstance()
- // First, run all the A tasks (in parallel):
- .beginWith(Arrays.asList(workA1, workA2, workA3))
- // ...when all A tasks are finished, run the single B task:
- .then(workB)
- // ...then run the C tasks (in any order):
- .then(Arrays.asList(workC1, workC2))
- .enqueue();
7、 執(zhí)行重復(fù)任務(wù)
就是在給定的時(shí)間間隔內(nèi)定期執(zhí)行任務(wù),比如說(shuō) 每個(gè)一個(gè)小時(shí),上報(bào)位置信息,每個(gè)3個(gè)小時(shí)備份一個(gè)日志等等;
這個(gè)時(shí)間間隔不可低于15分鐘;
- 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)
8、取消任務(wù)執(zhí)行
通過(guò)任務(wù)的ID可以獲取任務(wù)從而取消任務(wù)。任務(wù)ID可以從WorkRequest中獲取;
cancelAllWork():取消所有任務(wù);
cancelAllWorkByTag(String tag):取消一組帶有相同標(biāo)簽的任務(wù);
cancelUniqueWork( String uniqueWorkName):取消唯一任務(wù);
- UUID compressionWorkId = compressionWork.getId();
- WorkManager.getInstance().cancelWorkById(compressionWorkId);
注意并不是所有的任務(wù)都可以取消,當(dāng)任務(wù)正在執(zhí)行時(shí)是不能取消的,當(dāng)然任務(wù)執(zhí)行完成了,取消也是意義的,也就是說(shuō)當(dāng)任務(wù)加入到ManagerWork的隊(duì)列中但是還沒(méi)有執(zhí)行時(shí)才可以取消;
9、使用WorkManager遇到的問(wèn)題
9.1使用PeriodicWorkRequest只執(zhí)行一次,并不重復(fù)執(zhí)行
- WorkManager instance= new PeriodicWorkRequest.Builder(PollingWorker.class, 10, TimeUnit.MINUTES)
- .build();
原因:PeriodicWorkRequest默認(rèn)的時(shí)間間隔是15分鐘如果設(shè)置的時(shí)間小于15分鐘,就會(huì)出現(xiàn)問(wèn)題;
解決方法:設(shè)置的默認(rèn)時(shí)間必須大于或等于15分鐘。另外要注意,就算設(shè)置的時(shí)間為15分鐘也不一定間隔15分鐘就執(zhí)行;
9.2在doWork()方法中更新UI導(dǎo)致崩潰
原因:doWork()方法是在WorkManager管理的后臺(tái)線程中執(zhí)行的,更新UI操作只能在主線程中進(jìn)行
總結(jié)
在這個(gè)物欲橫流人心浮躁的社會(huì),我們一起學(xué)習(xí)加油共勉