だから私が読んだことから、ダガーはまだWorkerでの注入をサポートしていません。しかし、人々が示唆するように、いくつかの回避策があります。私はオンラインの例に従って多くの方法でそれをやろうとしましたが、それらのどれも私のために働きません。
Workerクラスに何もインジェクトしようとしない場合、コードは正常に動作しますが、DAOとサービスにアクセスする必要があるため、必要なことを実行できません。これらの依存関係で@Injectを使用する場合、依存関係はnullであるか、ワーカーが起動しません。つまり、デバッガーがワーカークラスに入りさえしません。
たとえば、私はこれを試しました:
@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依存関係はnullのままです。
私は他の多くのことを試しましたが、どれもうまくいきませんでした。これを行う方法さえありますか?ありがとう!!
WorkerFactory を参照する必要があります。1.0.0-alpha09
以降。
以前の回避策は、デフォルトの0-argコンストラクタを使用してWorker
を作成できることに依存していましたが、1.0.0-alpha10
これはもはやオプションではありません。
Worker
というDataClearingWorker
サブクラスがあり、このクラスには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
インスタンスの1つを直接インスタンス化することはできません。そのため、それらのいずれかを作成できる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
を作成する必要があります。これは、normalDaggerの方法で実行できます。
@Provides
@Singleton
fun workerFactory(foo: Foo): WorkerFactory {
return DaggerWorkerFactory(foo)
}
また、デフォルトの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
上記のブロックはinsideyour <application
タグ..それはあなたのActivities
、Services
などの兄弟です...
Application
サブクラス(または必要に応じて他の場所)で、WorkManager
を手動で初期化できます。
@Inject
lateinit var workerFactory: WorkerFactory
private fun configureWorkManager() {
val config = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
WorkManager.initialize(this, config)
}
バージョン1.0.0-beta01の時点で、WorkerFactoryによるDaggerインジェクションの実装がここにあります。
コンセプトはこの記事からです: https://medium.com/@nlg.tuan.kiet/bb9f474bde37 ステップごとの実装(in Kotlin)。
===========
達成しようとしているこの実装は次のとおりです。
ワーカーに依存関係を追加するたびに、関連するワーカークラスに依存関係を配置します
===========
1.すべての労働者の工場のインターフェースを追加
IWorkerFactory.kt
interface IWorkerFactory<T : ListenableWorker> {
fun create(params: WorkerParameters): T
}
2.単純なWorkerクラスとファクトリを追加whichimplements IWorkerFactoryおよびこのワーカーの依存関係も含む
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.ダガー用にWorkerKeyを追加multi-binding
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を使用してコンポーネントクラスを構築します
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" />
それでおしまい。
ワーカーに依存関係を追加するたびに、関連するワーカークラスに依存関係を配置します(ここではHelloWorkerなど)。
ワーカーを追加するたびに、ワーカークラスにファクトリを実装し、マルチバインディングのためにワーカーのファクトリをWorkerModuleに追加します。
AssistedInjectを使用して定型コードを減らすなどの詳細については、冒頭で述べた記事を参照してください。
Dagger2 Multibindings を使用してこの問題を解決します。
同様のアプローチがViewModel
オブジェクトの注入に使用されます(よく説明されています here )。ビューモデルの場合との重要な違いは、Context
コンストラクターにWorkerParameters
およびWorker
引数が存在することです。これらの引数をワーカーコンストラクターに提供するには、中間の短剣コンポーネントを使用する必要があります。
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
}
}
ワーカーのマルチバインドマップエントリのキーを指定するカスタムアノテーションを作成します。
@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
}
Context
およびWorkerParameters
オブジェクトを提供するビルダーとモジュールと共に中間コンポーネントを定義します。コンポーネントには、依存関係グラフからワーカーマップを取得し、モジュール間にワーカーバインディングモジュールを含めるためのメソッドが必要です。また、コンポーネントは親コンポーネントのサブコンポーネントとして宣言する必要があり、親コンポーネントには子コンポーネントのビルダーを取得するメソッドが必要です。
@Module
class ArgumentsModule(private val appContext: Context,
private val workerParameters: WorkerParameters) {
@Provides
fun provideAppContext() = appContext
@Provides
fun provideWorkerParameters() = workerParameters
}
typealias WorkerMap = MutableMap<Class<out Worker>, Provider<Worker>>
@Subcomponent(modules = [
ArgumentsModule::class,
HardWorkerModule::class])
interface WorkerFactoryComponent {
fun workers(): WorkerMap
@Subcomponent.Builder
interface Builder {
fun argumentsModule(module: ArgumentsModule): Builder
fun build(): WorkerFactoryComponent
}
}
// some module of the parent component
@Module(subcomponents = [WorkerFactoryComponent::class
//, ...
])
class ParentComponentModule {
// ...
}
// parent component
@ParentComponentScope
@Component(modules = [ParentComponentModule::class
//, ...
])
interface ParentComponent {
// ...
fun workerFactoryComponent(): WorkerFactoryComponent.Builder
}
WorkerFactory
を実装します。中間コンポーネントを作成し、ワーカーマップを取得し、対応するワーカープロバイダーを見つけて、要求されたワーカーを構築します。
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()
.argumentsModule(ArgumentsModule(appContext, workerParameters))
.build()
.run { createWorker(workerClassName, workers()) }
}
カスタムワーカーファクトリでWorkManager
を手動で初期化します(プロセスごとに1回だけ実行する必要があります)。マニフェストで自動初期化を無効にすることを忘れないでください。
マニフェスト:
<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)
WorkManager
機能の詳細については、こちらをご覧ください talk .
WorkManagerでalpha09
新しい WorkerFactory があり、これを使用してWorker
を希望どおりに初期化できます。
Worker
とApplicationContext
を受け取る新しいWorkerParams
コンストラクターを使用します。WorkerFactory
を介してConfiguration
の実装を登録します。configuration
を作成し、新しく作成したWorkerFactory
を登録します。WorkManager
を初期化します(代わりにContentProvider
を初期化するWorkManager
を削除します)。以下を行う必要があります。
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);