dagger2 多个参数
WorkManager is the latest solution by google which is very helpful in running background tasks. Under the hood it uses a combination of JobScheduler (API 23+) and BroadcastReceiver + AlarmManager (API 14–22). This basically creates a very powerful solution for scheduling or performing immediate background tasks along with constraints like Network, Disk Space etc. If you’re not familiar about the WorkManager, I suggest you do a WorkManager Codelab first.
WorkManager是Google提供的最新解决方案,对运行后台任务非常有帮助。 在后台,它结合使用JobScheduler(API 23+)和BroadcastReceiver + AlarmManager(API 14-22)。 这基本上为调度或执行即时后台任务以及诸如网络,磁盘空间等约束之类的任务提供了一个非常强大的解决方案。如果您不熟悉WorkManager,建议您首先进行WorkManager Codelab 。
Before we start, this article is for people who are familiar with Dagger 2 and have implemented Dagger 2 in their project.
在开始之前,本文适用于熟悉Dagger 2并已在其项目中实现Dagger 2的人员。
That being said, we not here to discuss WorkManager, but rather a specific problem; injecting custom parameters into the Worker class. Now why is this important?
话虽这么说,我们这里不是在讨论WorkManager,而是一个具体的问题。 将自定义参数注入Worker类 。 现在为什么这很重要?
Consider a Worker class (which needs to be defined in order to schedule a task):
考虑一个Worker类(需要定义它以便安排任务):
public class SampleWorker extends RxWorker {
public SampleWorker(
@NonNull Context context,
@NonNull WorkerParameters parameters) {
super(context, parameters);
}
public Single<Result> createWork() {
// Create a single observer which will hit a url and nothing else.
return Observable.range(0, 100)
.flatMap { download("https://www.google.com") }
.toList()
.map { Result.success() };
}
}
Here you can see, it accepts 2 parameters Context
and WorkerParameters
. The problem here is, you cannot instantiate the worker class directly. Don’t get alarmed by RxWorker. It extends Listenable worker. The methodology is the same for all types.
在这里,您可以看到它接受2个参数Context
和WorkerParameters
。 这里的问题是,您不能直接实例化worker类。 不要被RxWorker惊慌。 它扩展了可听的工作者。 所有类型的方法都相同。
If you remember, the way to schedule a worker is using the WorkManager class.
如果您还记得,安排工作人员的方法是使用WorkManager类。
public static final Constraints NETWORK_CONSTRAINT = new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build();
// Create a OneTimeWorkRequest using our worker; SampleWorker.class
OneTimeWorkRequest sampleWorkRequest = new OneTimeWorkRequest
.Builder(SampleWorker.class)
.setConstraints(NETWORK_CONSTRAINT)
.setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
.build();
// Enqueue
WorkManager.getInstance(applicationContext).enqueue(sampleWorkRequest);
As you can see, we don’t have any control over the object creation. Now the problem here is, what if you needed an instance of the database, or your Retrofit instance to call your backend API. How are you going to send those parameters?
如您所见,我们对对象的创建没有任何控制。 现在的问题是,如果您需要数据库实例或Retrofit实例来调用后端API,该怎么办。 您将如何发送这些参数?
The answer is Dagger.
答案是匕首。
向WorkerFactory打个招呼 (Say Hello To WorkerFactory)
What is a WorkerFactory?
什么是WorkerFactory?
A factory object that creates ListenableWorker instances. The factory is invoked every time a work runs
一个工厂对象,可创建ListenableWorker实例。 每次工作运行都会调用工厂
In other words, if your worker factory class is registered to WorkManager, then it’ll get called each time to create an instance of the worker class which we discussed above. That means, now you have control over the constructor parameters!
换句话说,如果您的工人工厂类已注册到WorkManager,则每次创建该工人类的实例时都会调用该实例,我们已在上面讨论过。 这意味着,现在您可以控制构造函数参数了!
I am here to guide you weave all the components together to get a hassle free worker class and dagger implementation
我在这里指导您将所有组件编织在一起,以获取无忧的工人阶级和匕首实现
分步实施 (Step By Step Implementation)
首先,我们将从创建一个批注开始: (First, we’ll start by creating an Annotation:)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@MapKey
public @interface WorkerKey {
Class<? extends RxWorker> value();
}
Why is this necessary?
为什么这是必要的?
Since we’ll be using Dagger and WorkerFactory, we need a way to give the factory the data to create instances of our worker(s)
由于我们将使用Dagger和WorkerFactory,因此我们需要一种方法为工厂提供数据以创建我们的worker实例
Here we create a Map of .class
vs instances of the workers we have in our code base using this annotation. We then provide
this map to our WorkerFactory
class to iterate and use the concrete instance from the map.The map will look something like this:Map<Class<? extends RxWorker>, Provider<RxWorker>> getWorkerMap();
在这里,我们使用此批注创建了一个.class
与我们在代码库中具有的工作程序实例的Map。 然后,我们将此地图provide
给WorkerFactory
类以迭代并使用该地图中的具体实例。该地图将如下所示: Map<Class<? extends RxWorker>, Provider<RxWorker>> getWorkerMap();
Map<Class<? extends RxWorker>, Provider<RxWorker>> getWorkerMap();
Provider
is of type java.inject
used by Dagger to get instances. Don’t worry about it too much. Just remember this piece of code.
Provider
的类型为Dagger用于获取实例的java.inject
。 不用太担心。 只要记住这段代码即可。
创建绑定模块-值的键 (Create a Binding Module — Key to the value)
Ok, now we need to use the annotation somewhere. If you remember, this sort of implementation is done when designing the framework for injecting ViewModels in our activities. Similarly here, we need to create a module to bind
the key to the value.
好的,现在我们需要在某个地方使用注释。 如果您还记得的话,这种实现是在设计用于在我们的活动中注入ViewModels的框架时完成的。 同样在这里,我们需要创建一个模块以bind
键bind
到值。
@Module
public abstract class WorkerBindingModule {
@Binds
@IntoMap
@WorkerKey(SampleWorker.class)
abstract RxWorker bindPushContentWorker(SampleWorker sampleWorkerInstance);
@Binds
@IntoMap
@WorkerKey(SampleWorker2.class)
abstract RxWorker bindContentTypesDumpWorker(SampleWorker2 sampleWorker2);
}
Simple enough? SampleWorker.class
on line 6 is the key, RxWorker
on line 7 is the value for the Map
. Dagger then tries to create instances of the value objects.
很简单? 第6行的SampleWorker.class
是键,第7行的RxWorker
是Map
的值。 然后,Dagger尝试创建值对象的实例。
将自定义参数添加到所需的worker类中并注释@Inject
(Add the custom parameter to the worker class we want and annotate @Inject
)
Coming back to our worker class, say we want to inject Retrofit
in our worker class. We add the parameter and annotate the constructor with @Inject
like dagger wants us to do.
回到我们的工人阶级,说我们想在我们的工人阶级中注入Retrofit
。 我们添加参数并使用@Inject
注释构造函数,就像dagger想要我们做的那样。
Now, when dagger tries to create this map and fill the values with the instances, it’ll invoke our custom constructor of the worker.
现在,当dagger尝试创建此映射并用实例填充值时,它将调用我们的worker的自定义构造函数。
public class SampleWorker extends RxWorker {
// Our custom parameter
private final Retrofit retrofit;
@Inject
public SampleWorker(
@NonNull Context context,
@NonNull WorkerParameters parameters,
Retrofit retrofit) {
super(context, parameters);
this.retrofit = retrofit;
}
public Single<Result> createWork() {
// Create a single observer which will hit a url and nothing else.
return Observable.range(0, 100)
.flatMap { download("https://www.google.com") }
.toList()
.map { Result.success() };
}
}
But there’s an important step missing here, how will it inject Retrofit? Hold that thought! We’ll come back to this later.
但是这里缺少重要的一步,它将如何注入Retrofit? 保持那个想法! 我们稍后会再讨论。
So, what do we have so far? We have:
那么,到目前为止我们有什么? 我们有:
- Annotation defining a key for Class vs Instance map 注释定义了类与实例映射的键
- Binder module which specifies the map dagger needs to create 绑定器模块,用于指定需要创建的地图匕首
- Our custom worker class which will actually perform our background task 我们的自定义工作者类实际上将执行我们的后台任务
We need to tell dagger to attach this module to its graph so that it can start injecting, right? You may want to hit build
and see whats going on till now.
我们需要告诉dagger将此模块附加到其图形上,以便它可以开始注入,对吗? 您可能想要进行build
,看看到目前为止发生了什么。
The thing is, it throws an error complaining that it cannot inject Retrofit. Specifically Retrofit cannot be injected without @Provides annotation
.
事实是,它抛出一个错误,抱怨它无法注入Retrofit。 具体来说, Retrofit cannot be injected without @Provides annotation
。
Now for dagger to inject Retrofit, it needs the instance of Retrofit from somewhere right. I am going out on a limb here and assuming you have a Repository module with you which has all the @Provides
for Retrofit
, OkHttp
and works. If you don't, best you create one.
现在,为了使Dagger注入Retrofit,它需要从正确的地方实例化Retrofit。 在这里,我的肢体,走出去,假设你有你库模块,拥有所有@Provides
的Retrofit
, OkHttp
和作品。 如果不这样做,最好创建一个。
Remember the WorkerBinding module above? Include the Repository Module here:
还记得上面的WorkerBinding模块吗? 在此处包括存储库模块:
@Module(includes = RepositoryModule.class)
public abstract class WorkerBindingModule { ...
@Module(includes = RepositoryModule.class)
public abstract class WorkerBindingModule { ...
The only thing left is adding this module to the parent component so that dagger recognises it. Go ahead and do that.
剩下的唯一事情就是将此模块添加到父组件中,以便匕首识别它。 继续做吧。
在AppComponent中添加WorkerBinding模块 (Add the WorkerBinding module in AppComponent)
Sounds simple right? So go ahead and add this module in the list of modules in the app component and hit build
.
听起来很简单吧? 因此,继续并将此模块添加到app组件的模块列表中,然后点击build
。
@Component(modules = {
WorkerBindingModule.class, // To bind our worker classes to the factory
AppModule.class, //. Which provides context
ActivityBinderModule.class, // TO bind our activities
FragmentBinderModule.class, // To bind our fragments
AndroidInjectionModule.class
})
@Singleton
public interface AppComponent extends AndroidInjector<WonderquilApplication> {
@Component.Builder
interface Builder {
@BindsInstance
Builder application(Application application);
AppComponent build();
}
}
At this point, your build will fail and dagger will spew out a whole lot of errors. Most prominently, it’ll say cannot bind Retrofit in 2 instances
and it'll print the entire graph.
在这一点上,您的构建将失败,并且匕首会喷出很多错误。 最突出的是,它将说cannot bind Retrofit in 2 instances
并且它将打印整个图形。
So what does this mean?
那么这是什么意思?
You will not get this error if you haven’t used repository pattern and not included retrofit anywhere else.
如果您未使用存储库模式并且未在其他任何地方包括改造,则不会收到此错误。
This error is basically thrown because:
基本上会引发此错误,因为:
We have created a binding (the map which we talked earlier) for the ViewModels also! Which in turn include Retrofit.
我们还为ViewModels创建了一个绑定(我们之前讨论过的地图)! 依次包括翻新。
We have included WorkerBindingModule in the top-level i.e., the app component level.
我们在顶层(即应用程序组件级别)中包含了WorkerBindingModule。
The graph has become something like this:
该图已变成这样:
![Rough hand sketch trying to show the dagger graph. The left side is the graph we are making, the right one is the existing](https://i-blog.csdnimg.cn/blog_migrate/446362ba4188ac0523062a9dd9df7cac.png)
As shown, on one side, Retrofit
is getting injected in the level SubComponent, but here, we try to directly inject it. Dagger doesn't like this. More so, it'll not be able to figure which class needs which particular instance of Retrofit
.
如图所示,一方面, Retrofit
被注入到SubComponent级,但是在这里,我们尝试直接注入它。 匕首不喜欢这样。 更重要的是,它将无法确定哪个类需要哪个特定的Retrofit
实例。
Ok, so you probably guessed what we need to do now. Remove that line where we have included the worker binding module in the AppComponent and hit build.
好的,所以您可能猜到了我们现在需要做什么。 删除我们在AppComponent中包含worker绑定模块的那一行,然后单击build。
I just wanted to show this error so that you understand what is going on here.
我只是想显示此错误,以便您了解此处发生的情况。
Now dagger will not complain. But now, our module is out of the graph, so it will not do anything i.e., there will no injection happening. We still need to integrate it into the graph.
现在匕首不会抱怨。 但是现在,我们的模块不在图表中,因此它不会做任何事情,即不会发生注入。 我们仍然需要将其集成到图中。
输入子组件-创建辅助子组件 (Enter SubComponent — Create a worker SubComponent)
@Subcomponent(modules = WorkerBindingModule.class)
public interface WorkManagerFactorySubComponent {
Map<Class<? extends RxWorker>, Provider<RxWorker>> getWorkerMap();
@Subcomponent.Builder
interface Builder {
@BindsInstance
Builder parameters(WorkerParameters parameters);
WorkManagerFactorySubComponent build();
}
}
If you notice, we didn’t include Context
here. Why, because of the same problem, it's injected on a different level. Context is provided at the AppComponent level and not in sub component level. Don’t worry, you’ll get the context. Wait on.In order to create this Map
, we have included our WorkerBindingModule
here as well. This provides dagger with the necessary requirements for creating this map.So when dagger starts to populate the map (as you will see where), it’ll call our Worker constructor with the extra Retrofit parameter.
如果您注意到,我们不在此处包括Context
。 为什么由于相同的问题而将其注入到不同的级别。 上下文是在AppComponent级别提供的,而不是在子组件级别提供的。 不用担心,您将获得上下文。 等待。为了创建此Map
,我们WorkerBindingModule
此处包括了WorkerBindingModule
。 这为dagger提供了创建此地图的必要条件。因此,当dagger开始填充地图时(如您所见),它将使用额外的Retrofit参数调用Worker构造函数。
创建WorkerFactory类 (Create the WorkerFactory Class)
Comes the part where we guide android how to create our worker class using our custom factory.
来到这一部分,我们指导android如何使用我们的自定义工厂创建worker类。
public class WorkManagerWorkFactory extends WorkerFactory {
private final WorkManagerFactorySubComponent.Builder subComponentBuilder;
@Inject
public WorkManagerWorkFactory(WorkManagerFactorySubComponent.Builder workerFactorySubComponent) {
this.subComponentBuilder = workerFactorySubComponent;
}
@Nullable
@Override
public RxWorker createWorker(@NonNull Context appContext, @NonNull String workerClassName, @NonNull WorkerParameters workerParameters) {
WorkManagerFactorySubComponent workManagerFactorySubComponent = subComponentBuilder.parameters(workerParameters).build();
return createWorker(workerClassName, workManagerFactorySubComponent.getWorkerMap());
}
@SneakyThrows
private RxWorker createWorker(String workerClassName, Map<Class<? extends RxWorker>, Provider<RxWorker>> workerMap) {
// Get the worker class definition
Class<? extends RxWorker> workerClass = Class.forName(workerClassName).asSubclass(RxWorker.class);
// Get the worker class instance
Provider<RxWorker> provider = workerMap.get(workerClass);
if (provider == null) {
// Fetch the binding
for (Map.Entry<Class<? extends RxWorker>, Provider<RxWorker>> entry : workerMap.entrySet()) {
if (workerClass.isAssignableFrom(entry.getKey())) {
provider = entry.getValue();
break;
}
}
}
if (provider == null)
throw new IllegalArgumentException("Missing binding for " + workerClassName + ". Consider adding an entry in " + WorkerBindingModule.class.getSimpleName());
return provider.get();
}
}
Yes, it looks a little complicated but trust me it’s quite simple.We had dagger inject the subcomponent and we called it’s build
method to create the concrete WorkManagerFactorySubComponent
by passing work parameters on line 13.
是的,看起来有点复杂,但是请相信我,这很简单。我们已经用匕首注入了子组件,我们称之为通过在第13行传递工作参数来创建具体的WorkManagerFactorySubComponent
的build
方法。
This prompted dagger to populate that map I specified earlier. Since it already has everything it needs, it’s able to inject the custom Retrofit parameter as well. This code is basically looping through that map and giving the WorkManager the correct Worker instance.
这提示匕首填充了我之前指定的地图。 由于它已经具有所需的一切,因此也可以注入自定义Retrofit参数。 这段代码基本上遍历了该映射,并为WorkManager提供了正确的Worker实例。
Remember I talked about getting the context? The thing is, we don’t need it here. Because we are going to initialise our worker manager at the application level i.e., AppComponent level; which already has the applicationContext.
还记得我说过获取上下文吗? 问题是,我们在这里不需要它。 因为我们要在应用程序级别(即AppComponent级别)初始化我们的工作者管理器; 已经有applicationContext了。
在应用程序类中初始化WorkManager (Initialise WorkManager in Application Class)
By default, WorkManager initialises itself using it own mechanism of ContentProviders and factory. But since we have our own factory, we need to tell the WorkManager explicitly not to go the default route.
默认情况下,WorkManager使用其自己的ContentProviders和factory机制进行初始化。 但是,由于我们拥有自己的工厂,因此需要明确告知WorkManager不要使用默认路线。
Our Application class becomes:
我们的应用程序类变为:
public class SampleApplication extends DaggerApplication {
@Inject
WorkManagerWorkFactory customWorkerFactory; // Field injection of our custom worker factory class
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
AppComponent component = DaggerAppComponent.builder().application(this).build();
component.inject(this);
return component;
}
@Override
public void onCreate() {
super.onCreate();
// INit our worker manager
WorkManager.initialize(this, new Configuration.Builder().setWorkerFactory(customWorkerFactory).build());
}
}
And in the manifest also:
并且在清单中还:
<application
.
.
. <!-- Activities and various other things -->
.
.
.
<provider
android:authorities="${applicationId}.workmanager-init"
android:name="androidx.work.impl.WorkManagerInitializer"
android:exported="false"
tools:node="remove"/>
</application>
This is to prevent WorkerManager to use its default ContentProvider to create the workers.
这是为了防止WorkerManager使用其默认的ContentProvider创建工作程序。
That’s it! Hit build and see the magic unfold. You will find a lot of ways to do this but I feel this is the simplest and least complicated.
而已! 点击构建并查看魔术的展开。 您会发现很多方法可以做到这一点,但我认为这是最简单,最不复杂的方法。
Now any new worker class you create, add it to the WorkerBindingModule
and you're good to go.
现在,您创建的任何新辅助类都将其添加到WorkerBindingModule
,您就可以开始使用了。
I used this technique in my own application and it works flawlessly. Try it out!
我在自己的应用程序中使用了该技术,并且可以完美地工作。 试试看!
dagger2 多个参数