在本篇文章中,我們將會討論使用 Dagger 自定義配置相關的內容,包括:
在我們的 WorkerFactory 中使用 Dagger 注入參數⚠️ 本文擴展自上一篇自定義 WorkManager 的文章。強烈建議在閱讀本文之前先去閱讀上一篇文章!為什麼是 Dagger
https://developer.android.google.cn/training/dependency-injection/dagger-basicshttps://codelabs.developers.google.com/codelabs/android-dagger/index.html行文中我假設您對 Dagger 庫和依賴注入概念均已有所了解。即使您正在使用其他的依賴注入庫,或者根本沒有使用依賴庫,本文所呈現的概念依然會對您有所幫助。上一篇文章中,我們探索了如何自定義 WorkManager,其中包括如何使用 DelegatingWorkerFactory 將附加的參數傳遞到 Worker 中。在本篇文章中,讓我們看一看如何使用 Dagger 注入這些參數。使用 Dagger 將參數注入到 WorkerFactory
如果您當前已經在使用 Dagger 來管理依賴,那麼首先需要將 Dagger 集成到您的 WorkerFactory 中。如果您使用 Dagger 在您的應用中傳遞 Retrofit 服務的引用,而且您想要將其傳遞給您的 Worker,則需要使用 Dagger 將該引用注入到自定義的 WorkerFactory 中。這樣一來,WorkFactory 就可以使用 Retrofit 的引用作為額外參數來初始化您的 Worker。假設這次我們有了 Dagger 注入的 Retrofit 服務的引用。但是這並沒有改變 WorkManager 需要自定義工廠和自定義配置的局面。簡單來說,我們將用 Dagger 把新的參數注入到我們的工廠中。@Singletonclass MyWorkerFactory @Inject constructor( service: DesignerNewsService) : DelegatingWorkerFactory() { init { addFactory(myWorkerFactory(service)) }} 提示: 如果想要 Dagger 能夠注入這個值,我們必須把它放進 Dagger 的圖中。這就是為什麼我們給 Factory 添加了一個 @inject 註解。本示例中,我們在 Application 裡使用一個 AppComponent 來設置 Dagger。AppComponent 稍後會在 MyApplication 中初始化,從而讓 Dagger 可以進行成員注入:@Singleton@Component(modules = [AppModule::class])interface AppComponent {
@Component.Factory interface Factory { fun create(@BindsInstance context: Context): AppComponent }
fun inject(application: MyApplication)}隨後,我們在 Application 類中注入我們的組件:class MyApplication : Application(), Configuration.Provider {
lateinit var appComponent: AppComponent
override fun onCreate() { super.onCreate() appComponent = DaggerAppComponent.factory().create(applicationContext) appComponent.inject(this) } ...}由於 Dagger 已經知道如何提供 MyWorkerFactory 實例,您現在可以通過使用 @Inject 來從 Dagger 圖中獲取 MyWorkerFactory,並在 getWorkManagerConfiguration 方法中進行使用。
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
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.") Result.success() } catch (e: Exception) { Timber.e("ConferenceDataService failed. It will retry.") if (runAttemptCount < MAX_NUMBER_OF_RETRY) { Result.retry() } else { Result.failure() } } }}源碼: ConferenceDataWorker.kt
如您所見,由於在 WorkerFactory 級別處理了參數的傳遞,因此在 Worker 類上沒有 Dagger 註解。這個參數是 Dagger 已知的,因此可以將其直接注入到我們的自定義 WorkerFactory 中: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 -> null } }}源碼: ConferenceDataWorkerFactory.kt
源碼: ConferenceDataWorkerFactory.kthttps://github.com/google/iosched/blob/f237889eb568e501add09ca0728af9760ea7a193/shared/src/main/java/com/google/samples/apps/iosched/shared/data/work/ConferenceDataWorkerFactory.kt#L25這裡仍然沒有 Dagger 註解. 原因是我們使用了一個 DelegatingWorkerFactory 來協調那些單個的工廠 (此時,我們在 IOsched 中只有一個工廠,但是我們以一種在需要時可以直接添加更多工廠的方式來構建它):@Singletonclass IoschedWorkerFactory @Inject constructor( refreshConferenceDataUseCase: RefreshConferenceDataUseCase) : DelegatingWorkerFactory() { init { addFactory(ConferenceDataWorkerFactory(refreshConferenceDataUseCase)) }}源碼: IoschedWorkerFactory.kt
IoschedWorkerFactory.kt
https://github.com/google/iosched/blob/adssched/shared/src/main/java/com/google/samples/apps/iosched/shared/data/work/IoschedWorkerFactory.kt
源碼: IoschedWorkerFactory.kt
https://github.com/google/iosched/blob/f237889eb568e501add09ca0728af9760ea7a193/shared/src/main/java/com/google/samples/apps/iosched/shared/data/work/IoschedWorkerFactory.kt#L24
OK,這裡就是使用 Dagger 向我們的構造函數注入參數的地方。在這個應用中,我們決定使用按需初始化,並且使用 Dagger 注入所有配置:@Inject lateinit var workerConfiguration: Configuration
override fun getWorkManagerConfiguration(): Configuration { return workerConfiguration}源碼: MainApplication.kt
https://github.com/google/iosched/blob/f237889eb568e501add09ca0728af9760ea7a193/mobile/src/main/java/com/google/samples/apps/iosched/MainApplication.kt#L38這使我們可以為不同的構建類型注入不同的配置。尤其是,我們為調試構建注入了日誌級別設置為 DEBUG 的配置:@Singleton@Providesfun provideWorkManagerConfiguration( ioschedWorkerFactory: IoschedWorkerFactory): Configuration { return Configuration.Builder() .setMinimumLoggingLevel(android.util.Log.DEBUG) .setWorkerFactory(ioschedWorkerFactory) .build()}源碼: debugRelease SharedModule.kt
源碼: debugRelease SharedModule.kt
https://github.com/google/iosched/blob/f237889eb568e501add09ca0728af9760ea7a193/shared/src/debugRelease/java/com/google/samples/apps/iosched/shared/di/SharedModule.kt#L184
SharedModule.kt (在 shared/src/staging/j/c/g/s/a/i/shared/di/ 中)@Singleton@Providesfun provideWorkManagerConfiguration( ioschedWorkerFactory: IoschedWorkerFactory): Configuration { return Configuration.Builder() .setWorkerFactory(ioschedWorkerFactory) .build()}源碼: staging SharedModule.kt
https://github.com/google/iosched/blob/adssched/shared/src/staging/java/com/google/samples/apps/iosched/shared/di/SharedModule.kthttps://github.com/google/iosched/blob/f237889eb568e501add09ca0728af9760ea7a193/shared/src/staging/java/com/google/samples/apps/iosched/shared/di/SharedModule.kt#L135當我們首次獲取 WorkManager 實例時,WorkManager 將按需初始化。當我們收到 Firebase 消息以獲取新的時間表時,就會觸發這個操作:IoschedFirebaseMessagingService.ktprivate 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
IoschedFirebaseMessagingService.kthttps://github.com/google/iosched/blob/adssched/shared/src/main/java/com/google/samples/apps/iosched/shared/fcm/IoschedFirebaseMessagingService.kt源碼: IoschedFirebaseMessagingService.kthttps://github.com/google/iosched/blob/f237889eb568e501add09ca0728af9760ea7a193/shared/src/main/java/com/google/samples/apps/iosched/shared/fcm/IoschedFirebaseMessagingService.kt#L51至此,我們結束了探索如何使用 Dagger 把參數注入到您的 Worker,同時也了解了如何將 WorkManager 集成到 iosched 這類的大型應用中。
WorkManager 是一個功能十分強大的庫,它的默認配置已經可以覆蓋許多常見的使用場景。然而當您遇到某些情況時,諸如需要增加日誌級別或需要把額外參數傳入到您的 Worker 時,則需要一個自定義的配置。希望通過最近兩篇文章所做的介紹,能讓您對自定義 WorkManager 有一個良好的認識。如果您有任何疑問,可以在評論區中留言。 點擊屏末 | 閱讀原文 | 查看 Android 官方中文文檔 —— 使用 WorkManager 調度任務