繁体   English   中英

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

[英]Dagger2: Unable to inject dependencies in WorkManager

所以从我读到的,Dagger 还不支持在 Worker 中注入。 但是正如人们所建议的那样,有一些解决方法。 我已经尝试通过多种方式按照在线示例进行操作,但没有一种对我有用。

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

例如,我尝试这样做:

@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 依赖项保持为空。

我试过做很多其他的事情,但没有奏效。 有没有办法做到这一点? 谢谢!!

概述

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

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

例子

假设您有一个名为DataClearingWorkerWorker子类,并且该类需要您的 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子类,它可以为你创建其中之一; 不仅创建一个,还要设置你的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初始化(自动发生)并手动初始化。

AndroidManifest.xml ,您可以像这样禁用它:

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

请务必将com.your.app.package替换为您的实际应用程序包。 上面的<provider位于您的<application标签内...所以它是您的ActivitiesServices等的兄弟...

在您的Application子类中(或其他地方,如果您愿意),您可以手动初始化WorkManager

@Inject
lateinit var workerFactory: WorkerFactory

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

    WorkManager.initialize(this, config)
}

2020/06 更新

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

有了 Hilt,您所要做的就是

  1. 将注释@HiltAndroidApp添加到您的应用程序类
  2. 在 Application 类的字段中注入开箱即用的HiltWorkerFactory
  3. 实现接口Configuration.Provider并返回步骤 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 ,我只是一步一步地发布我自己的实现(在 Kotlin 中)。

============

这个实现试图实现的是:

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

============

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

工人工厂.kt

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

2.添加一个简单的Worker 类,其中包含一个实现 IWorkerFactory的 Factory以及此 worker 的依赖项

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的多绑定添加一个WorkerKey

WorkerKey.kt

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

4.multi-binding worker添加Dagger模块(其实就是多绑定工厂)

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来构造组件类

应用组件.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. 1.0.0-alpha09 发布后添加自定义WorkerFactory,利用创建worker的能力

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.在 Application 类中,将 WorkerFactory 替换为我们自定义的:

应用程序

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类中(比如这里的HelloWorker)。

每次要添加worker时,在worker类中实现工厂,将worker的工厂添加到WorkerModule中进行多绑定。

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

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

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

  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 映射,找到相应的 worker 提供者并构造请求的 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)

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

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

  • 使用接受ApplicationContextWorkerParams的新Worker构造WorkerParams
  • 通过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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM