使用 Dagger 自定義 WorkManager

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

若是您一直關注本系列文章,則會發現咱們已經討論過:java

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

  • 在咱們的 WorkerFactory 中使用 Dagger 注入參數
  • 按需初始化
⚠️ 本文擴展自上一篇自定義 WorkManager 的文章。強烈建議在閱讀本文以前先去閱讀 上一篇文章

爲何是 Dagger

Dagger 是 Android 開發的首選依賴注入庫,Google 正積極參與它的開發。若是您還沒開始使用 Dagger,或者但願瞭解更多有關它的信息,請查閱如下資料:官方指南Codelab 實戰教程 以及咱們近期發佈的關於在 最新 Android Studio 中使用 Dagger 導航 的文章。ios

行文中我假設您對 Dagger 庫和依賴注入概念均已有所瞭解。git

即便您正在使用其餘的依賴注入庫,或者根本沒有使用依賴庫,本文所呈現的概念依然會對您有所幫助。github

回顧

上一篇文章 中,咱們探索瞭如何自定義 WorkManager,其中包括如何使用 DelegatingWorkerFactory將附加的參數傳遞到 Worker 中。在本篇文章中,讓咱們看一看如何使用 Dagger 注入這些參數。數據庫

使用 Dagger 將參數注入到 WorkerFactory

若是您當前已經在使用 Dagger 來管理依賴,那麼首先須要將 Dagger 集成到您的 WorkerFactory 中。若是您使用 Dagger 在您的應用中傳遞 Retrofit 服務的引用,並且您想要將其傳遞給您的 Worker,則須要使用 Dagger 將該引用注入到自定義的 WorkerFactory 中。這樣一來,WorkFactory 就可使用 Retrofit 的引用做爲額外參數來初始化您的 Worker。緩存

假設此次咱們有了 Dagger 注入的 Retrofit 服務的引用。可是這並無改變 WorkManager 須要自定義工廠和自定義配置的局面。簡單來講,咱們將用 Dagger 把新的參數注入到咱們的工廠中。app

/* Copyright 2020 Google LLC.    
   SPDX-License-Identifier: Apache-2.0 */
@Singleton
class MyWorkerFactory @Inject constructor(
    service: DesignerNewsService
) : DelegatingWorkerFactory() {
    init {
        addFactory(myWorkerFactory(service))
 // Add here other factories that you may need in your application
 // 在這裏添加您應用中可能會使用的其餘工廠
    }
}
⚠️ 提示:若是想要 Dagger 可以注入這個值,咱們必須把它放進 Dagger 的圖中。這就是爲何咱們給 Factory 添加了一個 @inject 註解。

本示例中,咱們在 Application 裏使用一個 AppComponent 來設置 Dagger。AppComponent 稍後會在 MyApplication 中初始化,從而讓 Dagger 能夠進行成員注入:異步

/* Copyright 2020 Google LLC.    
   SPDX-License-Identifier: Apache-2.0 */
@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {

  @Component.Factory
  interface Factory {
    fun create(@BindsInstance context: Context): AppComponent
  }

  fun inject(application: MyApplication)
}

隨後,咱們在 Application 類中注入咱們的組件:

 

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

    // other @Inject variables
    // 另外一個 @inject 變量

    lateinit var appComponent: AppComponent

    override fun onCreate() {
        super.onCreate()
        appComponent = DaggerAppComponent.factory().create(applicationContext)
        appComponent.inject(this)
    }
   ...
}

因爲 Dagger 已經知道如何提供 MyWorkerFactory 實例,您如今能夠經過使用 @Inject 來從 Dagger 圖中獲取 MyWorkerFactory,並在 getWorkManagerConfiguration 方法中進行使用。

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

    @Inject lateinit var myWorkerFactory: MyWorkerFactory 
    ...

    override fun getWorkManagerConfiguration(): Configuration =
        Configuration.Builder()
                     .setMinimumLoggingLevel(android.util.Log.INFO)
                     .setWorkerFactory(myWorkerFactory)
                     .build()
    ...
}

項目中的其餘部分保持不變。

生產環境示例

在使用中型或大型數據庫時,Dagger 的表現十分亮眼。咱們升級了 Google I/O 與 Android 開發峯會的時間表應用:iosched,使其用上 WorkManager 和 Dagger,它同時也是咱們用於展現協程 Flow 最佳實踐的應用,詳情請查看文章:基於 Android 開發者峯會應用的協程 Flow 最佳實踐

2019 Android 開發者峯會應用 中,JobScheduler 被 WorkManager 所取代,用於強制更新時間表。爲了能將時間表的緊急更新強制推送至設備,咱們爲應用添加了這個功能。

在這種狀況下,咱們須要在 Worker 中使用的額外參數是 refreshEventDataUseCase。您能夠在 github 的 iosched 倉庫中的 ADSsched 分支 中查看引入了此功能的提交 (commits)。

讓咱們從 Worker 自己開始,看看最重要的幾部分:

ConferenceDataWorker.kt

/* Copyright 2019 Google LLC.    
   SPDX-License-Identifier: Apache-2.0 */
/**
 * A Job that refreshes the conference data in the repository (if the app is active) and
 * in the cache (if the app is not active).
 * 一個用來刷新資源庫(當應用活躍時)以及緩存(當應用不活躍時)中的會議數據的任務。
 */
class ConferenceDataWorker(
    ctx: Context,
    params: WorkerParameters,
    private val refreshEventDataUseCase: RefreshConferenceDataUseCase
) : CoroutineWorker(ctx, params) {

    override suspend fun doWork(): Result {

        Timber.i("ConferenceDataService triggering refresh conference data.")

        return try {
            refreshEventDataUseCase(Unit)
            Timber.d("ConferenceDataService finished successfully.")
            // Finishing indicating this job doesn't need to be rescheduled.
            // Finishing 意味着這個任務不須要被從新安排執行。
            Result.success()
        } catch (e: Exception) {
            Timber.e("ConferenceDataService failed. It will retry.")
            // Indicating worker should be retried
            // 意味着 Worker 應該被重試
            if (runAttemptCount < MAX_NUMBER_OF_RETRY) {
                Result.retry()
            } else {
                Result.failure()
            }
        }
    }
}

源碼:ConferenceDataWorker.kt

如您所見,因爲在 WorkerFactory 級別處理了參數的傳遞,所以在 Worker 類上沒有 Dagger 註解。

這個參數是 Dagger 已知的,所以能夠將其直接注入到咱們的自定義 WorkerFactory 中:

 

/* Copyright 2019 Google LLC.    
   SPDX-License-Identifier: Apache-2.0 */
class ConferenceDataWorkerFactory(
    private val refreshEventDataUseCase: RefreshConferenceDataUseCase
) : WorkerFactory() {

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

        return when (workerClassName) {
            ConferenceDataWorker::class.java.name ->
                ConferenceDataWorker(appContext, workerParameters, refreshEventDataUseCase)
            else ->
                // Return null, so that the base class can delegate to the default WorkerFactory.
                // 返回 null,這樣基類就能夠代理到默認的 WorkerFactory
                null
        }
    }
}

源碼:ConferenceDataWorkerFactory.kt

這裏仍然沒有 Dagger 註解...... 緣由是咱們使用了一個 DelegatingWorkerFactory 來協調那些單個的工廠(此時,咱們在 IOsched 中只有一個工廠,可是咱們以一種在須要時能夠直接添加更多工廠的方式來構建它):

IoschedWorkerFactory.kt
 

/* Copyright 2019 Google LLC.    
   SPDX-License-Identifier: Apache-2.0 */
@Singleton
class IoschedWorkerFactory @Inject constructor(
    refreshConferenceDataUseCase: RefreshConferenceDataUseCase
) : DelegatingWorkerFactory() {
    init {
        addFactory(ConferenceDataWorkerFactory(refreshConferenceDataUseCase))
    }
}

源碼:IoschedWorkerFactory.kt

OK,這裏就是使用 Dagger 向咱們的構造函數注入參數的地方。

在這個應用中,咱們決定使用按需初始化,而且使用 Dagger 注入全部配置:

/* Copyright 2019 Google LLC.    
   SPDX-License-Identifier: Apache-2.0 */
@Inject lateinit var workerConfiguration: Configuration

// 使用 DelegatingWorkerFactory 爲 WorkManager 設置自定義配置
override fun getWorkManagerConfiguration(): Configuration {
    return workerConfiguration
}

源碼: MainApplication.kt

這使咱們能夠爲不一樣的構建類型注入不一樣的配置。尤爲是,咱們爲調試構建注入了日誌級別設置爲 DEBUG 的配置:

/* Copyright 2019 Google LLC.    
   SPDX-License-Identifier: Apache-2.0 */
@Singleton
@Provides
fun provideWorkManagerConfiguration(
    ioschedWorkerFactory: IoschedWorkerFactory
): Configuration {
    return Configuration.Builder()
        .setMinimumLoggingLevel(android.util.Log.DEBUG)
        .setWorkerFactory(ioschedWorkerFactory)
        .build()
}

源碼:debugRelease SharedModule.kt

同時,發佈版本使用默認調試級別來設置自定義工廠:

SharedModule.kt (在 shared/src/staging/j/c/g/s/a/i/shared/di/ 中)

/* Copyright 2019 Google LLC.    
   SPDX-License-Identifier: Apache-2.0 */
@Singleton
@Provides
fun provideWorkManagerConfiguration(
    ioschedWorkerFactory: IoschedWorkerFactory
): Configuration {
    return Configuration.Builder()
        .setWorkerFactory(ioschedWorkerFactory)
        .build()
}

源碼:staging SharedModule.kt

當咱們首次獲取 WorkManager 實例時,WorkManager 將按需初始化。當咱們收到 Firebase 消息以獲取新的時間表時,就會觸發這個操做:

IoschedFirebaseMessagingService.kt

/* Copyright 2019 Google LLC.    
   SPDX-License-Identifier: Apache-2.0 */
private fun scheduleFetchEventData() {
    val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build()

    val conferenceDataWorker = OneTimeWorkRequestBuilder<ConferenceDataWorker>()
        .setInitialDelay(MINIMUM_LATENCY, TimeUnit.SECONDS)
        .setConstraints(constraints)
        .build()

    val operation = WorkManager.getInstance(this)
        .enqueueUniqueWork(
            uniqueConferenceDataWorker,
            ExistingWorkPolicy.KEEP,
            conferenceDataWorker)
        .result

    operation.addListener(
        { Timber.i("ConferenceDataWorker enqueued..") },
        { it.run() }
    )
}

源碼:IoschedFirebaseMessagingService.kt

至此,咱們結束了探索如何使用 Dagger 把參數注入到您的 Worker,同時也瞭解瞭如何將 WorkManager 集成到 iosched 這類的大型應用中。

總結

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

但願經過最近兩篇文章所作的介紹,能讓您對自定義 WorkManager 有一個良好的認識。若是您有任何疑問,能夠在評論區中留言。

編碼愉快!

相關文章
相關標籤/搜索