Dagger2:无法在 WorkManager 中注入依赖项

Dagger2: Unable to inject dependencies in WorkManager

据我了解,Dagger 尚不支持在 Worker 中注入。但是正如人们所建议的那样,有一些解决方法。我尝试按照在线示例通过多种方式来完成此操作,但 none 对我有用。

当我不尝试向 Worker class 注入任何东西时,代码工作正常,只是我不能做我想做的事,因为我需要访问一些 DAO 和服务。如果我在这些依赖项上使用 @Inject,则依赖项要么为空,要么 worker 永远不会启动,即调试器甚至不进入 Worker class.

例如,我尝试这样做:

@Component(modules = {Module.class})
public interface Component{

    void inject(MyWorker myWorker);
}

@Module
public class Module{

    @Provides
    public MyRepository getMyRepo(){
        return new myRepository();
    }

}

在我的工人中

@Inject
MyRepository myRepo;

public MyWorker() {
    DaggerAppComponent.builder().build().inject(this);
}

但是执行永远不会到达工人。如果我删除构造函数,myRepo 依赖项将保持为空。

我尝试做很多其他事情,但 none 工作。有没有办法做到这一点?谢谢!!

在 WorkManager alpha09 中有一个新的 WorkerFactory,您可以使用它以您想要的方式初始化 Worker

  • 使用接受 ApplicationContextWorkerParams 的新 Worker 构造函数。
  • 通过 Configuration 注册 WorkerFactory 的实现。
  • 创建一个configuration并注册新创建的WorkerFactory
  • 使用此配置初始化 WorkManager(同时删除代表您初始化 WorkManagerContentProvider)。

您需要执行以下操作:

public DaggerWorkerFactory implements WorkerFactory {
  @Nullable Worker createWorker(
  @NonNull Context appContext,
  @NonNull String workerClassName,
  @NonNull WorkerParameters workerParameters) {

  try {
      Class<? extends Worker> workerKlass = Class.forName(workerClassName).asSubclass(Worker.class);
      Constructor<? extends Worker> constructor = 
      workerKlass.getDeclaredConstructor(Context.class, WorkerParameters.class);

      // This assumes that you are not using the no argument constructor 
      // and using the variant of the constructor that takes in an ApplicationContext
      // and WorkerParameters. Use the new constructor to @Inject dependencies.
      Worker instance = constructor.newInstance(appContext,workerParameters);
      return instance;
    } catch (Throwable exeption) {
      Log.e("DaggerWorkerFactory", "Could not instantiate " + workerClassName, e);
      // exception handling
      return null;
    }
  }
}

// Create a configuration
Configuration configuration = new Configuration.Builder()
  .setWorkerFactory(new DaggerWorkerFactory())
  .build();

// Initialize WorkManager
WorkManager.initialize(context, configuration);

我用Dagger2 Multibindings来解决这个问题。

类似的方法用于注入 ViewModel 对象(描述得很好 )。与视图模型案例的重要区别是 Worker 构造函数中存在 ContextWorkerParameters 参数。要向 worker 构造函数提供这些参数,应使用中间 dagger 组件。

  1. @Inject 注释 Worker 的构造函数,并提供所需的依赖项作为构造函数参数。

    class HardWorker @Inject constructor(context: Context,
                                         workerParams: WorkerParameters,
                                         private val someDependency: SomeDependency)
        : Worker(context, workerParams) {
    
        override fun doWork(): Result {
            // do some work with use of someDependency
            return Result.SUCCESS
        }
    }
    
  2. 创建指定工作人员多绑定映射条目键的自定义注释。

    @MustBeDocumented
    @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
    @Retention(AnnotationRetention.RUNTIME)
    @MapKey
    annotation class WorkerKey(val value: KClass<out Worker>)
    
  3. 定义工作者绑定。

    @Module
    interface HardWorkerModule {
    
        @Binds
        @IntoMap
        @WorkerKey(HardWorker::class)
        fun bindHardWorker(worker: HardWorker): Worker
    }
    
  4. 定义中间组件及其构建器。该组件必须具有从依赖图中获取工作人员映射的方法,并在其模块中包含工作人员绑定模块。此外,该组件必须声明为其父组件的子组件,并且父组件必须具有获取子组件构建器的方法。

    typealias WorkerMap = MutableMap<Class<out Worker>, Provider<Worker>>
    
    @Subcomponent(modules = [HardWorkerModule::class])
    interface WorkerFactoryComponent {
    
        fun workers(): WorkerMap
    
        @Subcomponent.Builder
        interface Builder {
            @BindsInstance
            fun setParameters(params: WorkerParameters): Builder
            @BindsInstance
            fun setContext(context: Context): Builder
            fun build(): WorkerFactoryComponent
        }
    }
    
    // parent component
    @ParentComponentScope
    @Component(modules = [
                //, ...
            ])
    interface ParentComponent {
    
        // ...
    
        fun workerFactoryComponent(): WorkerFactoryComponent.Builder
    }
    
  5. 实施WorkerFactory。它将创建中间组件,获取worker map,找到相应的worker provider并构造请求的worker。

    class DIWorkerFactory(private val parentComponent: ParentComponent) : WorkerFactory() {
    
        private fun createWorker(workerClassName: String, workers: WorkerMap): ListenableWorker? = try {
            val workerClass = Class.forName(workerClassName).asSubclass(Worker::class.java)
    
            var provider = workers[workerClass]
            if (provider == null) {
                for ((key, value) in workers) {
                    if (workerClass.isAssignableFrom(key)) {
                        provider = value
                        break
                    }
                }
            }
    
            if (provider == null)
                throw IllegalArgumentException("no provider found")
            provider.get()
        } catch (th: Throwable) {
            // log
            null
        }
    
        override fun createWorker(appContext: Context,
                                  workerClassName: String,
                                  workerParameters: WorkerParameters) = parentComponent
                .workerFactoryComponent()
                .setContext(appContext)
                .setParameters(workerParameters)
                .build()
                .workers()
                .let { createWorker(workerClassName, it) }
    }
    
  6. 使用自定义工作工厂手动初始化 WorkManager(每个进程只能执行一次)。不要忘记在清单中禁用自动初始化。

清单:

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

申请onCreate

    val configuration = Configuration.Builder()
            .setWorkerFactory(DIWorkerFactory(parentComponent))
            .build()
    WorkManager.initialize(context, configuration)
  1. 使用工人

    val request = OneTimeWorkRequest.Builder(workerClass).build(HardWorker::class.java)
    WorkManager.getInstance().enqueue(request)
    

观看此 talk 以了解有关 WorkManager 功能的更多信息。

概述

您需要查看 WorkerFactory,从 1.0.0-alpha09 开始可用。

以前的解决方法依赖于能够使用默认的 0-arg 构造函数创建 Worker,但从 1.0.0-alpha10 开始,这不再是一个选项。

例子

假设您有一个名为 DataClearingWorkerWorker subclass,并且此 class 需要来自您的 Dagger 图的 Foo

class DataClearingWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {

    lateinit var foo: Foo

    override fun doWork(): Result {
        foo.doStuff()
        return Result.SUCCESS
    }
}

现在,您不能直接实例化其中一个 DataClearingWorker 实例。所以你需要定义一个 WorkerFactory subclass 可以为你创建其中一个;不仅要创建一个,还要设置 Foo 字段。

class DaggerWorkerFactory(private val foo: Foo) : WorkerFactory() {

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

        val workerKlass = Class.forName(workerClassName).asSubclass(Worker::class.java)
        val constructor = workerKlass.getDeclaredConstructor(Context::class.java, WorkerParameters::class.java)
        val instance = constructor.newInstance(appContext, workerParameters)

        when (instance) {
            is DataClearingWorker -> {
                instance.foo = foo
            }
            // optionally, handle other workers               
        }

        return instance
    }
}

最后,您需要创建一个可以访问 FooDaggerWorkerFactory。您可以使用普通 Dagger 方式执行此操作。

@Provides
@Singleton
fun workerFactory(foo: Foo): WorkerFactory {
    return DaggerWorkerFactory(foo)
}

禁用默认 WorkManager 初始化

您还需要禁用默认 WorkManager 初始化(自动发生)并手动初始化。

如何执行此操作取决于您使用的 androidx.work 版本:

2.6.0 及更高版本:

AndroidManifest.xml中添加:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="YOUR_APP_PACKAGE.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.0 之前:

AndroidManifest.xml中添加:

 <provider
        android:name="androidx.work.impl.WorkManagerInitializer"
        android:authorities="YOUR_APP_PACKAGE.workmanager-init"
        android:enabled="false"
        android:exported="false"
        tools:replace="android:authorities" />

请务必将 YOUR_APP_PACKAGE 替换为您实际的应用程序包。上面的 <provider 块进入 inside 你的 <application 标签..所以它是你的 Activities, Services 等的兄弟...

在你的 Application subclass,(或者你喜欢的其他地方),你可以手动初始化 WorkManager.

@Inject
lateinit var workerFactory: WorkerFactory

private fun configureWorkManager() {
    val config = Configuration.Builder()
        .setWorkerFactory(workerFactory)
        .build()

    WorkManager.initialize(this, config)
}

2020/06更新

有了 Hilt and Hilt for Jetpack,事情变得容易多了。

有了 Hilt,您所要做的就是

  1. 向您的应用程序添加注释 @HiltAndroidApp class
  2. 在应用程序 class
  3. 字段中注入开箱即用 HiltWorkerFactory
  4. 实现接口Configuration.Provider和return步骤2中注入的工作工厂。

现在,将Worker的构造函数上的注解从@Inject改为@WorkerInject

class ExampleWorker @WorkerInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    someDependency: SomeDependency // your own dependency
) : Worker(appContext, workerParams) { ... }

就是这样!

(另外,不要忘记禁用默认工作管理器初始化

===========

旧解

从版本 1.0.0-beta01 开始,这里是使用 WorkerFactory 实现 Dagger 注入。

概念来自这篇文章: https://medium.com/@nlg.tuan.kiet/bb9f474bde37 而我只是post自己一步一步实现它(在科特林).

===========

此实现试图实现的目标是:

每次要给一个worker添加依赖,就把依赖放在相关的worker中class

===========

1.为所有工人工厂添加接口

IWorkerFactory.kt

interface IWorkerFactory<T : ListenableWorker> {
    fun create(params: WorkerParameters): T
}

2. 添加一个简单的 Worker class 和一个 实现 IWorkerFactory[=130 的 Factory =] 以及这个工人的依赖性

HelloWorker.kt

class HelloWorker(
    context: Context,
    params: WorkerParameters,
    private val apiService: ApiService // our dependency
): Worker(context, params) {
    override fun doWork(): Result {
        Log.d("HelloWorker", "doWork - fetchSomething")
        return apiService.fetchSomething() // using Retrofit + RxJava
            .map { Result.success() }
            .onErrorReturnItem(Result.failure())
            .blockingGet()
    }

    class Factory @Inject constructor(
        private val context: Provider<Context>, // provide from AppModule
        private val apiService: Provider<ApiService> // provide from NetworkModule
    ) : IWorkerFactory<HelloWorker> {
        override fun create(params: WorkerParameters): HelloWorker {
            return HelloWorker(context.get(), params, apiService.get())
        }
    }
}

3.为Dagger的multi-binding

添加一个WorkerKey

WorkerKey.kt

@MapKey
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class WorkerKey(val value: KClass<out ListenableWorker>)

4.multi-binding worker添加一个Dagger模块(实际上是multi-binding factory)

WorkerModule.kt

@Module
interface WorkerModule {
    @Binds
    @IntoMap
    @WorkerKey(HelloWorker::class)
    fun bindHelloWorker(factory: HelloWorker.Factory): IWorkerFactory<out ListenableWorker>
    // every time you add a worker, add a binding here
}

5.WorkerModule放入AppComponent。这里我使用dagger-android构造组件class

AppComponent.kt

@Singleton
@Component(modules = [
    AndroidSupportInjectionModule::class,
    NetworkModule::class, // provides ApiService
    AppModule::class, // provides context of application
    WorkerModule::class // <- add WorkerModule here
])
interface AppComponent: AndroidInjector<App> {
    @Component.Builder
    abstract class Builder: AndroidInjector.Builder<App>()
}

6. 添加自定义 WorkerFactory 以利用自 1.0.0-alpha09[=] 发布版本以来创建 worker 的能力26=]

DaggerAwareWorkerFactory.kt

class DaggerAwareWorkerFactory @Inject constructor(
    private val workerFactoryMap: Map<Class<out ListenableWorker>, @JvmSuppressWildcards Provider<IWorkerFactory<out ListenableWorker>>>
) : WorkerFactory() {
    override fun createWorker(
        appContext: Context,
        workerClassName: String,
        workerParameters: WorkerParameters
    ): ListenableWorker? {
        val entry = workerFactoryMap.entries.find { Class.forName(workerClassName).isAssignableFrom(it.key) }
        val factory = entry?.value
            ?: throw IllegalArgumentException("could not find worker: $workerClassName")
        return factory.get().create(workerParameters)
    }
}

7. 在应用程序 class 中,将 WorkerFactory 替换为我们自定义的:

App.kt

class App: DaggerApplication() {
    override fun onCreate() {
        super.onCreate()
        configureWorkManager()
    }

    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerAppComponent.builder().create(this)
    }

    @Inject lateinit var daggerAwareWorkerFactory: DaggerAwareWorkerFactory

    private fun configureWorkManager() {
        val config = Configuration.Builder()
            .setWorkerFactory(daggerAwareWorkerFactory)
            .build()
        WorkManager.initialize(this, config)
    }
}

8.不要忘记禁用默认工作管理器初始化

AndroidManifest.xml

<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:authorities="${applicationId}.workmanager-init"
    android:enabled="false"
    android:exported="false"
    tools:replace="android:authorities" />

就是这样。

每次你想给一个worker添加一个依赖,你把依赖放在相关的worker中class(比如这里的HelloWorker)。

每次要添加worker时,在worker中实现factoryclass,将worker的factory添加到WorkerModule中进行多绑定

更多细节,比如使用AssistedInject减少样板代码,请参考我开头提到的文章。