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

Posted

技术标签:

【中文标题】Dagger2:无法在 WorkManager 中注入依赖项【英文标题】:Dagger2: Unable to inject dependencies in WorkManager 【发布时间】:2019-02-25 07:10:49 【问题描述】:

所以根据我的阅读,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 依赖项仍然为空。

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

【问题讨论】:

WorkManager 团队现在有一个使用 Dagger 的示例 - medium.com/androiddevelopers/… 【参考方案1】:

概述

您需要查看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
    

最后,您需要创建一个DaggerWorkerFactory,它可以访问Foo。你可以用普通匕首的方式来做到这一点。

@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 替换为您的实际应用程序包。上面的 &lt;provider 块位于 inside 您的 &lt;application 标记中。所以它是您的 ActivitiesServices 等的兄弟...

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

@Inject
lateinit var workerFactory: WorkerFactory

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

    WorkManager.initialize(this, config)

【讨论】:

这个 DaggerWorkerFactory 是否必须像 DaggerWorkerFactory(val depA: DepA, val depB: DepB, val depC: DepC) 一样依赖每个worker的所有依赖项? 是的,我相信是的。我想不出一种避免它知道所有依赖项的好方法。 @CraigRussel 我无法让它工作。我在 Application 子类的 workerFactory 字段上得到了 UninitializedPropertyAccessException。它应该在哪里初始化?这个答案对 1.0.0-alpha12 仍然有效吗? @sphrak 听起来像 Dagger 还没有设置。你在配置 Dagger 之前是否在使用工人工厂? 当可以自动完成时,在WorkerFactory实现中手动注入依赖项是相当弱的解决方案。如果你有一百个不同的Wokrers,那将是一个真正的痛苦。【参考方案2】:

2020/06 更新

Hilt 和 Hilt for Jetpack 让事情变得容易得多。

有了 Hilt,你所要做的就是

    将注释@HiltAndroidApp 添加到您的应用程序类 在应用程序类的字段中注入开箱即用的HiltWorkerFactory 实现接口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.为所有工人的工厂添加一个接口

IWorkerFactory.kt

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

2. 添加一个简单的 Worker 类,其中包含一个 Factory,该类实现了 IWorkerFactory,并且还具有该 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.多绑定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来构造组件类

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 发布版本以来创建工人的能力

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 替换为我们自定义的:

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

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

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

【讨论】:

为什么Android需要如此复杂?! ViewModelFactory 要简单得多,但我们不要使用它,而是使用这个极其复杂的系统 非常感谢您提供的简化解决方案 :) 我一直在关注此博客,但在某个时候卡住了,而您是我的救星。 是否有一个示例项目可以正常工作?上述技术对我没有任何帮助。 你在这个话题上节省了我的第二天。昨天,我在用 AssistedInject 关注这篇文章,但它总是有一个我无法解决的错误,所以我放弃它并尝试你的解决方案。再次感谢你。干得好! 这里有更多关于如何在WorkerManager中使用Hilt proandroiddev.com/hilt-migration-guide-54c48ca18353【参考方案3】:

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

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

    使用@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
        
    
    

    创建自定义注释,指定工作人员多绑定地图条目的键。

    @MustBeDocumented
    @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
    @Retention(AnnotationRetention.RUNTIME)
    @MapKey
    annotation class WorkerKey(val value: KClass<out Worker>)
    

    定义工作者绑定。

    @Module
    interface HardWorkerModule 
    
        @Binds
        @IntoMap
        @WorkerKey(HardWorker::class)
        fun bindHardWorker(worker: HardWorker): Worker
    
    

    定义中间组件及其构建器。组件必须具有从依赖图中获取worker映射并在其模块中包含worker绑定模块的方法。此外,组件必须声明为其父组件的子组件,并且父组件必须具有获取子组件构建器的方法。

    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
    
    

    实现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) 
    
    

    使用自定义工作工厂手动初始化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)

    使用工人

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

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

【讨论】:

我不知道为什么,但我的应用程序编译并运行,但是来自 AppModule 的对象没有被注入到构造函数中。如果我尝试注入字段,它将为空。我可以将它与典型的 AppComponent 一起用作 ParentComponent 吗?如何连接点? @MichałZiobro 是的,您可以将 AppComponent 用作 ParentComponent。你不会忘记用@Inject 注释来注释你的工人的构造函数吗?您也可以调试提供调查问题的工作人员,只需在DIWorkerFactory 类的provider.get() 行放置断点,然后进入。【参考方案4】:

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

使用新的Worker 构造函数,它接受ApplicationContextWorkerParams。 通过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);

【讨论】:

我有点困惑。首先,变量 clazz 无法解析,因为它没有在任何地方声明。我假设我必须在那里使用workerKlass。其次,我需要将配置创建部分放在哪里?如果我没记错的话,workmanager 的初始化也将在排队工作请求时完成,即 WorkManager.initialize(context, config).enqueue(something);抱歉问题太多了。 我希望这个答案可以更完整。这显然不像注入 ViewModelFactory 那样工作。你如何注入你的依赖项?什么是“workerClassName”以及如何使用它?你在工厂中注入并在Dagger中声明工厂吗?还是注入Worker,在工厂里做点什么?

以上是关于Dagger2:无法在 WorkManager 中注入依赖项的主要内容,如果未能解决你的问题,请参考以下文章

无法在 WorkManager 中设置自定义工人工厂

无法使用 Dagger2 将对象注入 ViewModel 类

Android Annotations生成类,但dagger2 @Module类无法访问这些类

kotlin + Dagger2 :没有@Provides-annotated 方法就无法提供

Dagger2:如果没有 @Provides-annotated 方法,就无法提供 ViewModel

Dagger2+Retrofit 使用 Subcomponent 更改 URL:如果没有 @Provides- 或 @Produces-annotated 方法,则无法提供