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