自定義 WorkManager —— 基礎概念

WorkManager 是一個 Android Jetpack 擴展庫,它可以讓您輕鬆規劃那些可延後、異步但又需要可靠運行的任務。對於絕大部分後臺執行任務來說,使用 WorkManager 是目前 Android 平臺上的最佳實踐。

目前爲止本系列已經討論過:

在本篇文章中,我們將會討論自定義配置相關的內容,包括:

  • 爲什麼可能會需要自定義配置
  • 如何聲明自定義配置
  • WorkerFactory 以及自定義 WorkerFactory 的原因
  • DelegatingWorkerFactory 詳解

本系列的下一篇文章將對依賴注入和 Dagger 展開討論,請持續關注我們。

使用 WorkManager 時,您需要自己定義 Worker/CoroutineWorker 或任何 ListenableWorker 的派生類。WorkManager 會在正確的時間點實例化您的 Worker,其時機獨立於您應用的運行,不受其運行狀態的影響。爲了可以初始化您的 Worker,WorkManager 會使用一個 WorkerFactory

默認 WorkerFactory 所創建的 Worker 只包含兩個參數:

如果您需要通過 Worker 的構造函數傳入更多參數,則需要一個自定義的 WorkerFactory。

延伸閱讀 : 我們講過默認的 WorkerFactory 使用反射來實例化正確的 ListenableWorker 類,但當我們的 Worker 類的類名被 R8 (或 ProGuard) 最小化之後,這個操作就會失敗。爲了避免這種情況,WorkManager 包含了一個 proguard-rules.pro 文件來避免您的 Worker 類的類名被混淆。

自定義配置和 WorkerFactory

WorkManager 類遵循 單例模式,而且它只能在實例化之前進行配置。這意味着,如果您想自定義它的配置,就必須先禁用默認配置。

如果您嘗試通過 initialize() 方法再次初始化 WorkManager,該方法就會拋出一個異常 (於 1.0.0 版本中加入)。爲了避免異常,您需要禁用默認的初始化。您可以稍後在您的 Application 的 onCreate 方法中配置和初始化您的 WorkManager。

2.1.0 版本 中加入了一個更好的初始化 WorkManager 的方式。您可以通過在您的 Application 類中實現 WorkManager 的 Configuration.Provider 接口的方式來使用按需初始化。接下來,您只需要使用 getInstance(context) 獲得實例,WorkManager 就會通過您的配置初始化它自己。

可配置參數

如上所講,您可以配置用來創建 Worker 的 WorkerFactory,但是您也可以自定義其他的參數。WorkManager 的 Configuration.Builder 參考指南中包含了參數的完整列表。這裏我想強調兩個附加參數:

  • Logging 級別
  • JobId 範圍

當我們有需要時,可以通過修改日誌級別方便地理解 WorkManager 中正在發生什麼。關於這個話題,我們有一個 專門的文檔頁。您也可以查看 Advanced WorkManager codelab 實戰教程,以瞭解此功能在真實示例中的實現,以及您可以通過此功能獲取到什麼樣的信息。

如果以在我們的應用中使用 JobScheduler API 一樣的方式使用 WorkManager,我們可能也會想要自定義 JobId 範圍。因爲在這種情況下,您會想要避免在同一個地方使用相同的 JobId 範圍。版本 2.4.0 中也加入了一個新的 Lint 規則 來覆蓋這種情況。

WorkManager 的 WorkerFactory

我們已經知道,WorkManager 有一個默認的 WorkerFactory,它可以根據我們經由 WorkRequest 傳入的 Worker 類的類名,通過反射來找到應該實例化的 Worker 類。

⚠️ 如果您在創建了一個 WorkRequest 後重構了應用,併爲您的 Worker 類起了另一個名字,WorkManager 就會因爲無法找到正確的類而拋出一個 ClassNotFoundException。

您可能會想要爲您的 Worker 的構造函數添加其他參數。假設您有一個 Worker 需要引用一個 Retrofit 服務來跟遠程服務器進行通訊:

/* 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()
            }
        }
    }
}

如果我們在一個應用上作出上面的修改,程序仍然可被正常編譯。但是隻要代碼被執行、WorkManager 嘗試去實例化這個 CoroutineWorker 時,應用就會因爲拋出異常而被關閉。異常的描述爲無法找到正確的方法來進行實例化:

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

這時我們就需要一個自定義的 WorkerFactory。

但是彆着急,我們已經看到其中涉及的幾個步驟。現在讓我們回顧一下我們已經做了的事情,然後深入瞭解其中每一步的詳細信息:

  1. 禁用默認初始化
  2. 實現一個自定義 WorkerFactory
  3. 創建自定義配置
  4. 初始化 WorkManager

禁用默認初始化

WorkManager 的文檔 中描述,禁用操作要在您的 AndroidManifest.xml 文件中完成。移除默認情況下從 WorkManager 庫中自動合併的節點。

<!-- 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>

實現一個自定義 WorkerFactory

爲了創建包含正確參數的 Worker,現在需要實現我們自己的工廠 (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? {
               // 這裏只能處理一個 Worker,請不要這樣做!
               // 參考下文來更好地使用 DelegatingWorkerFactory
        return UpvoteStoryWorker(appContext, workerParameters, DesignerNewsService)

    }
}

創建一個自定義 WorkerConfiguration**

接下來,我們必須將我們的工廠註冊到我們的 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

當您的應用中只有一個 Worker 類時,以上便是您所需要做的所有事情。而如果您有多個或者預計未來會有多個 Worker 類,更好的解決方案是使用在 2.1 版中加入的 DelegatingWorkerFactory

使用 DelegatingWorkerFactory

我們可以通過使用 DelegatingWorkerFactory 來替代將 WorkManager 配置爲直接使用某個工廠的操作。我們可以使用 DelegatingWorkerFactory 的 addFactory() 方法向其添加我們的工廠,這樣一來,您就有了多個工廠,其中每個都可以管理一個或多個 Worker。在 DelegatingWorkerFactory 中註冊您的工廠,這將有助於協調多個工廠的執行。

在這種情況下,您的工廠需要檢查是否知道如何處理作爲參數傳入的 workerClassName。如果答案是否定的,就返回 null,而 DelegatingWorkerFactory 便會去尋找下一個註冊的工廠。如果沒有任何被註冊的工廠知道如何處理某個類,那麼它將回退到使用反射的默認工廠。

下面是我們的工廠類代碼,修改爲當它不知道如何處理某個 workerClassName 時,將返回 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,這樣基類就可以代理到默認的 WorkerFactory
                null
        }

    }
}

我們的 WorkManager 配置會變成:

/* 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))
     // 在這裏添加您應用中可能會使用的其他 WorkerFactory

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

如果您有多個 Worker 需要不同的參數,您可以創建第二個 WorkerFactory,並通過再次調用 addFactory 來添加它。

總結

WorkManager 是一個功能十分強大的庫,它的默認配置已經可以覆蓋許多常見的使用場景。然而當您遇到某些情況時,諸如需要增加日誌級別或需要傳入額外參數到您的 Worker 時,則需要一個自定義的配置。

希望您能通過本文對此主題有一個良好的認識。如果您有任何疑問,可以在評論區中留言。

接下來的文章我們將會討論如何在自定義 WorkManager 配置時使用 Dagger,感興趣的讀者請繼續關注。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章