Android?Jetpack庫重要組件WorkManager的使用
前言
WorkManager是Jetpack很重要的一個組件; 本篇我們就先來講講它是如何使用的,在講解之前我們先了解關(guān)于后臺處理的一些痛點(diǎn)
后臺處理指南
我們知道每個 Android 應(yīng)用都有一個主線程,它負(fù)責(zé)處理界面(包括測量和繪制視圖)、協(xié)調(diào)用戶互動以及接收生命周期事件; 如果有太多工作在主線程中進(jìn)行,則應(yīng)用可能會掛起或運(yùn)行速度變慢,從而導(dǎo)致用戶體驗不佳。任何長時間運(yùn)行的計算和操作(例如解碼位圖、訪問磁盤或執(zhí)行網(wǎng)絡(luò)請求)都應(yīng)在單獨(dú)的后臺線程上完成
一般來說,任何所需時間超過幾毫秒的任務(wù)都應(yīng)該分派到后臺線程; 在用戶與應(yīng)用積極互動時,可能需要執(zhí)行幾項這樣的任務(wù);即使在用戶沒有積極使用應(yīng)用時,應(yīng)用可能也需要運(yùn)行一些任務(wù)(例如,定期與后端服務(wù)器同步或定期從應(yīng)用內(nèi)提取新內(nèi)容)
后臺處理面臨的挑戰(zhàn)
后臺任務(wù)會使用設(shè)備的有限資源,例如 RAM 和電池電量; 如果處理不當(dāng),可能會導(dǎo)致用戶體驗不佳;為了最大限度地延長電池續(xù)航時間并強(qiáng)制推行良好的應(yīng)用行為,Android 會在應(yīng)用(或前臺服務(wù)通知)對用戶不可見時,限制后臺工作;為此Google在不同平臺上逐步的改進(jìn)
Android 6.0(API 級別 23)引入了低電耗模式和應(yīng)用待機(jī)模式
低電耗模式會在未插接設(shè)備的電源,在屏幕關(guān)閉的情況下,讓設(shè)備在一段時間內(nèi)保持不活動狀態(tài),那么設(shè)備就會進(jìn)入低電耗模式; 在低電耗模式下,系統(tǒng)會嘗試通過限制應(yīng)用訪問占用大量網(wǎng)絡(luò)和 CPU 資源的服務(wù)來節(jié)省電量。它還會阻止應(yīng)用訪問網(wǎng)絡(luò),并延遲其作業(yè)、同步和標(biāo)準(zhǔn)鬧鐘
系統(tǒng)會定期退出低電耗模式一小段時間,讓應(yīng)用完成其延遲的活動; 在此維護(hù)期內(nèi),系統(tǒng)會運(yùn)行所有待處理的同步、作業(yè)和鬧鐘,并允許應(yīng)用訪問網(wǎng)絡(luò)。在每個維護(hù)期結(jié)束時,系統(tǒng)會再次進(jìn)入低電耗模式,暫停網(wǎng)絡(luò)訪問并推遲作業(yè)、同步和鬧鐘
隨著時間的推移,系統(tǒng)安排維護(hù)期的次數(shù)越來越少,這有助于在設(shè)備未連接至充電器的情況下長期處于不活動狀態(tài)時降低耗電量; 一旦用戶通過移動設(shè)備、打開屏幕或連接至充電器喚醒設(shè)備,系統(tǒng)就會立即退出低電耗模式,并且所有應(yīng)用都會恢復(fù)正?;顒?/p>
應(yīng)用待機(jī)模式允許系統(tǒng)判定應(yīng)用在用戶未主動使用它時是否處于閑置狀態(tài); 當(dāng)用戶有一段時間未觸摸應(yīng)用時,系統(tǒng)便會作出此判定;當(dāng)用戶將設(shè)備插入電源時,系統(tǒng)會從待機(jī)狀態(tài)釋放應(yīng)用,允許它們自由訪問網(wǎng)絡(luò)并執(zhí)行任何待處理的作業(yè)和同步。如果設(shè)備長時間處于閑置狀態(tài),系統(tǒng)將允許閑置應(yīng)用訪問網(wǎng)絡(luò),頻率大約每天一次
低電耗模式和應(yīng)用待機(jī)模式管理在 Android 6.0 或更高版本上運(yùn)行的所有應(yīng)用的行為,無論它們是否專用于 API 級別 23
Android 7.0(API 級別 24)限制了隱式廣播
引入了隨時隨地使用低電耗模式; 使得低電耗模式又前進(jìn)了一步,隨時隨地可以省電;只要屏幕關(guān)閉了一段時間,且設(shè)備未插入電源,低電耗模式就會對應(yīng)用使用熟悉的 CPU 和網(wǎng)絡(luò)限制;這意味著用戶即使將設(shè)備放入口袋里也可以省電
Android 8.0(API 級別 26)進(jìn)一步限制后臺行為
例如在后臺獲取位置信息和釋放緩存的喚醒鎖定
Android 9.0(API 級別 28)引入了應(yīng)用待機(jī)存儲分區(qū)
通過它,系統(tǒng)會根據(jù)應(yīng)用使用模式動態(tài)確定應(yīng)用資源請求的優(yōu)先級; 應(yīng)用待機(jī)存儲分區(qū)有助于系統(tǒng)根據(jù)應(yīng)用的使用時間新近度和使用頻率對應(yīng)用資源請求確定優(yōu)先級
根據(jù)應(yīng)用使用模式,每個應(yīng)用都會被放置在五個優(yōu)先級存儲分區(qū)之一中; 系統(tǒng)會根據(jù)應(yīng)用所在的存儲分區(qū)限制每個應(yīng)用可用的設(shè)備資源
- Android 6.0(API 級別 23)引入了低電耗模式和應(yīng)用待機(jī)模式
低電耗模式會在屏幕處于關(guān)閉狀態(tài)且設(shè)備處于靜止?fàn)顟B(tài)時限制應(yīng)用行為; 應(yīng)用待機(jī)模式會將未使用的應(yīng)用置于一種特殊狀態(tài),進(jìn)入這種狀態(tài)后,應(yīng)用的網(wǎng)絡(luò)訪問、作業(yè)和同步會受到限制
- Android 7.0(API 級別 24)限制了隱式廣播,并引入了隨時隨地使用低電耗模式
- Android 8.0(API 級別 26)進(jìn)一步限制了后臺行為,例如在后臺獲取位置信息和釋放緩存的喚醒鎖定
- Android 9(API 級別 28)引入了應(yīng)用待機(jī)存儲分區(qū),通過它,系統(tǒng)會根據(jù)應(yīng)用使用模式動態(tài)確定應(yīng)用資源請求的優(yōu)先級
如何選擇合適的后臺解決方案
下面有一張圖完美的解答了這個問題
- 從上圖我們可以清晰的了解如何選擇后臺解決方案,如果是一個長時間的http下載的話就使用DownloadManager
- 否則的話就看是不是一個可以延遲的任務(wù),如果不可以就使用Foreground service
- 如果是的話就看是不是可以由系統(tǒng)條件觸發(fā),如果是的話就使用WorkManager
- 如果不是就看是不是需要在一個固定的時間執(zhí)行這個任務(wù),如果是的話就使用AlarmManager
- 如果不是的話就使用WorkManager
WorkManager概述
- 使用 WorkManager API 可以輕松地調(diào)度可延遲的工作以及預(yù)計即使您的設(shè)備或應(yīng)用重啟也會運(yùn)行的工作,即使在應(yīng)用退出或設(shè)備重啟時仍應(yīng)運(yùn)行的可延遲異步任務(wù)
- 最高向后兼容到 API 14
- 在運(yùn)行 API 23 及以上級別的設(shè)備上使用 JobScheduler
- 在運(yùn)行 API 14-22 的設(shè)備上結(jié)合使用 BroadcastReceiver 和 AlarmManager
- 添加網(wǎng)絡(luò)可用性或充電狀態(tài)等工作約束
- 調(diào)度一次性或周期性異步任務(wù)
- 監(jiān)控和管理計劃任務(wù)
- 將任務(wù)鏈接起來
- 確保任務(wù)執(zhí)行,即使應(yīng)用或設(shè)備重啟也同樣執(zhí)行任務(wù)
- 遵循低電耗模式等省電功能
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方法,或者使用協(xié)程的話,得繼承自CoroutineWorker。doWork方法有一個返回值,來標(biāo)記任務(wù)是否成功或者是否要retry; 返回值有三種,分別是Result.success(),Result.failure(),Result.retry()
執(zhí)行成功返回Result.success() 執(zhí)行失敗返回Result.failure() 需要重新執(zhí)行返回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執(zhí)行的條件
//添加約束 val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(false) .setRequiresCharging(false) .setRequiresDeviceIdle(false) .setRequiresStorageNotLow(false) .build() //對一次性執(zhí)行添加約束,如果返回faliure或者retry的話就在適當(dāng)?shù)募s束條件下執(zhí)行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) //或者定時每隔一個小時執(zhí)行任務(wù) val periodicWorkRequest = PeriodicWorkRequest.Builder(AppsWorker::class.java, 1, TimeUnit.HOURS) .setConstraints(constraints) .build(); WorkManager.getInstance(context).enqueue(periodicWorkRequest)
需要注意的是類似于JobSceeduler
,周期性執(zhí)行的任務(wù)最少間隔時間不能小于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----- 傳入執(zhí)行worker需要的數(shù)據(jù) .setInputData(Data.Builder().putString("parameter1", "value of parameter1").build()) //-----3-----設(shè)置避退策略 .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.HOURS) .build() //-----4-----將任務(wù)添加到隊列中 //WorkManager.getInstance(context).enqueue(request) //或者采用uniqueName執(zhí)行 WorkManager.getInstance(context).beginUniqueWork("uniqueName", ExistingWorkPolicy.REPLACE, request).enqueue() //-----5-----對任務(wù)加入監(jiān)聽 WorkManager.getInstance(context).getWorkInfoByIdLiveData(request.id).observe(context, Observer { //-----8----獲取doWork中傳入的參數(shù) Log.i("aaa", "workInfo ${it.outputData.getString("result1")} ${it.state}: ") }) //或者采用tag的方式監(jiān)聽狀態(tài) WorkManager.getInstance(context).getWorkInfosByTagLiveData("tagCountWorker").observe(context, Observer { Log.i("aaa", "workInfo tag-- ${it[0].outputData.getString("result1")} ${it[0].state}: ") }) //或者采用uniqueName的形式監(jiān)聽任務(wù)執(zhí)行的狀態(tài) 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-----獲取傳入的參數(shù) Log.i("aaa", "count: $i parameter: ${inputData.getString("parameter1")}") } //-----7-----傳入返回的參數(shù) return Result.success(Data.Builder().putString("result1", "value of result1").build()) } }
- 為了測試方便,我把執(zhí)行的代碼寫在了enqueue中了,在enqueue中,我們首先在注釋1處添加了約束
- 在注釋2處添加了執(zhí)行worker需要的參數(shù)。這個參數(shù)可以在doWork中獲取到,如注釋6處所示;傳入的數(shù)據(jù)不能超過10kb
- 注釋3處我們設(shè)置了避退策略,如果我們的一次性任務(wù)返回了retry,這里就可以起作用了,避退策略有兩種方式。一種是指數(shù)級的EXPONENTIAL,還有一種是線性的LINEAR
- 然后注釋4處將任務(wù)加入到隊列中,這里僅僅是加入隊列,并不能保證執(zhí)行,因為WorkManager主要的定位就是針對可延遲的任務(wù),它需要根據(jù)添加的約束和系統(tǒng)自身的情況來做出什么時間執(zhí)行這個任務(wù)
- 注釋5處可以根據(jù)request的id獲取到任務(wù)的執(zhí)行狀態(tài),返回值是一個LiveData類型的,并將其加入到生命周期觀察序列中;所以當(dāng)任務(wù)的執(zhí)行狀態(tài)發(fā)生變化的時候就會在注釋8處打印信息
- 我們還可以在任務(wù)執(zhí)行結(jié)束的時候傳入需要返回的參數(shù),但是只能在success和failure的時候傳入,傳入的數(shù)據(jù)可以再注釋8處獲取
5 執(zhí)行任務(wù)的方式
如果我們想要以鏈?zhǔn)綀?zhí)行一系列任務(wù),如圖所示,我們可以使用:
WorkManager.getInstance(context).beginWith(requestA).then(requestB).enqueue()
如果我們的任務(wù)A和任務(wù)B之間沒有關(guān)系,需要在任務(wù)A和B都完成的情況下執(zhí)行任務(wù)C的話,如圖所示,這時候就可以這么調(diào)用:
WorkManager.getInstance(context).beginWith(listOf(requestA,requestB)).then(requestC).enqueue()
如果我們想要AB和CD并行的執(zhí)行完,然后執(zhí)行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()
需要注意的是任務(wù)一旦發(fā)起,任務(wù)是可以保證一定會被執(zhí)行的,就算退出應(yīng)用,甚至重啟手機(jī)都阻止不了他;但可能由于添加了環(huán)境約束等原因會在不確定的時間執(zhí)行罷了
6 取消任務(wù)的執(zhí)行
//通過request.id取消任務(wù) WorkManager.getInstance(context).cancelWorkById(request.id) //通過request的tag取消任務(wù) WorkManager.getInstance(context).cancelAllWorkByTag("tag") //通過request的uniqueName取消任務(wù) WorkManager.getInstance(context).cancelUniqueWork("uniqueName") //取消所有的work任務(wù) WorkManager.getInstance(context).cancelAllWork()
以上可以看到可以通過四種方式取消任務(wù)
到此這篇關(guān)于Android Jetpack庫重要組件WorkManager的使用的文章就介紹到這了,更多相關(guān)Android Jetpack WorkManager內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android Studio+MAT實戰(zhàn)內(nèi)存泄漏
這篇文章主要介紹了Android Studio+MAT實戰(zhàn)內(nèi)存泄漏的相關(guān)技術(shù)內(nèi)容,并在需要注意的地方做了提示,需要參考學(xué)習(xí)下吧。2017-12-12PopupWindow?RecyclerView實現(xiàn)下拉選擇Spinner示例解析
這篇文章主要介紹了PopupWindow?RecyclerView實現(xiàn)下拉選擇Spinner示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07Android?RecyclerLineChart實現(xiàn)圖表繪制教程
這篇文章主要為大家介紹了Android?RecyclerLineChart實現(xiàn)圖表繪制教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12詳細(xì)介紹Android-Room數(shù)據(jù)庫的使用
這篇文章主要介紹了詳細(xì)介紹Android-Room數(shù)據(jù)庫的使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03