上一次咱們對Paging的應用進行了一次全面的分析,這一次咱們來聊聊WorkManager。java
若是你對Paging還未了解,推薦閱讀這篇文章:android
Paging在RecyclerView中的應用,有這一篇就夠了git
原本這一篇文章上週就可以發佈出來,但我寫文章有一個特色,都會結合具體的Demo來進行闡述,而WorkManager的Demo早就完成了,只是要結合文章一塊兒闡述實在須要時間,上週自身緣由也就延期了,想一想仍是寫代碼容易啊...😿😿github
哎呀很少說了,進入正題!數據庫
WorkManager是什麼?官方給的解釋是:它對可延期任務操做很是簡單,同時穩定性很是強,對於異步任務,即便App退出運行或者設備重啓,它都可以很好的保證任務的順利執行。api
因此關鍵點是簡單與穩定性。網絡
對於日常的使用,若是一個後臺任務在執行的過程當中,app忽然退出或者手機斷網,這時後臺任務將直接終止。app
典型的場景是:App的關注功能。若是用戶在弱網的狀況下點擊關注按鈕,此時用戶因爲某種緣由立刻退出了App,但關注的請求並無成功發送給服務端,那麼下次用戶再進入時,拿到的仍是以前未關注的狀態信息。這就產生了操做上的bug,下降了用戶的體驗,增長了用戶沒必要要的操做。框架
那麼該如何解決呢?很簡單,看WorkManager的定義,使用WorkManager就能夠輕鬆解決。這裏就再也不拓展實現代碼了,只要你繼續看完這篇文章,你就能輕鬆實現。異步
固然你不使用WorkManager也能實現,這就涉及到它的另外一個好處:簡單。若是你不使用WorkManager,你就要對不一樣API版本進行區分。
val service = ComponentName(this, MyJobService::class.java) val mJobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler val builder = JobInfo.Builder(jobId, serviceComponent) .setRequiredNetworkType(jobInfoNetworkType) .setRequiresCharging(false) .setRequiresDeviceIdle(false) .setExtras(extras).build() mJobScheduler.schedule(jobInfo)
經過JobScheduler來建立一個Job,一旦所設的條件達到,就會執行該Job。但JobScheduler是在API21加入的,同時在API21&22有一個系統Bug
這就意味着它只能用在API23及以上的版本
if (Build.VERSION.SDK_INT >= 23) { // use JobScheduler }
既然只能API23及以上才能使用JobScheduler,那麼在API23如下又該如何呢?
這時對於API23如下,可使用AlarmManager來進行任務的執行,同時結合BoradcastReceiver來進行任務的條件監聽,例如網絡的鏈接狀態、設備的啓動等。
看到這裏是否是開始頭大了呢,咱們開始的目的只是想作一個穩定性的後臺任務,最後發現竟然還要進行版本兼容。兼容性與實現性進一步加大。
那麼有沒有統一的實現方式呢?固然有,它就是WorkManager,它的核心原理使用的就是上面所分析的結合體。
他會結合版本自動使用最佳的實現方式,同時還會提供額外的便利操做,例如狀態監聽、鏈式請求等等。
WorkManager的使用,我將其分爲如下幾步:
下面咱們來經過Demo逐步瞭解。
WorkManager每個任務都是由Work構成,因此Work是任務具體執行的核心所在。既然是核心所在,你可能會認爲它會很是難實現,但偏偏相反,它的實現很是簡單,你只需實現它的doWork方法便可。例如咱們來實現一個清除相關目錄下的.png圖片的Work
class CleanUpWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { override fun doWork(): Result { val outputDir = File(applicationContext.filesDir, Constants.OUTPUT_PATH) if (outputDir.exists()) { val fileLists = outputDir.listFiles() for (file in fileLists) { val fileName = file.name if (!TextUtils.isEmpty(fileName) && fileName.endsWith(".png")) { file.delete() } } } return Result.success() } }
全部代碼都在doWork中,實現邏輯也很是簡單:找到相關目錄,而後逐一判斷目錄中的文件是否爲.png圖片,若是是就刪除。
以上是邏輯代碼,關鍵點是返回值Result.success(),它是一個Result類型,可用值有三個
對於success與failure,它還支持傳遞Data類型的值,Data內部是一個Map來管理的,因此對於kotlin能夠直接使用workDataOf
return Result.success(workDataOf(Constants.KEY_IMAGE_URI to outputFileUri.toString()))
它傳遞的值將放入OutputData中,能夠在鏈式請求中傳遞,與最終的響應結果獲取。其實本質是WorkManager結合了Room,將數據保存在數據庫中。
這一步要點就是這麼多,下面進入下一步。
WorkManager主要是經過WorkRequest來配置任務的,而它的WorkRequest種類包括:
首先OneTimeWorkRequest是做用於一次性任務,即任務只執行一次,一旦執行完就自動結束。它的構建也很是簡單:
val cleanUpRequest = OneTimeWorkRequestBuilder<CleanUpWorker>().build()
這樣就配置了與CleanUpWorker相關的WorkRequest,並且是一次性的。
在配置WorkRequest的過程當中咱們還能夠對其添加別的配置,例如添加tag、傳入inputData與添加constraint約束條件等等。
val constraint = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() val blurRequest = OneTimeWorkRequestBuilder<BlurImageWorker>() .setInputData(workDataOf(Constants.KEY_IMAGE_RES_ID to R.drawable.yaodaoji)) .addTag(Constants.TAG_BLUR_IMAGE) .setConstraints(constraint) .build()
添加tag是爲了打上標籤,以便後續獲取結果;傳入的inputData能夠在BlurImageWork中獲取傳入的值;添加網絡鏈接constraint約束條件,表明只有在網絡鏈接的狀態下才會觸發該WorkRequest。
而BlurImageWork的核心代碼以下:
override suspend fun doWork(): Result { val resId = inputData.getInt(Constants.KEY_IMAGE_RES_ID, -1) if (resId != -1) { val bitmap = BitmapFactory.decodeResource(applicationContext.resources, resId) val outputBitmap = apply(bitmap) val outputFileUri = writeToFile(outputBitmap) return Result.success(workDataOf(Constants.KEY_IMAGE_URI to outputFileUri.toString())) } return Result.failure() }
在doWork中,經過InputData來獲取上述blurRequest中傳入的InputData數據。而後經過apply來處理圖片,最後使用writeToFile寫入到本地文件中,並返回路徑。
因爲篇幅有限,這裏就不一一展開,感興趣的能夠查看源碼
PeriodicWorkRequest是能夠週期性的執行任務,它的使用方式與配置和OneTimeWorkRequest一致。
val constraint = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() // at least 15 minutes mPeriodicRequest = PeriodicWorkRequestBuilder<DataSourceWorker>(15, TimeUnit.MINUTES) .setConstraints(constraint) .addTag(Constants.TAG_DATA_SOURCE) .build()
不過須要注意的是:它的週期間隔最少爲15分鐘。
上面咱們已經將WorkRequest配置好了,剩下要作的是將其加入到work工做鏈中進行執行。
對於單個的WorkRequest,能夠直接經過WorkManager的enqueue方法
private val mWorkManager: WorkManager = WorkManager.getInstance(application) mWorkManager.enqueue(cleanUpRequest)
若是你想使用鏈式工做,只需調用beginWith或者beginUniqueWork方法便可。其實它們本質都是實例化了一個WorkContinuationImpl,只是調用了不一樣的構造方法。而最終的構造方法爲:
WorkContinuationImpl(@NonNull WorkManagerImpl workManagerImpl, String name, ExistingWorkPolicy existingWorkPolicy, @NonNull List<? extends WorkRequest> work, @Nullable List<WorkContinuationImpl> parents) { }
其中beginWith方法只需傳入WorkRequest
val workContinuation = mWorkManager.beginWith(cleanUpWork)
beginUniqueWork容許咱們建立一個獨一無二的鏈式請求。使用也很簡單:
val workContinuation = mWorkManager.beginUniqueWork(Constants.IMAGE_UNIQUE_WORK, ExistingWorkPolicy.REPLACE, cleanUpWork)
其中第一個參數是設置該鏈式請求的name;第二個參數ExistingWorkPolicy是設置name相同時的表現,它三個值,分別爲:
而不論是beginWith仍是beginUniqueWork,它都會返回WorkContinuation對象,經過該對象咱們能夠將後續任務加入到鏈式請求中。例如將上面的cleanUpRequest(清除)、blurRequest(圖片模糊處理)與saveRequest(保存)串行起來執行,實現以下:
val cleanUpRequest = OneTimeWorkRequestBuilder<CleanUpWorker>().build() val workContinuation = mWorkManager.beginUniqueWork(Constants.IMAGE_UNIQUE_WORK, ExistingWorkPolicy.REPLACE, cleanUpRequest) val blurRequest = OneTimeWorkRequestBuilder<BlurImageWorker>() .setInputData(workDataOf(Constants.KEY_IMAGE_RES_ID to R.drawable.yaodaoji)) .addTag(Constants.TAG_BLUR_IMAGE) .build() val saveRequest = OneTimeWorkRequestBuilder<SaveImageToMediaWorker>() .addTag(Constants.TAG_SAVE_IMAGE) .build() workContinuation.then(blurRequest) .then(saveRequest) .enqueue()
除了串行執行,還支持並行。例如將cleanUpRequest與blurRequest並行處理,完成以後再與saveRequest串行
val left = mWorkManager.beginWith(cleanUpRequest) val right = mWorkManager.beginWith(blurRequest) WorkContinuation.combine(arrayListOf(left, right)) .then(saveRequest) .enqueue()
須要注意的是:若是你的WorkRequest是PeriodicWorkRequest類型,那麼它不支持創建鏈式請求,這一點須要注意了。簡單的理解,週期性的任務原則上是沒有終止的,是個閉環,也就不存在所謂的鏈了。
這就到最後一步了,獲取響應結果WorkInfo。WorkManager支持兩種方式來獲取響應結果
同時返回的WorkInfo還支持LiveData數據格式。
例如,如今咱們要監聽上述blurRequest與saveRequest的狀態,使用tag來獲取:
// ViewModel internal val blurWorkInfo: LiveData<List<WorkInfo>> get() = mWorkManager.getWorkInfosByTagLiveData(Constants.TAG_BLUR_IMAGE) internal val saveWorkInfo: LiveData<List<WorkInfo>> get() = mWorkManager.getWorkInfosByTagLiveData(Constants.TAG_SAVE_IMAGE) // Activity private fun addObserver() { vm.blurWorkInfo.observe(this, Observer { if (it == null || it.isEmpty()) return@Observer with(it[0]) { if (!state.isFinished) { vm.processEnable.value = false } else { vm.processEnable.value = true val uri = outputData.getString(Constants.KEY_IMAGE_URI) if (!TextUtils.isEmpty(uri)) { vm.blurUri.value = Uri.parse(uri) } } } }) vm.saveWorkInfo.observe(this, Observer { saveImageUri = "" if (it == null || it.isEmpty()) return@Observer with(it[0]) { saveImageUri = outputData.getString(Constants.KEY_SHOW_IMAGE_URI) vm.showImageEnable.value = state.isFinished && !TextUtils.isEmpty(saveImageUri) } }) ...... ...... }
再來看一個經過id獲取的:
// ViewModel internal val dataSourceInfo: MediatorLiveData<WorkInfo> = MediatorLiveData() private fun addSource() { val periodicWorkInfo = mWorkManager.getWorkInfoByIdLiveData(mPeriodicRequest.id) dataSourceInfo.addSource(periodicWorkInfo) { dataSourceInfo.value = it } } // Activity private fun addObserver() { vm.dataSourceInfo.observe(this, Observer { if (it == null) return@Observer with(it) { if (state == WorkInfo.State.ENQUEUED) { val result = outputData.getString(Constants.KEY_DATA_SOURCE) if (!TextUtils.isEmpty(result)) { Toast.makeText(this@OtherWorkerActivity, result, Toast.LENGTH_LONG).show() } } } }) }
結合LiveData使用是否是很簡單呢? WorkInfo獲取的本質是經過操做Room數據庫來獲取。在文章的Work部分已經提到,在執行完Work任務以後傳遞的數據將會保存到Room數據庫中。
因此WorkManager與AAC的結合度很是高,目的也是致力於爲咱們開發者提供一套完整的框架,同時也說明Google對AAC框架的重視。
若是你還未了解AAC,推薦你閱讀我以前的文章
最後咱們將上面的幾個WorkRequest結合起來執行,看下它們的最終效果:
經過這篇文章,但願你可以熟悉運用WorkManager。若是這篇文章對你有所幫助,你能夠順手點贊、關注一波,這是對我最大的鼓勵!
該庫的目的是結合詳細的Demo來全面解析Android相關的知識點, 幫助讀者可以更快的掌握與理解所闡述的要點