Jetpack之WorkManager

  • 概述

    WorkManager是什么萎胰?

    WorkManager是適合用于持久性工作的推薦解決方案钝尸。如果工作始終要通過(guò)應(yīng)用重啟和系統(tǒng)重新啟動(dòng)來(lái)調(diào)度作烟,便是持久性的工作。由于大多數(shù)后臺(tái)處理操作都是通過(guò)持久性工作完成的球涛,因此 WorkManager 是適用于后臺(tái)處理操作的主要推薦 API。

    WorkManager 可處理三種類型的持久性工作:

    • 立即執(zhí)行:必須立即開始且很快就完成的任務(wù)校镐,可以加急宾符。
    • 長(zhǎng)時(shí)間運(yùn)行:運(yùn)行時(shí)間可能較長(zhǎng)(有可能超過(guò) 10 分鐘)的任務(wù)。
    • 可延期執(zhí)行:延期開始并且可以定期運(yùn)行的預(yù)定任務(wù)灭翔。

    除此之外,WorkManager還可添加很多額外功能:

    1. 使用工作約束明確定義工作運(yùn)行的最佳條件辣苏。例如肝箱,僅在設(shè)備采用不按流量計(jì)費(fèi)的網(wǎng)絡(luò)連接時(shí)、當(dāng)設(shè)備處于空閑狀態(tài)或者有足夠的電量時(shí)運(yùn)行稀蟋。

    2. WorkManager 允許您使用靈活的調(diào)度窗口調(diào)度工作煌张,以運(yùn)行一次性重復(fù)工作。您還可以對(duì)工作進(jìn)行標(biāo)記或命名退客,以便調(diào)度唯一的骏融、可替換的工作以及監(jiān)控或取消工作組。

      已調(diào)度的工作存儲(chǔ)在內(nèi)部托管的 SQLite 數(shù)據(jù)庫(kù)中萌狂,由 WorkManager 負(fù)責(zé)確保該工作持續(xù)進(jìn)行档玻,并在設(shè)備重新啟動(dòng)后重新調(diào)度。

      此外茫藏,WorkManager 遵循低電耗模式等省電功能和最佳做法误趴,因此您在這方面無(wú)需擔(dān)心。

    3. 您可以使用 WorkManager 調(diào)度需在后臺(tái)立即執(zhí)行的工作务傲。您應(yīng)該使用加急工作來(lái)處理對(duì)用戶來(lái)說(shuō)很重要且會(huì)在幾分鐘內(nèi)完成的任務(wù)凉当。

    4. 有時(shí)工作會(huì)失敗。WorkManager 提供了靈活的重試政策售葡,包括可配置的指數(shù)退避政策看杭。

    5. 對(duì)于復(fù)雜的相關(guān)工作,您可以使用直觀的接口將各個(gè)工作任務(wù)串聯(lián)起來(lái)挟伙,這樣您便可以控制哪些部分依序運(yùn)行楼雹,哪些部分并行運(yùn)行。對(duì)于每項(xiàng)工作任務(wù)尖阔,您可以定義工作的輸入和輸出數(shù)據(jù)烘豹。將工作串聯(lián)在一起時(shí),WorkManager 會(huì)自動(dòng)將輸出數(shù)據(jù)從一個(gè)工作任務(wù)傳遞給下一個(gè)工作任務(wù)诺祸。

    6. WorkManager 無(wú)縫集成 CoroutinesRxJava携悯,讓您可以插入自己的異步 API,非常靈活筷笨。

    WorkManager 適用于需要可靠運(yùn)行的工作憔鬼,即使用戶導(dǎo)航離開屏幕龟劲、退出應(yīng)用或重啟設(shè)備也不影響工作的執(zhí)行。例如:

    • 向后端服務(wù)發(fā)送日志或分析數(shù)據(jù)轴或。
    • 定期將應(yīng)用數(shù)據(jù)與服務(wù)器同步昌跌。

    WorkManager 不適用于那些可在應(yīng)用進(jìn)程結(jié)束時(shí)安全終止的進(jìn)程內(nèi)后臺(tái)工作。它也并非對(duì)所有需要立即執(zhí)行的工作都適用的通用解決方案照雁,對(duì)于對(duì)于短暫的蚕愤、應(yīng)用存活周期內(nèi)的可以使用協(xié)程、RxJava饺蚊、線程池等來(lái)處理萍诱;而對(duì)于精確的、在低電耗模式下也能被喚醒的任務(wù)污呼,則使用AlarmManager(在電源和資源管理方面并不高效裕坊,但是貴在精確)來(lái)完成,適合用于精確鬧鐘或通知(例如日歷活動(dòng))場(chǎng)景燕酷,不適合后臺(tái)工作籍凝。

  • 使用入門

    • 添加依賴

      dependencies {
          def work_version = "2.8.0"
      
          // (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"
      
          // optional - Multiprocess support
          implementation "androidx.work:work-multiprocess:$work_version"
      }
      
    • 創(chuàng)建Worker

      class UploadWorker(appContext: Context, workerParams: WorkerParameters):
             Worker(appContext, workerParams) {
         override fun doWork(): Result {
             // Do the work here--in this case, upload the images.
             uploadImages()
             /**結(jié)果返回*/
               //Result.failure()
                   //Result.retry()
             return Result.success(
                  Data.Builder()
                      .putString("name","MPH")
                      .build()
             )
         }
      }
      
    • 創(chuàng)建request并開啟后臺(tái)任務(wù)

      val uploadWorkRequest: WorkRequest = OneTimeWorkRequestBuilder<UploadWorker>().build()
      //啟動(dòng)任務(wù)
      WorkManager.getInstance(myContext).enqueue(uploadWorkRequest)
      
  • 任務(wù)配置

    上面的使用入門介紹了使用WorkManager的一般步驟,下面我們來(lái)看看如何根據(jù)不同需求做具體的任務(wù)配置苗缩。

    WorkRequest 本身是抽象基類饵蒂。該類有兩個(gè)派生實(shí)現(xiàn),可用于創(chuàng)建 OneTimeWorkRequestPeriodicWorkRequest 請(qǐng)求酱讶。顧名思義苹享,OneTimeWorkRequest 適用于調(diào)度非重復(fù)性工作,而 PeriodicWorkRequest 則更適合調(diào)度以一定間隔重復(fù)執(zhí)行的工作浴麻。

    • 一次性工作OneTimeWorkRequest創(chuàng)建

      對(duì)于無(wú)需額外配置的簡(jiǎn)單工作得问,可以使用靜態(tài)方法 from

      val myWorkRequest = OneTimeWorkRequest.from(MyWork::class.java)
      

      對(duì)于更復(fù)雜的工作,可以使用構(gòu)建器:

      val uploadWorkRequest: WorkRequest =
         OneTimeWorkRequestBuilder<MyWork>()
             // Additional configuration
             .build()
      
    • 定期任務(wù)PeriodicWorkRequest創(chuàng)建

      有時(shí)可能需要定期運(yùn)行某些工作软免。例如宫纬,可能要定期備份數(shù)據(jù)、定期下載應(yīng)用中的新鮮內(nèi)容或者定期上傳日志到服務(wù)器膏萧。

      //內(nèi)聯(lián)方法簡(jiǎn)化創(chuàng)建方式(內(nèi)部也是用PeriodicWorkRequest.Builder)
      val saveRequest =
             PeriodicWorkRequestBuilder<SaveImageToFileWorker>(1, TimeUnit.HOURS)
                       // 間隔一小時(shí)
                 .build()
      //手動(dòng)創(chuàng)建
      PeriodicWorkRequest.Builder(NewsWork::class.java, 20, TimeUnit.MINUTES).build()
      

      時(shí)間間隔定義為兩次重復(fù)執(zhí)行之間的最短時(shí)間漓骚,實(shí)際上任務(wù)執(zhí)行時(shí)機(jī)并不是嚴(yán)格按照間隔時(shí)間安排,但是一定會(huì)超過(guò)最短間隔時(shí)間榛泛。工作器的確切執(zhí)行時(shí)間取決于您在 WorkRequest 對(duì)象中設(shè)置的約束以及系統(tǒng)執(zhí)行的優(yōu)化:

      val myUploadWork = PeriodicWorkRequestBuilder<SaveImageToFileWorker>(
             1, TimeUnit.HOURS, // repeatInterval (the period cycle)
             15, TimeUnit.MINUTES) // flexInterval
          .build()
      

      任務(wù)會(huì)在從 repeatInterval - flexInterval 開始蝌蹂,一直到間隔結(jié)束的這段時(shí)間內(nèi)運(yùn)行。

      任務(wù)間隔時(shí)間和分給任務(wù)的執(zhí)行時(shí)間段都有最小限制:

      /**
       * The minimum interval duration for {@link PeriodicWorkRequest} (in milliseconds).
       */
      public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.
      /**
       * The minimum flex duration for {@link PeriodicWorkRequest} (in milliseconds).
       */
      public static final long MIN_PERIODIC_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes.
      
    • 加急工作

      加急工作具有以下特征:

      • 重要性:加急工作適用于對(duì)用戶很重要或由用戶啟動(dòng)的任務(wù)曹锨。
      • 速度:加急工作最適合那些立即啟動(dòng)并在幾分鐘內(nèi)完成的簡(jiǎn)短任務(wù)孤个。
      • 配額:限制前臺(tái)執(zhí)行時(shí)間的系統(tǒng)級(jí)配額決定了加急作業(yè)是否可以啟動(dòng)。
      • 電源管理電源管理限制(如省電模式和低電耗模式)不太可能影響加急工作沛简。
      • 延遲時(shí)間:系統(tǒng)立即執(zhí)行加急工作齐鲤,前提是系統(tǒng)的當(dāng)前工作負(fù)載允許執(zhí)行此操作斥废。這意味著這些工作對(duì)延遲時(shí)間較為敏感,不能安排到以后執(zhí)行给郊。

      在用戶想要發(fā)送消息或附加的圖片時(shí)牡肉,可能會(huì)在聊天應(yīng)用內(nèi)使用加急工作。同樣淆九,處理付款或訂閱流程的應(yīng)用也可能需要使用加急工作统锤。這是因?yàn)檫@些任務(wù)對(duì)用戶很重要,會(huì)在后臺(tái)快速執(zhí)行炭庙,并需要立即開始執(zhí)行饲窿。

      執(zhí)行時(shí)間并非無(wú)限制,而是受配額限制煤搜。如果您的應(yīng)用使用其執(zhí)行時(shí)間并達(dá)到分配的配額,在配額刷新之前唧席,您無(wú)法再執(zhí)行加急工作擦盾。這樣,Android 可以更有效地在應(yīng)用之間平衡資源淌哟。

      每個(gè)應(yīng)用均有自己的前臺(tái)執(zhí)行時(shí)間配額迹卢。可用的執(zhí)行時(shí)間取決于待機(jī)模式存儲(chǔ)分區(qū)和進(jìn)程的重要性徒仓。

      調(diào)用 setExpedited() 來(lái)聲明 WorkRequest 應(yīng)該使用加急作業(yè)腐碱,以盡可能快的速度運(yùn)行:

      val request = OneTimeWorkRequestBuilder()
          .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
          .build()
      
      WorkManager.getInstance(context)
          .enqueue(request)
      

      參數(shù)表示配額策略,有兩種:

      • OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST掉弛,這會(huì)導(dǎo)致作業(yè)作為普通工作請(qǐng)求運(yùn)行症见。
      • OutOfQuotaPolicy.DROP_WORK_REQUEST,這會(huì)在配額不足時(shí)導(dǎo)致請(qǐng)求取消殃饿。

      在系統(tǒng)負(fù)載過(guò)高或者配額滿了的情況下加急任務(wù)也會(huì)被延遲谋作。

    • 工作約束

      可以對(duì)定期工作設(shè)置約束。例如乎芳,你可以為工作請(qǐng)求添加約束遵蚜,以便工作僅在用戶設(shè)備充電時(shí)運(yùn)行。在這種情況下奈惑,除非滿足約束條件吭净,否則即使過(guò)了定義的重復(fù)間隔,PeriodicWorkRequest 也不會(huì)運(yùn)行肴甸。這可能會(huì)導(dǎo)致工作在某次運(yùn)行時(shí)出現(xiàn)延遲寂殉,甚至?xí)蛟谙鄳?yīng)間隔內(nèi)未滿足條件而被跳過(guò)。

      image-20230222140850528

      例如原在,以下代碼會(huì)構(gòu)建了一個(gè)工作請(qǐng)求不撑,該工作請(qǐng)求僅在用戶設(shè)備正在充電且連接到 Wi-Fi 網(wǎng)絡(luò)時(shí)才會(huì)運(yùn)行:

      val constraints = Constraints.Builder()
         .setRequiredNetworkType(NetworkType.UNMETERED)
         .setRequiresCharging(true)
         .build()
      
      val myWorkRequest: WorkRequest =
         OneTimeWorkRequestBuilder<MyWork>()
             .setConstraints(constraints)
             .build()
      

      如果指定了多個(gè)約束文兢,工作將僅在滿足所有約束時(shí)才會(huì)運(yùn)行。

      如果在工作運(yùn)行時(shí)不再滿足某個(gè)約束焕檬,WorkManager 將停止工作器姆坚。系統(tǒng)將在滿足所有約束后重試工作。

    • 延遲執(zhí)行

      如果工作沒有約束实愚,或者當(dāng)工作加入隊(duì)列時(shí)所有約束都得到了滿足兼呵,那么系統(tǒng)可能會(huì)選擇立即運(yùn)行該工作。如果您不希望工作立即運(yùn)行腊敲,可以將工作指定為在經(jīng)過(guò)一段最短初始延遲時(shí)間后再啟動(dòng)击喂。

      val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
           //10分鐘后執(zhí)行
         .setInitialDelay(10, TimeUnit.MINUTES)
         .build()
      
    • 重試策略

      如果需要讓 WorkManager 重試工作,可以從doWork方法返回 Result.retry()碰辅。然后懂昂,系統(tǒng)將根據(jù)退避延遲時(shí)間退避政策重新調(diào)度工作。

      • 退避延遲時(shí)間指定了首次嘗試后重試工作前的最短等待時(shí)間没宾。此值建議不超過(guò) 10 秒~5小時(shí)范圍凌彬。
      • 退避政策定義了在后續(xù)重試過(guò)程中,退避延遲時(shí)間隨時(shí)間以怎樣的方式增長(zhǎng)循衰。WorkManager 支持 2 個(gè)退避政策铲敛,即 LINEAREXPONENTIAL

      每個(gè)工作請(qǐng)求都有退避政策和退避延遲時(shí)間会钝。默認(rèn)政策是 EXPONENTIAL伐蒋,延遲時(shí)間為 10 秒,但您可以在工作請(qǐng)求配置中替換此設(shè)置迁酸。如果政策為 LINEAR先鱼,每次嘗試重試時(shí),重試間隔都會(huì)增加約 10 秒奸鬓。例如型型,第一次運(yùn)行以 Result.retry() 結(jié)束并在 10 秒后重試;然后全蝶,如果工作在后續(xù)嘗試后繼續(xù)返回 Result.retry()闹蒜,那么接下來(lái)會(huì)在 20 秒、30 秒抑淫、40 秒后重試绷落,以此類推。如果退避政策設(shè)置為 EXPONENTIAL始苇,那么重試時(shí)長(zhǎng)序列將接近 20砌烁、40、80 秒,以此類推函喉。

      val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
         .setBackoffCriteria(
             BackoffPolicy.LINEAR,
             OneTimeWorkRequest.MIN_BACKOFF_MILLIS,//可以指定自定義時(shí)長(zhǎng)
             TimeUnit.MILLISECONDS)
         .build()
      
    • 輸入數(shù)據(jù)和輸出數(shù)據(jù)

      // Define the Worker requiring input
      class UploadWork(appContext: Context, workerParams: WorkerParameters)
         : Worker(appContext, workerParams) {
      
         override fun doWork(): Result {
             val imageUriInput =
                 inputData.getString("IMAGE_URI") ?: return Result.failure()
      
             uploadFile(imageUriInput)
               //若Worker 停止后避归,從 Worker.doWork() 返回什么已不重要;Result 將被忽略
             return Result.success(
                      //設(shè)置輸出數(shù)據(jù)
                          Data.Builder()
                                   .putString("name","MPH")
                                   .build()
             )
         }
         ...
      }
      val myUploadWork = OneTimeWorkRequestBuilder<UploadWork>()
           //設(shè)置輸入數(shù)據(jù)
         .setInputData(workDataOf(
             "IMAGE_URI" to "http://..."
         ))
         .build()
      

      輸入輸出數(shù)據(jù)都是Map形式的Data類型管呵。

    • 設(shè)置Tag

      每個(gè)工作請(qǐng)求都可以添加多個(gè)標(biāo)識(shí)符梳毙,該標(biāo)識(shí)符可用于在以后標(biāo)識(shí)該工作,以便取消工作或觀察其進(jìn)度捐下。

      如果有一組在邏輯上相關(guān)的工作账锹,對(duì)這些工作項(xiàng)進(jìn)行標(biāo)記可能也會(huì)很有幫助。通過(guò)標(biāo)記坷襟,您一起處理一組工作請(qǐng)求奸柬。

      val myWorkRequest = OneTimeWorkRequestBuilder<MyWork>()
         .addTag("cleanup")
           .addTag("room")  //可以添加多個(gè),相當(dāng)于添加分類
         .build()
      
  • 管理任務(wù)

    • 設(shè)置唯一工作

      在將工作加入隊(duì)列時(shí)請(qǐng)小心謹(jǐn)慎婴程,以避免重復(fù)廓奕。例如,應(yīng)用可能會(huì)每 24 小時(shí)嘗試將其日志上傳到后端服務(wù)档叔。如果不謹(jǐn)慎桌粉,即使作業(yè)只需運(yùn)行一次,最終也可能會(huì)多次將同一作業(yè)加入隊(duì)列蹲蒲。為了實(shí)現(xiàn)此目標(biāo)番甩,您可以將工作調(diào)度為唯一工作侵贵。

      唯一工作既可用于一次性工作届搁,也可用于定期工作:

      • WorkManager.enqueueUniqueWork()(用于一次性工作)
      • WorkManager.enqueueUniquePeriodicWork()(用于定期工作)

      這兩種方法都接受 3 個(gè)參數(shù):

      • uniqueWorkName - 用于唯一標(biāo)識(shí)工作請(qǐng)求的 String
      • existingWorkPolicy - 此 enum 可告知 WorkManager:如果已有使用該名稱且尚未完成的唯一工作鏈窍育,應(yīng)執(zhí)行什么操作卡睦。
      • work - 要調(diào)度的 WorkRequest
      val sendLogsWorkRequest =
             PeriodicWorkRequestBuilder<SendLogsWorker>(24, TimeUnit.HOURS)
                 .setConstraints(Constraints.Builder()
                     .setRequiresCharging(true)
                     .build()
                  )
                 .build()
      WorkManager.getInstance(this).enqueueUniquePeriodicWork(
                 "sendLogs", //自定義名字
                 ExistingPeriodicWorkPolicy.KEEP,
                 sendLogsWorkRequest
      )
      

      第二個(gè)參數(shù)是當(dāng)設(shè)置多個(gè)同一名字的唯一任務(wù)時(shí)如何處理這種沖突漱抓。

      對(duì)于一次性工作表锻,您需要提供一個(gè) ExistingWorkPolicy,支持用于處理沖突的 4 個(gè)選項(xiàng)如下:

      • REPLACE:用新工作替換現(xiàn)有工作乞娄。此選項(xiàng)將取消現(xiàn)有工作瞬逊。

      • KEEP:保留現(xiàn)有工作,并忽略新工作仪或。

      • APPEND:將新工作附加到現(xiàn)有工作的末尾确镊。此政策將導(dǎo)致您的新工作鏈接到現(xiàn)有工作,在現(xiàn)有工作完成后運(yùn)行》渡荆現(xiàn)有工作將成為新工作的先決條件蕾域。如果現(xiàn)有工作變?yōu)?CANCELLEDFAILED 狀態(tài),新工作也會(huì)變?yōu)?CANCELLEDFAILED。如果您希望無(wú)論現(xiàn)有工作的狀態(tài)如何都運(yùn)行新工作旨巷,請(qǐng)改用 APPEND_OR_REPLACE巨缘。

      • APPEND_OR_REPLACE 函數(shù)類似于 APPEND,不過(guò)它并不依賴于先決條件工作狀態(tài)采呐。即使現(xiàn)有工作變?yōu)?CANCELLEDFAILED 狀態(tài)若锁,新工作仍會(huì)運(yùn)行。

      而對(duì)于定期工作懈万,需要提供一個(gè) ExistingPeriodicWorkPolicy拴清,它支持 REPLACEKEEP 這兩個(gè)選項(xiàng)。這些選項(xiàng)的功能與其對(duì)應(yīng)的 ExistingWorkPolicy 功能相同会通。

    • 監(jiān)聽任務(wù)進(jìn)度和結(jié)果

      //獲取
      workManager.getWorkInfoById(syncWorker.id) // ListenableFuture<WorkInfo>
      workManager.getWorkInfosForUniqueWork("sync") // ListenableFuture<List<WorkInfo>>
      workManager.getWorkInfosByTag("syncTag") // ListenableFuture<List<WorkInfo>>
      
      val work1Id = getWorkInfoById(work1.id).get().id
      getWorkInfosByTag("cleanup").get().forEach {
          val workState = it.state
          val workOutputData = it.outputData
      }
      //取消
      workManager.cancelWorkById(syncWorker.id)
      workManager.cancelUniqueWork("sync")
      workManager.cancelAllWorkByTag("syncTag")
      
      
      workManager.getWorkInfoByIdLiveData(syncWorker.id)
                     .observe(viewLifecycleOwner) { workInfo ->
         if(workInfo?.state == WorkInfo.State.SUCCEEDED) {
             Snackbar.make(requireView(),
            R.string.work_completed, Snackbar.LENGTH_SHORT)
                 .show()
         }
      }
      

      WorkManager 2.4.0 及更高版本支持使用 WorkQuery 對(duì)象對(duì)已加入隊(duì)列的作業(yè)進(jìn)行復(fù)雜查詢口予。WorkQuery 支持按工作的標(biāo)記、狀態(tài)和唯一工作名稱的組合進(jìn)行查詢:

      val workQuery = WorkQuery.Builder
             .fromTags(listOf("syncTag"))
             .addStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.CANCELLED))
             .addUniqueWorkNames(listOf("preProcess", "sync")
          )
         .build()
      
      val workInfos: ListenableFuture<List<WorkInfo>> = workManager.getWorkInfos(workQuery)
      

      WorkManager 會(huì)在后臺(tái)檢查工作的 State涕侈。如果工作已經(jīng)完成沪停,系統(tǒng)不會(huì)執(zhí)行任何操作。否則裳涛,工作的狀態(tài)會(huì)更改為 CANCELLED木张,之后就不會(huì)運(yùn)行這個(gè)工作。任何依賴于此工作WorkRequest 作業(yè)也將變?yōu)?CANCELLED端三∠侠瘢可以重寫ListenableWorker.onStopped()方法來(lái)處理清理工作,它會(huì)在停止時(shí)調(diào)用郊闯,可以調(diào)用 ListenableWorker.isStopped() 方法以檢查工作器是否已停止妻献。

      對(duì)于使用 ListenableWorkerWorker 的 Java 開發(fā)者,setProgressAsync() API 會(huì)返回 ListenableFuture<Void>团赁;更新進(jìn)度是異步過(guò)程育拨,因?yàn)楦逻^(guò)程涉及將進(jìn)度信息存儲(chǔ)在數(shù)據(jù)庫(kù)中。在 Kotlin 中欢摄,可以使用 CoroutineWorker 對(duì)象的 setProgress() 擴(kuò)展函數(shù)來(lái)更新進(jìn)度信息:

      import android.content.Context
      import androidx.work.CoroutineWorker
      import androidx.work.Data
      import androidx.work.WorkerParameters
      import kotlinx.coroutines.delay
      
      class ProgressWorker(context: Context, parameters: WorkerParameters) :
          CoroutineWorker(context, parameters) {
      
          companion object {
              const val Progress = "Progress"
              private const val delayDuration = 1L
          }
      
          override suspend fun doWork(): Result {
              val firstUpdate = workDataOf(Progress to 0)
              val lastUpdate = workDataOf(Progress to 100)
              //如果是Worker熬丧,則調(diào)用setProgressAsync方法
              setProgress(firstUpdate)
              delay(delayDuration)
              setProgress(lastUpdate)
              return Result.success()
          }
      }
      

      觀察進(jìn)度信息也很簡(jiǎn)單,可以使用 getWorkInfoBy…()getWorkInfoBy…LiveData() 方法怀挠,并引用 WorkInfo:

      WorkManager.getInstance(applicationContext)
          // requestId is the WorkRequest id
          .getWorkInfoByIdLiveData(requestId)
          .observe(observer, Observer { workInfo: WorkInfo? ->
                  if (workInfo != null) {
                      val progress = workInfo.progress
                      val value = progress.getInt(Progress, 0)
                      // Do something with progress information
                  }
          })
      
    • 鏈接任務(wù)

      鏈接任務(wù)只能用于OneTimeWorkRequest析蝴。

      如需創(chuàng)建工作鏈,您可以使用 WorkManager.beginWith(OneTimeWorkRequest)WorkManager.beginWith(List)绿淋,這會(huì)返回 WorkContinuation 實(shí)例闷畸。

      然后,可以使用 WorkContinuation 通過(guò) then(OneTimeWorkRequest)then(List) 添加 OneTimeWorkRequest 依賴實(shí)例躬它。 .

      每次調(diào)用 WorkContinuation.then(...) 都會(huì)返回一個(gè)新的 WorkContinuation 實(shí)例边坤。如果添加了 OneTimeWorkRequest 實(shí)例的 List竣稽,這些請(qǐng)求可能會(huì)并行運(yùn)行错忱。

      最后,您可以使用 WorkContinuation.enqueue() 方法對(duì) WorkContinuation 工作鏈執(zhí)行 enqueue() 操作疮跑。

      WorkManager.getInstance(myContext)
         // Candidates to run in parallel
         .beginWith(listOf(plantName1, plantName2, plantName3))
         // Dependent work (only runs after all previous work in chain)
         .then(cache)
         .then(upload)
         // Call enqueue to kick things off
         .enqueue()
      

      當(dāng)您鏈接 OneTimeWorkRequest 實(shí)例時(shí),父級(jí)工作請(qǐng)求的輸出將作為子級(jí)的輸入傳入凸舵。因此祖娘,在上面的示例中,plantName1啊奄、plantName2plantName3 的輸出將作為 cache 請(qǐng)求的輸入傳入渐苏。

      為了管理來(lái)自多個(gè)父級(jí)工作請(qǐng)求的輸入,WorkManager 使用 InputMerger菇夸。

      WorkManager 提供兩種不同類型的 InputMerger

      • OverwritingInputMerger 會(huì)嘗試將所有輸入中的所有鍵添加到輸出中琼富。如果發(fā)生沖突,它會(huì)覆蓋先前設(shè)置的鍵庄新。是默認(rèn)的合并規(guī)則鞠眉。

        image-20230222154711873
      • ArrayCreatingInputMerger 會(huì)嘗試合并輸入,并在必要時(shí)創(chuàng)建數(shù)組择诈。

        val cache: OneTimeWorkRequest = OneTimeWorkRequestBuilder<PlantWorker>()
           .setInputMerger(ArrayCreatingInputMerger::class)
           .setConstraints(constraints)
           .build()
        
        image-20230222154827076

      如果有更具體的用例械蹋,則可以創(chuàng)建 InputMerger 的子類來(lái)編寫自己的用例。

      當(dāng)上游的任務(wù)成功后才會(huì)執(zhí)行下游的任務(wù)羞芍,只要上游的任務(wù)失敗或者取消哗戈,則其下游的任務(wù)都會(huì)失敗或者取消,但是對(duì)同級(jí)的任務(wù)不會(huì)被影響荷科。

  • WorkManager配置和初始化

    從WorkManager2.6開始唯咬,在androidx.work:work-runtime庫(kù)中的AndroidManifest文件中就配置了WorkManagerInitializer的provider:

    <application>
        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge" >
            <meta-data
                android:name="androidx.work.WorkManagerInitializer"
                android:value="androidx.startup" />
        </provider>
          ...
    </application>
    
    public final class WorkManagerInitializer implements Initializer<WorkManager> {
        @Override
        public WorkManager create(@NonNull Context context) {
            WorkManager.initialize(context, new Configuration.Builder().build());
            return WorkManager.getInstance(context);
        }
          ...
    }
    

    這就說(shuō)明,如果你使用的是2.6以上版本的話步做,在應(yīng)用啟動(dòng)時(shí)副渴,WorkManager就會(huì)自動(dòng)初始化奈附。那如果我們想要在使用時(shí)才初始化的話怎么辦呢全度?這時(shí)就得根據(jù)Manifest合并規(guī)則在app的AndroidManifest中重寫來(lái)覆蓋它:

     <!-- 如果應(yīng)用不需要InitializationProvider的話可以直接移除InitializationProvider -->
    <provider
       android:name="androidx.startup.InitializationProvider"
       android:authorities="${applicationId}.androidx-startup"
       tools:node="remove">
    </provider> 
    <!--如果還用到其他Initializer,只是禁用WorkManagerInitializer的話-->
    <provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <meta-data
            android:name="androidx.work.WorkManagerInitializer"
            android:value="androidx.startup"
            tools:node="remove" />
     </provider>
    

    如果使用2.6之前的版本斥滤,則為:

    <provider
        android:name="androidx.work.impl.WorkManagerInitializer"
        android:authorities="${applicationId}.workmanager-init"
        tools:node="remove" />
    

    如果想要自定義WorkManager配置将鸵,則需要讓 Application 類實(shí)現(xiàn) Configuration.Provider 接口,并提供自己的 Configuration.Provider.getWorkManagerConfiguration() 實(shí)現(xiàn)佑颇。當(dāng)需要使用 WorkManager 時(shí)顶掉,請(qǐng)務(wù)必調(diào)用方法 WorkManager.getInstance(Context)。WorkManager 會(huì)調(diào)用應(yīng)用的自定義 getWorkManagerConfiguration() 方法來(lái)發(fā)現(xiàn)其 Configuration(無(wú)需自行調(diào)用 WorkManager.initialize()):

    class MyApplication() : Application(), Configuration.Provider {
         override fun getWorkManagerConfiguration() =
               Configuration.Builder()
                    .setMinimumLoggingLevel(android.util.Log.INFO)
                                  .setExecutor(Executors.newFixedThreadPool(8))
                    .build()
    }
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挑胸,一起剝皮案震驚了整個(gè)濱河市痒筒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖簿透,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件移袍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡老充,警方通過(guò)查閱死者的電腦和手機(jī)葡盗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)啡浊,“玉大人觅够,你說(shuō)我怎么就攤上這事∠锵” “怎么了喘先?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)廷粒。 經(jīng)常有香客問我苹祟,道長(zhǎng),這世上最難降的妖魔是什么评雌? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任树枫,我火速辦了婚禮,結(jié)果婚禮上景东,老公的妹妹穿的比我還像新娘砂轻。我一直安慰自己,他們只是感情好斤吐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布搔涝。 她就那樣靜靜地躺著,像睡著了一般和措。 火紅的嫁衣襯著肌膚如雪庄呈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天派阱,我揣著相機(jī)與錄音诬留,去河邊找鬼。 笑死贫母,一個(gè)胖子當(dāng)著我的面吹牛文兑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腺劣,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼绿贞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了橘原?” 一聲冷哼從身側(cè)響起籍铁,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涡上,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后拒名,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吓懈,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年靡狞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耻警。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡甸怕,死狀恐怖甘穿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梢杭,我是刑警寧澤温兼,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站武契,受9級(jí)特大地震影響募判,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咒唆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一届垫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧全释,春花似錦装处、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至李命,卻和暖如春登淘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背封字。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工黔州, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人周叮。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓辩撑,卻偏偏與公主長(zhǎng)得像界斜,于是被迫代替她去往敵國(guó)和親仿耽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容