自定義 WorkManager —— 基礎(chǔ)概念

image

WorkManager 是一個(gè) Android Jetpack 擴(kuò)展庫,它可以讓您輕松規(guī)劃那些可延后探膊、異步但又需要可靠運(yùn)行的任務(wù)页畦。對(duì)于絕大部分后臺(tái)執(zhí)行任務(wù)來說寄狼,使用 WorkManager 是目前 Android 平臺(tái)上的最佳實(shí)踐。

目前為止本系列已經(jīng)討論過:

在本篇文章中,我們將會(huì)討論自定義配置相關(guān)的內(nèi)容持寄,包括:

  • 為什么可能會(huì)需要自定義配置
  • 如何聲明自定義配置
  • WorkerFactory 以及自定義 WorkerFactory 的原因
  • DelegatingWorkerFactory 詳解

本系列的下一篇文章將對(duì)依賴注入和 Dagger 展開討論源梭,請(qǐng)持續(xù)關(guān)注我們。

使用 WorkManager 時(shí)稍味,您需要自己定義 Worker/CoroutineWorker 或任何 ListenableWorker 的派生類废麻。WorkManager 會(huì)在正確的時(shí)間點(diǎn)實(shí)例化您的 Worker,其時(shí)機(jī)獨(dú)立于您應(yīng)用的運(yùn)行模庐,不受其運(yùn)行狀態(tài)的影響烛愧。為了可以初始化您的 Worker,WorkManager 會(huì)使用一個(gè) WorkerFactory掂碱。

默認(rèn) WorkerFactory 所創(chuàng)建的 Worker 只包含兩個(gè)參數(shù):

如果您需要通過 Worker 的構(gòu)造函數(shù)傳入更多參數(shù)怜姿,則需要一個(gè)自定義的 WorkerFactory。

延伸閱讀 : 我們講過默認(rèn)的 WorkerFactory 使用反射來實(shí)例化正確的 ListenableWorker 類疼燥,但當(dāng)我們的 Worker 類的類名被 R8 (或 ProGuard) 最小化之后沧卢,這個(gè)操作就會(huì)失敗。為了避免這種情況醉者,WorkManager 包含了一個(gè) proguard-rules.pro 文件來避免您的 Worker 類的類名被混淆但狭。

自定義配置和 WorkerFactory

WorkManager 類遵循 單例模式,而且它只能在實(shí)例化之前進(jìn)行配置撬即。這意味著立磁,如果您想自定義它的配置,就必須先禁用默認(rèn)配置剥槐。

如果您嘗試通過 initialize() 方法再次初始化 WorkManager唱歧,該方法就會(huì)拋出一個(gè)異常 (于 1.0.0 版本中加入)。為了避免異常才沧,您需要禁用默認(rèn)的初始化迈喉。您可以稍后在您的 Application 的 onCreate 方法中配置和初始化您的 WorkManager。

2.1.0 版本 中加入了一個(gè)更好的初始化 WorkManager 的方式温圆。您可以通過在您的 Application 類中實(shí)現(xiàn) WorkManager 的 Configuration.Provider 接口的方式來使用按需初始化挨摸。接下來,您只需要使用 getInstance(context) 獲得實(shí)例岁歉,WorkManager 就會(huì)通過您的配置初始化它自己得运。

可配置參數(shù)

如上所講,您可以配置用來創(chuàng)建 Worker 的 WorkerFactory锅移,但是您也可以自定義其他的參數(shù)熔掺。WorkManager 的 Configuration.Builder 參考指南中包含了參數(shù)的完整列表。這里我想強(qiáng)調(diào)兩個(gè)附加參數(shù):

  • Logging 級(jí)別
  • JobId 范圍

當(dāng)我們有需要時(shí)非剃,可以通過修改日志級(jí)別方便地理解 WorkManager 中正在發(fā)生什么置逻。關(guān)于這個(gè)話題,我們有一個(gè) 專門的文檔頁备绽。您也可以查看 Advanced WorkManager codelab 實(shí)戰(zhàn)教程券坞,以了解此功能在真實(shí)示例中的實(shí)現(xiàn)鬓催,以及您可以通過此功能獲取到什么樣的信息。

如果以在我們的應(yīng)用中使用 JobScheduler API 一樣的方式使用 WorkManager恨锚,我們可能也會(huì)想要自定義 JobId 范圍宇驾。因?yàn)樵谶@種情況下,您會(huì)想要避免在同一個(gè)地方使用相同的 JobId 范圍猴伶。版本 2.4.0 中也加入了一個(gè)新的 Lint 規(guī)則 來覆蓋這種情況课舍。

WorkManager 的 WorkerFactory

我們已經(jīng)知道,WorkManager 有一個(gè)默認(rèn)的 WorkerFactory他挎,它可以根據(jù)我們經(jīng)由 WorkRequest 傳入的 Worker 類的類名筝尾,通過反射來找到應(yīng)該實(shí)例化的 Worker 類。

?? 如果您在創(chuàng)建了一個(gè) WorkRequest 后重構(gòu)了應(yīng)用雇盖,并為您的 Worker 類起了另一個(gè)名字忿等,WorkManager 就會(huì)因?yàn)闊o法找到正確的類而拋出一個(gè) ClassNotFoundException。

您可能會(huì)想要為您的 Worker 的構(gòu)造函數(shù)添加其他參數(shù)崔挖。假設(shè)您有一個(gè) Worker 需要引用一個(gè) Retrofit 服務(wù)來跟遠(yuǎn)程服務(wù)器進(jìn)行通訊:

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */
class UpvoteStoryWorker(
  appContext: Context, 
  workerParams: WorkerParameters, 
  private val service: UpvoteStoryHttpApi) 
    : CoroutineWorker(appContext, workerParams) {

    override suspend fun doWork(): Result {

        return try {
           // 投票操作
            Result.success()
        } catch (e: Exception) {
            if (runAttemptCount < MAX_NUMBER_OF_RETRY) {
                Result.retry()
            } else {
                Result.failure()
            }
        }
    }
}

如果我們?cè)谝粋€(gè)應(yīng)用上作出上面的修改,程序仍然可被正常編譯庵寞。但是只要代碼被執(zhí)行狸相、WorkManager 嘗試去實(shí)例化這個(gè) CoroutineWorker 時(shí),應(yīng)用就會(huì)因?yàn)閽伋霎惓6魂P(guān)閉捐川。異常的描述為無法找到正確的方法來進(jìn)行實(shí)例化:

Caused by java.lang.NoSuchMethodException: <init> [class android.content.Context, class androidx.work.WorkerParameters]

這時(shí)我們就需要一個(gè)自定義的 WorkerFactory脓鹃。

但是別著急,我們已經(jīng)看到其中涉及的幾個(gè)步驟」帕ぃ現(xiàn)在讓我們回顧一下我們已經(jīng)做了的事情瘸右,然后深入了解其中每一步的詳細(xì)信息:

  1. 禁用默認(rèn)初始化
  2. 實(shí)現(xiàn)一個(gè)自定義 WorkerFactory
  3. 創(chuàng)建自定義配置
  4. 初始化 WorkManager

禁用默認(rèn)初始化

WorkManager 的文檔 中描述,禁用操作要在您的 AndroidManifest.xml 文件中完成岩齿。移除默認(rèn)情況下從 WorkManager 庫中自動(dòng)合并的節(jié)點(diǎn)太颤。

<!-- Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 -->
<application
…
  <provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    tools:node="remove" />
</application>

實(shí)現(xiàn)一個(gè)自定義 WorkerFactory

為了創(chuàng)建包含正確參數(shù)的 Worker,現(xiàn)在需要實(shí)現(xiàn)我們自己的工廠 (factory):

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */
class MyWorkerFactory(private val service: UpvoteStoryHttpApi) : WorkerFactory() {

    override fun createWorker(
        appContext: Context,
        workerClassName: String,
        workerParameters: WorkerParameters
    ): ListenableWorker? {
               // 這里只能處理一個(gè) Worker盹沈,請(qǐng)不要這樣做龄章!
               // 參考下文來更好地使用 DelegatingWorkerFactory
        return UpvoteStoryWorker(appContext, workerParameters, DesignerNewsService)

    }
}

創(chuàng)建一個(gè)自定義 WorkerConfiguration**

接下來,我們必須將我們的工廠注冊(cè)到我們的 WorkManager 的自定義配置中:

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */
class MyApplication : Application(), Configuration.Provider {
    override fun getWorkManagerConfiguration(): Configuration = 
        Configuration.Builder()
                     .setMinimumLoggingLevel(android.util.Log.DEBUG)
                     .setWorkerFactory(MyWorkerFactory(DesignerNewsService))
                     .build()
...
}

初始化 WorkManager

當(dāng)您的應(yīng)用中只有一個(gè) Worker 類時(shí)乞封,以上便是您所需要做的所有事情做裙。而如果您有多個(gè)或者預(yù)計(jì)未來會(huì)有多個(gè) Worker 類,更好的解決方案是使用在 2.1 版中加入的 DelegatingWorkerFactory肃晚。

使用 DelegatingWorkerFactory

我們可以通過使用 DelegatingWorkerFactory 來替代將 WorkManager 配置為直接使用某個(gè)工廠的操作锚贱。我們可以使用 DelegatingWorkerFactory 的 addFactory() 方法向其添加我們的工廠,這樣一來关串,您就有了多個(gè)工廠拧廊,其中每個(gè)都可以管理一個(gè)或多個(gè) Worker监徘。在 DelegatingWorkerFactory 中注冊(cè)您的工廠,這將有助于協(xié)調(diào)多個(gè)工廠的執(zhí)行卦绣。

在這種情況下耐量,您的工廠需要檢查是否知道如何處理作為參數(shù)傳入的 workerClassName。如果答案是否定的滤港,就返回 null廊蜒,而 DelegatingWorkerFactory 便會(huì)去尋找下一個(gè)注冊(cè)的工廠。如果沒有任何被注冊(cè)的工廠知道如何處理某個(gè)類溅漾,那么它將回退到使用反射的默認(rèn)工廠山叮。

下面是我們的工廠類代碼,修改為當(dāng)它不知道如何處理某個(gè) workerClassName 時(shí)添履,將返回 null:

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */
class MyWorkerFactory(private val service: DesignerNewsService) : WorkerFactory() {

    override fun createWorker(
        appContext: Context,
        workerClassName: String,
        workerParameters: WorkerParameters
    ): ListenableWorker? {

        return when(workerClassName) {
            UpvoteStoryWorker::class.java.name ->
                ConferenceDataWorker(appContext, workerParameters, service)
            else ->
                // 返回 null屁倔,這樣基類就可以代理到默認(rèn)的 WorkerFactory
                null
        }

    }
}

我們的 WorkManager 配置會(huì)變成:

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */
class MyApplication : Application(), Configuration.Provider {

    override fun getWorkManagerConfiguration(): Configuration {
        val myWorkerFactory = DelegatingWorkingFactory()
     myWorkerFactory.addFactory(MyWorkerFactory(service))
     // 在這里添加您應(yīng)用中可能會(huì)使用的其他 WorkerFactory

            return Configuration.Builder()
                    .setMinimumLoggingLevel(android.util.Log.INFO)
                    .setWorkerFactory(myWorkerFactory)
                    .build()
    }
...
}

如果您有多個(gè) Worker 需要不同的參數(shù),您可以創(chuàng)建第二個(gè) WorkerFactory暮胧,并通過再次調(diào)用 addFactory 來添加它锐借。

總結(jié)

WorkManager 是一個(gè)功能十分強(qiáng)大的庫,它的默認(rèn)配置已經(jīng)可以覆蓋許多常見的使用場景往衷。然而當(dāng)您遇到某些情況時(shí)钞翔,諸如需要增加日志級(jí)別或需要傳入額外參數(shù)到您的 Worker 時(shí),則需要一個(gè)自定義的配置席舍。

希望您能通過本文對(duì)此主題有一個(gè)良好的認(rèn)識(shí)布轿。如果您有任何疑問,可以在評(píng)論區(qū)中留言来颤。

接下來的文章我們將會(huì)討論如何在自定義 WorkManager 配置時(shí)使用 Dagger汰扭,感興趣的讀者請(qǐng)繼續(xù)關(guān)注。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末福铅,一起剝皮案震驚了整個(gè)濱河市萝毛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌本讥,老刑警劉巖珊泳,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拷沸,居然都是意外死亡色查,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門撞芍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秧了,“玉大人,你說我怎么就攤上這事序无⊙檎保” “怎么了衡创?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長晶通。 經(jīng)常有香客問我璃氢,道長,這世上最難降的妖魔是什么狮辽? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任一也,我火速辦了婚禮,結(jié)果婚禮上喉脖,老公的妹妹穿的比我還像新娘椰苟。我一直安慰自己,他們只是感情好树叽,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布舆蝴。 她就那樣靜靜地躺著,像睡著了一般题诵。 火紅的嫁衣襯著肌膚如雪洁仗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天性锭,我揣著相機(jī)與錄音京痢,去河邊找鬼。 笑死篷店,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的臭家。 我是一名探鬼主播疲陕,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼钉赁!你這毒婦竟也來了蹄殃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤你踩,失蹤者是張志新(化名)和其女友劉穎诅岩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體带膜,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吩谦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了膝藕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片式廷。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖芭挽,靈堂內(nèi)的尸體忽然破棺而出滑废,到底是詐尸還是另有隱情蝗肪,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布蠕趁,位于F島的核電站薛闪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏俺陋。R本人自食惡果不足惜豁延,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望倔韭。 院中可真熱鬧术浪,春花似錦、人聲如沸寿酌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽醇疼。三九已至硕并,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間秧荆,已是汗流浹背倔毙。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乙濒,地道東北人陕赃。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像颁股,于是被迫代替她去往敵國和親么库。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359