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

var*_*nkr 15 android dagger-2 android-workmanager

所以从我读到的,Dagger还没有支持注入Worker.但是人们建议有一些解决方法.我尝试过多种方式在线跟踪示例,但它们都不适用于我.

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

例如,我尝试这样做:

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

    void inject(MyWorker myWorker);
}

@Module
public class Module{

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

}
Run Code Online (Sandbox Code Playgroud)

在我的工人

@Inject
MyRepository myRepo;

public MyWorker() {
    DaggerAppComponent.builder().build().inject(this);
}
Run Code Online (Sandbox Code Playgroud)

但随后执行从未到达工人.如果我删除构造函数,myRepo依赖项将保持为null.

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

Cra*_*ell 22

概观

您需要查看从此开始提供的WorkerFactory1.0.0-alpha09.

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

假设你有一个Worker被调用的子类DataClearingWorker,并且这个类需要Foo来自你的Dagger图.

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

    lateinit var foo: Foo

    override fun doWork(): Result {
        foo.doStuff()
        return Result.SUCCESS
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您不能直接实例化其中一个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
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,您需要创建一个DaggerWorkerFactory可以访问的权限Foo.你可以用普通的 Dagger方式做到这一点.

@Provides
@Singleton
fun workerFactory(foo: Foo): WorkerFactory {
    return DaggerWorkerFactory(foo)
}
Run Code Online (Sandbox Code Playgroud)

禁用默认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" />
Run Code Online (Sandbox Code Playgroud)

请务必将com.your.app.package替换为您的实际应用程序包.在<provider上述块进入里面<application标签..所以这是你的兄弟姐妹Activities,Services等等...

在您的Application子类中(或者您喜欢的其他位置),您可以手动初始化WorkManager.

@Inject
lateinit var workerFactory: WorkerFactory

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

    WorkManager.initialize(this, config)
}
Run Code Online (Sandbox Code Playgroud)

  • 当可以自动完成时,在“WorkerFactory”实现中手动注入依赖项是相当弱的解决方案。如果你有一百个不同的“Wokrer”,那将是一个真正的痛苦。 (2认同)

dio*_*usk 8

1.0.0-beta01版本开始,这是使用WorkerFactory进行Dagger注入的实现。

这个概念来自本文https : //medium.com/@nlg.tuan.kiet/bb9f474bde37,我只是一步一步地发布自己的实现(在Kotlin中)。

===========

此实现要实现的目的是:

每次您要向工作程序添加依赖项时,都将其放入相关的工作程序类中

===========

1.为所有工人的工厂添加界面

IWorkerFactory.kt

interface IWorkerFactory<T : ListenableWorker> {
    fun create(params: WorkerParameters): T
}
Run Code Online (Sandbox Code Playgroud)

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())
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

3.为Dagger的多重绑定添加一个WorkerKey

WorkerKey.kt

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

4.多绑定工作人员添加一个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
}
Run Code Online (Sandbox Code Playgroud)

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>()
}
Run Code Online (Sandbox Code Playgroud)

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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" />
Run Code Online (Sandbox Code Playgroud)

而已。

每次您要向工作程序添加依赖项时,都将其放入相关的工作程序类中(例如此处的HelloWorker)。

每次您要添加一个工作器时,都在worker类中实现工厂,并将该工作器的工厂添加到WorkerModule中以进行多重绑定。

有关更多详细信息,例如使用AssistedInject减少样板代码,请参阅我在开始时提到的文章。

  • 为什么 Android 需要如此复杂?!ViewModelFactory 非常简单,但我们不要使用它,而是使用这个完全复杂的系统 (4认同)

art*_*art 6

我使用Dagger2 多重绑定来解决这个问题。

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

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

    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
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 创建自定义注释,指定工作线程多绑定映射条目的键。

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

    @Module
    interface HardWorkerModule {
    
        @Binds
        @IntoMap
        @WorkerKey(HardWorker::class)
        fun bindHardWorker(worker: HardWorker): Worker
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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) }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  6. WorkManager使用自定义工人工厂手动初始化(每个进程必须仅完成一次)。不要忘记在清单中禁用自动初始化。

显现:

    <provider
        android:name="androidx.work.impl.WorkManagerInitializer"
        android:authorities="${applicationId}.workmanager-init"
        android:exported="false"
        tools:node="remove" />
Run Code Online (Sandbox Code Playgroud)

应用onCreate

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

    val request = OneTimeWorkRequest.Builder(workerClass).build(HardWorker::class.java)
    WorkManager.getInstance().enqueue(request)
    
    Run Code Online (Sandbox Code Playgroud)

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