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
开始,这不再是一个选项。
示例
假设您有一个名为 DataClearingWorker
的 Worker
子类,并且该类需要 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 替换为您的实际应用程序包。上面的 <provider
块位于 inside 您的 <application
标记中。所以它是您的 Activities
、Services
等的兄弟...
在您的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
实现中手动注入依赖项是相当弱的解决方案。如果你有一百个不同的Wokrer
s,那将是一个真正的痛苦。【参考方案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的多重绑定
添加一个WorkerKeyWorkerKey.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
构造函数中存在Context
和WorkerParameters
参数。为了向 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
构造函数,它接受ApplicationContext
和WorkerParams
。
通过Configuration
注册WorkerFactory
的实现。
创建configuration
并注册新创建的WorkerFactory
。
使用此配置初始化WorkManager
(同时删除代表您初始化WorkManager
的ContentProvider
)。
您需要执行以下操作:
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 中注入依赖项的主要内容,如果未能解决你的问题,请参考以下文章
无法使用 Dagger2 将对象注入 ViewModel 类
Android Annotations生成类,但dagger2 @Module类无法访问这些类
kotlin + Dagger2 :没有@Provides-annotated 方法就无法提供
Dagger2:如果没有 @Provides-annotated 方法,就无法提供 ViewModel
Dagger2+Retrofit 使用 Subcomponent 更改 URL:如果没有 @Provides- 或 @Produces-annotated 方法,则无法提供