Android Jetpack庫重要組件WorkManager的使用

前言

WorkManager是Jetpack很重要的一個組件; 本篇我們就先來講講它是如何使用的,在講解之前我們先瞭解關於後臺處理的一些痛點

後臺處理指南

我們知道每個 Android 應用都有一個主線程,它負責處理界面(包括測量和繪制視圖)、協調用戶互動以及接收生命周期事件; 如果有太多工作在主線程中進行,則應用可能會掛起或運行速度變慢,從而導致用戶體驗不佳。任何長時間運行的計算和操作(例如解碼位圖、訪問磁盤或執行網絡請求)都應在單獨的後臺線程上完成

一般來說,任何所需時間超過幾毫秒的任務都應該分派到後臺線程; 在用戶與應用積極互動時,可能需要執行幾項這樣的任務;即使在用戶沒有積極使用應用時,應用可能也需要運行一些任務(例如,定期與後端服務器同步或定期從應用內提取新內容)

後臺處理面臨的挑戰

後臺任務會使用設備的有限資源,例如 RAM 和電池電量; 如果處理不當,可能會導致用戶體驗不佳;為瞭最大限度地延長電池續航時間並強制推行良好的應用行為,Android 會在應用(或前臺服務通知)對用戶不可見時,限制後臺工作;為此Google在不同平臺上逐步的改進

Android 6.0(API 級別 23)引入瞭低電耗模式和應用待機模式

低電耗模式會在未插接設備的電源,在屏幕關閉的情況下,讓設備在一段時間內保持不活動狀態,那麼設備就會進入低電耗模式; 在低電耗模式下,系統會嘗試通過限制應用訪問占用大量網絡和 CPU 資源的服務來節省電量。它還會阻止應用訪問網絡,並延遲其作業、同步和標準鬧鐘

系統會定期退出低電耗模式一小段時間,讓應用完成其延遲的活動; 在此維護期內,系統會運行所有待處理的同步、作業和鬧鐘,並允許應用訪問網絡。在每個維護期結束時,系統會再次進入低電耗模式,暫停網絡訪問並推遲作業、同步和鬧鐘

隨著時間的推移,系統安排維護期的次數越來越少,這有助於在設備未連接至充電器的情況下長期處於不活動狀態時降低耗電量; 一旦用戶通過移動設備、打開屏幕或連接至充電器喚醒設備,系統就會立即退出低電耗模式,並且所有應用都會恢復正常活動

應用待機模式允許系統判定應用在用戶未主動使用它時是否處於閑置狀態; 當用戶有一段時間未觸摸應用時,系統便會作出此判定;當用戶將設備插入電源時,系統會從待機狀態釋放應用,允許它們自由訪問網絡並執行任何待處理的作業和同步。如果設備長時間處於閑置狀態,系統將允許閑置應用訪問網絡,頻率大約每天一次

低電耗模式和應用待機模式管理在 Android 6.0 或更高版本上運行的所有應用的行為,無論它們是否專用於 API 級別 23

Android 7.0(API 級別 24)限制瞭隱式廣播

引入瞭隨時隨地使用低電耗模式; 使得低電耗模式又前進瞭一步,隨時隨地可以省電;隻要屏幕關閉瞭一段時間,且設備未插入電源,低電耗模式就會對應用使用熟悉的 CPU 和網絡限制;這意味著用戶即使將設備放入口袋裡也可以省電

Android 8.0(API 級別 26)進一步限制後臺行為

例如在後臺獲取位置信息和釋放緩存的喚醒鎖定

Android 9.0(API 級別 28)引入瞭應用待機存儲分區

通過它,系統會根據應用使用模式動態確定應用資源請求的優先級; 應用待機存儲分區有助於系統根據應用的使用時間新近度和使用頻率對應用資源請求確定優先級

根據應用使用模式,每個應用都會被放置在五個優先級存儲分區之一中; 系統會根據應用所在的存儲分區限制每個應用可用的設備資源

  • Android 6.0(API 級別 23)引入瞭低電耗模式和應用待機模式

低電耗模式會在屏幕處於關閉狀態且設備處於靜止狀態時限制應用行為; 應用待機模式會將未使用的應用置於一種特殊狀態,進入這種狀態後,應用的網絡訪問、作業和同步會受到限制

  • Android 7.0(API 級別 24)限制瞭隱式廣播,並引入瞭隨時隨地使用低電耗模式
  • Android 8.0(API 級別 26)進一步限制瞭後臺行為,例如在後臺獲取位置信息和釋放緩存的喚醒鎖定
  • Android 9(API 級別 28)引入瞭應用待機存儲分區,通過它,系統會根據應用使用模式動態確定應用資源請求的優先級

如何選擇合適的後臺解決方案

下面有一張圖完美的解答瞭這個問題

  • 從上圖我們可以清晰的瞭解如何選擇後臺解決方案,如果是一個長時間的http下載的話就使用DownloadManager
  • 否則的話就看是不是一個可以延遲的任務,如果不可以就使用Foreground service
  • 如果是的話就看是不是可以由系統條件觸發,如果是的話就使用WorkManager
  • 如果不是就看是不是需要在一個固定的時間執行這個任務,如果是的話就使用AlarmManager
  • 如果不是的話就使用WorkManager

WorkManager概述

  • 使用 WorkManager API 可以輕松地調度可延遲的工作以及預計即使您的設備或應用重啟也會運行的工作,即使在應用退出或設備重啟時仍應運行的可延遲異步任務
  • 最高向後兼容到 API 14
  • 在運行 API 23 及以上級別的設備上使用 JobScheduler
  • 在運行 API 14-22 的設備上結合使用 BroadcastReceiver 和 AlarmManager
  • 添加網絡可用性或充電狀態等工作約束
  • 調度一次性或周期性異步任務
  • 監控和管理計劃任務
  • 將任務鏈接起來
  • 確保任務執行,即使應用或設備重啟也同樣執行任務
  • 遵循低電耗模式等省電功能

WorkManager使用

1 聲明依賴項

dependencies {
  def work_version = "2.3.1"

    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"

    // Kotlin + coroutines
    implementation "androidx.work:work-runtime-ktx:$work_version"

    // optional – RxJava2 support
    implementation "androidx.work:work-rxjava2:$work_version"

    // optional – GCMNetworkManager support
    implementation "androidx.work:work-gcm:$work_version"

    // optional – Test helpers
    androidTestImplementation "androidx.work:work-testing:$work_version"
}

2 自定義一個繼承自Worker的類

重寫doWork方法,或者使用協程的話,得繼承自CoroutineWorker。doWork方法有一個返回值,來標記任務是否成功或者是否要retry; 返回值有三種,分別是Result.success(),Result.failure(),Result.retry()

執行成功返回Result.success() 執行失敗返回Result.failure() 需要重新執行返回Result.retry()

override fun doWork(): Result {
    for (i in 1..3) {
        Thread.sleep(500)
        Log.i("aaa", "count: $i parameter: ${inputData.getString("parameter1")}")
    }
    return Result.success(Data.Builder().putString("result1", "value of result1").build())
}

3 選擇worker執行的條件

//添加約束
val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .setRequiresBatteryNotLow(false)
                .setRequiresCharging(false)
                .setRequiresDeviceIdle(false)
                .setRequiresStorageNotLow(false)
                .build()
  //對一次性執行添加約束,如果返回faliure或者retry的話就在適當的約束條件下執行worker
  val request = OneTimeWorkRequestBuilder<CountWorker>()
                .setConstraints(constraints)
                .setInputData(Data.Builder().putString("parameter1", "value of parameter1").build())
                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.HOURS)
                .build()
 WorkManager.getInstance(context).enqueue(request)
//或者定時每隔一個小時執行任務  
val periodicWorkRequest = PeriodicWorkRequest.Builder(AppsWorker::class.java,
                                    1, TimeUnit.HOURS)
                                     .setConstraints(constraints)
                                     .build();
 WorkManager.getInstance(context).enqueue(periodicWorkRequest)

需要註意的是類似於JobSceeduler,周期性執行的任務最少間隔時間不能小於15mins

4 下面貼出自定義worker類的全部源碼

class CountWorker(context: Context, parameters: WorkerParameters)
    : Worker(context, parameters) {
    companion object {
        fun enqueue(context: ComponentActivity) {
            val constraints = Constraints.Builder()
                    .setRequiredNetworkType(NetworkType.CONNECTED)
                    .setRequiresBatteryNotLow(false)
                    .setRequiresCharging(false)
                    .setRequiresDeviceIdle(false)
                    .setRequiresStorageNotLow(false)
                    .build()
            val request = OneTimeWorkRequestBuilder<CountWorker>()
            		//-----1-----添加約束
                    .setConstraints(constraints)
                    //-----2----- 傳入執行worker需要的數據
                    .setInputData(Data.Builder().putString("parameter1", "value of parameter1").build())
                    //-----3-----設置避退策略
                    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.HOURS)
                    .build()
             //-----4-----將任務添加到隊列中
            //WorkManager.getInstance(context).enqueue(request)
            //或者采用uniqueName執行
        	WorkManager.getInstance(context).beginUniqueWork("uniqueName", ExistingWorkPolicy.REPLACE, request).enqueue()
            //-----5-----對任務加入監聽
            WorkManager.getInstance(context).getWorkInfoByIdLiveData(request.id).observe(context, Observer {
            	//-----8----獲取doWork中傳入的參數
                Log.i("aaa", "workInfo ${it.outputData.getString("result1")} ${it.state}: ")
            })
            //或者采用tag的方式監聽狀態
            WorkManager.getInstance(context).getWorkInfosByTagLiveData("tagCountWorker").observe(context, Observer {
            Log.i("aaa", "workInfo tag-- ${it[0].outputData.getString("result1")} ${it[0].state}: ")
        	})
        	//或者采用uniqueName的形式監聽任務執行的狀態
        	WorkManager.getInstance(context).getWorkInfosForUniqueWorkLiveData("uniqueName").observe(context, Observer {
            Log.i("aaa", "workInfo uniqueName-- ${it[0].outputData.getString("result1")} ${it[0].state}: ")
       	 })
        }
    }
    override fun doWork(): Result {
        for (i in 1..3) {
            Thread.sleep(500)
            //-----6-----獲取傳入的參數
            Log.i("aaa", "count: $i parameter: ${inputData.getString("parameter1")}")
        }
       //-----7-----傳入返回的參數
        return Result.success(Data.Builder().putString("result1", "value of result1").build())
    }
}
  • 為瞭測試方便,我把執行的代碼寫在瞭enqueue中瞭,在enqueue中,我們首先在註釋1處添加瞭約束
  • 在註釋2處添加瞭執行worker需要的參數。這個參數可以在doWork中獲取到,如註釋6處所示;傳入的數據不能超過10kb
  • 註釋3處我們設置瞭避退策略,如果我們的一次性任務返回瞭retry,這裡就可以起作用瞭,避退策略有兩種方式。一種是指數級的EXPONENTIAL,還有一種是線性的LINEAR
  • 然後註釋4處將任務加入到隊列中,這裡僅僅是加入隊列,並不能保證執行,因為WorkManager主要的定位就是針對可延遲的任務,它需要根據添加的約束和系統自身的情況來做出什麼時間執行這個任務
  • 註釋5處可以根據request的id獲取到任務的執行狀態,返回值是一個LiveData類型的,並將其加入到生命周期觀察序列中;所以當任務的執行狀態發生變化的時候就會在註釋8處打印信息
  • 我們還可以在任務執行結束的時候傳入需要返回的參數,但是隻能在success和failure的時候傳入,傳入的數據可以再註釋8處獲取

5 執行任務的方式

如果我們想要以鏈式執行一系列任務,如圖所示,我們可以使用:

 WorkManager.getInstance(context).beginWith(requestA).then(requestB).enqueue()

如果我們的任務A和任務B之間沒有關系,需要在任務A和B都完成的情況下執行任務C的話,如圖所示,這時候就可以這麼調用:

WorkManager.getInstance(context).beginWith(listOf(requestA,requestB)).then(requestC).enqueue()

如果我們想要AB和CD並行的執行完,然後執行E的話,如圖所示,可以采用:

val continuation1 = WorkManager.getInstance(context).beginWith(requestA).then(requestB)
val continuation2 = WorkManager.getInstance(context).beginWith(requestC).then(requestD)
WorkContinuation.combine(listOf(continuation1, continuation2)).then(requestE).enqueue()

需要註意的是任務一旦發起,任務是可以保證一定會被執行的,就算退出應用,甚至重啟手機都阻止不瞭他;但可能由於添加瞭環境約束等原因會在不確定的時間執行罷瞭

6 取消任務的執行

//通過request.id取消任務
WorkManager.getInstance(context).cancelWorkById(request.id)
//通過request的tag取消任務
WorkManager.getInstance(context).cancelAllWorkByTag("tag")
//通過request的uniqueName取消任務
WorkManager.getInstance(context).cancelUniqueWork("uniqueName")
//取消所有的work任務
WorkManager.getInstance(context).cancelAllWork()

以上可以看到可以通過四種方式取消任務

到此這篇關於Android Jetpack庫重要組件WorkManager的使用的文章就介紹到這瞭,更多相關Android Jetpack WorkManager內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: