2

依赖注入利器 - Dagger ‡

 3 years ago
source link: https://blog.hanschen.site/2016/12/18/dagger2/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

依赖注入利器 - Dagger ‡

发表于

2016-12-18 更新于 2021-04-13 分类于 Java

阅读次数: 33 Valine: 0
本文字数: 22k 阅读时长 ≈ 20 分钟

在开发过程中,为了实现解耦,我们经常使用依赖注入,常见的依赖注入方式有:

  • 构造方法注入:在构造方法中把依赖作为参数传递进去
  • setter方法注入:添加setter方法,把依赖传递进去
  • 接口注入:把注入方法抽到一个接口中,然后实现该接口,把依赖传递进去

下面用一个小栗子来说明三种方式的用法:

public class PersonService implements DependencyInjecter {

private PersonDao personDao;

// 构造方法注入
public PersonService(PersonDao personDao) {
this.personDao = personDao;
}

// setter方法注入
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}

// 接口注入:实现DependencyInjecter接口
@Override
public void injectPersonDao(PersonDao personDao) {
this.personDao = personDao;
}

... ...
}

我们来看下使用一般的依赖注入方法时,代码会是怎么样的:

public class MainActivity extends AppCompatActivity {

private PersonService mService;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 创建PersonService的依赖:personDao
PersonDao personDao = new PersonDaoImpl();
// 通过构造方法注入依赖
mService = new PersonService(personDao);
}
}

看起来还好是吧?但现实情况下,依赖情况往往是比较复杂的,比如很可能我们的依赖关系如下图:
2019-9-2-11-38-41.png

PersonDaoImpl依赖类A,类A依赖B,B依赖C和D…在这种情况下,我们就要写出下面这样的代码了:

public class MainActivity extends AppCompatActivity {

private PersonService mService;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 创建依赖D
D d = new D();
// 创建依赖C
C c = new C();
// 创建依赖B
B b = new B(c, d);
// 创建依赖A
A a = new A(b);
// 创建PersonService的依赖:personDao
PersonDao personDao = new PersonDaoImpl(a);
// 通过构造方法注入依赖
mService = new PersonService(personDao);
}
}

MainActivity只是想使用PersonService而已,却不得不关注PersonService的依赖是什么、PersonDaoImpl依赖的依赖是什么,需要把整个依赖关系搞清楚才能使用PersonService。而且还有一个不好的地方,一旦依赖关系变更了,比如A不再依赖B了,那么就得修改所有创建A的地方。那么,有没有更好的方式呢?Dagger就是为此而生的,让我们看看使用Dagger后,MainActivity会变成什么模样:

public class MainActivity extends AppCompatActivity {

@Inject
PersonService mService;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// Dagger注入,读者现在可先不关注里面做了什么操作
DaggerPersonServiceComponent.create().inject(MainActivity.this);

// 注意,mService已经是非空了,可以正常使用
mService.update(1, "HansChen");
......
}
}

之前创建A、B、C、D、PersonDaoImpl等依赖的代码全不见了,只需要调用一个注入语句就全搞定了。调用了注入语句之后,mService就可以正常使用了,是不是挺方便呢?至于这句注入语句具体干了什么,读者现在可以先不管,后面会有详细说明,这里只是做一个使用演示而已。

我们大概猜想一下,在MainActivity使用PersonService需要做哪些?

  1. 分析生成依赖关系图,如PersonService–>PersonDaoImpl–>A–>B–>C&D
  2. 根据依赖关系图获取相关依赖,比如依次创建D、C、B、A、PersonDaoImpl、PersonService的实例
  3. 把生成的PersonService实例传递给MainActivity的mService成员变量

其实Dagger做的也就是上面这些事情了,接下来就让我们真正开始学习Dagger吧

声明需要注入的对象

首先我们应该用javax.inject.Inject去注解需要被自动注入的对象,@Inject是Java标准的依赖注入(JSR-330)注解。比如下面栗子中,需要注入的对象就是MainActivity的mService。这里有个要注意的地方,被@Inject注解的变量不能用private修饰

public class MainActivity extends AppCompatActivity {

// 注意,不能被private修饰
@Inject
PersonService mService;
......
}

如何实例化出依赖?

在执行依赖注入的时候,Dagger会查找@Inject注解的成员变量,并尝试获取该类的实例,Dagger最直接的方式就是直接new出相应的对象了。实例化对象的时候,会调用对象的构造方法,但假如有多个构造方法,具体用哪个构造方法来实例化对象?Dagger肯定是不会帮我们“擅自做主”的,用哪个构造方法来实例化对象应该是由我们做主的,所以我们需要给相应的构造方法添加@Inject注解
当Dagger需要实例化该对象的时候,会调用@Inject注解的构造方法来实例化对象:

public class PersonService implements DependencyInjecter {

private PersonDao personDao;

// 用@Inject注解,相当于告诉Dagger需要实例化PersonService的时候,请调用这个构造方法
@Inject
public PersonService(PersonDao personDao) {
this.personDao = personDao;
}

......
}

聪明的你应该发现了,调用PersonService的构造方法需要传入PersonDao实例,所以要实例化PersonService,必须先要实例化PersonDao,Dagger会帮我们自动分析出这个依赖关系,并把它添加到依赖关系图里面!Dagger会尝试先去实例化一个PersonDao,如果PersonDao又依赖于另外一个对象A,那么就先尝试去实例化A……以此类推,是不是很像递归?当所有依赖都被实例化出来之后,我们的PersonService当然也被构造出来了。

问题又来了,如果PersonDao是一个接口呢?Dagger怎么知道这个接口应该怎么实现?答案是不知道的,那么Dagger怎么实例化出一个接口出来?这个就是Module存在的意义之一了。关于Module的讲解我们会在后面详细说明,我们现在只要知道,Module里面会定义一些方法,这些方法会返回我们的依赖,就像:

@Module
public class PersonServiceModule {

/**
* 提供PersonDao接口实例
*/
@Provides
PersonDao providePersonDao(A a) {
return new PersonDaoImpl(a);
}
}

Dagger根据需求获取一个实例的时候,并不总是通过new出来的,它会优先查找Module
中是否有返回相应实例的方法,如果有,就调用Module的方法来获取实例。

比如你用@Inject注解了一个成员变量,Dagger会查找Module中是否有用@Provides注解的,返回该类实例的方法,有的话就会调用provide方法来获得实例,然后注入,如果没有的话Dagger就会尝试new出一个实例。就像我们现在这个栗子,PersonService依赖于PersonDao接口,Dagger不能直接为我们new出一个接口,但我们可以提供一个Module,在Module中定义一个返回PersonDao接口实例的方法,这样,Dagger就可以解决实例化PersonDao的问题了。

我们再梳理一下流程,如果我们用@Inject注解了一个成员变量,并调用注入代码之后,Dagger会这样处理:

  1. 查找Module中是否有用@Provides注解的,返回该类实例的方法
  2. 如果有,就调用那个provide方法来获得实例,然后注入
  3. 如果没有,就尝试调用相应的类中被@Inject注解的构造方法new出一个实例,然后注入
  4. 如果没有一个构造方法被@Inject注解,Dagger会因不能满足依赖而出错

所以假如一个变量被@Inject注解,要么在Module中提供provide方法获取实例,要么该类提供一个被@Inject注解的构造方法,否则Dagger会出错

Module的使用

一般而言,Dagger会获取所有依赖的实例,比如当需要一个TestBean的时候,会通过new TestBean()创建实例并注入到类中。但是,以下情况会就不好处理了:

  1. 需要生成的是一个接口,而Dagger不能直接实例化接口
  2. 不能在第三方库的类中添加注解
  3. 可配置的对象必须是配置的

为了解决以上问题,我们需要定义一个被@Module注解的类,在里面定义用@Provides注解的方法。用该方法返回所需的实例。

@Module
public class PersonServiceModule {

@Provides
D provideD() {
return new D();
}

@Provides
C provideC() {
return new C();
}

@Provides
B provideB(C c, D d) {
return new B(c, d);
}

@Provides
A provideA(B b) {
return new A(b);
}

/**
* 提供PersonDao实例
*/
@Provides
PersonDao providePersonDao(A a) {
return new PersonDaoImpl(a);
}
}

就像providePersonDao返回了PersonDao接口实例,Dagger虽然不能直接实例化出PersonDao接口,但却可以调用Module的providePersonDao方法来获得一个实例。providePersonDao方法需要传入A的实例,那么这里也构成了一个依赖关系图。Dagger会先获取A的实例,然后把实例传递给providePersonDao方法。

Component的使用

到目前为止,我们虽然知道了:

  • Dagger怎么获取实例:
    • 从Module的provide方法中获取
    • 通过@Inject注解的构造方法new出新的实例
  • Dagger会推导provide方法和构造方法的参数,形成依赖图,并“满足”我们依赖图的需求,获取依赖的实例

看样子需要注入的依赖可以获取了,但是不是总觉得还有点“零碎”,整个流程还没连贯起来?比如,Module既然是一个类,生成依赖图的时候,怎么知道跟哪个Module挂钩?即使最后生成了需要的实例,注入的“目的地”是哪里?怎么才能把它注入到“目的地”?残缺的这部分功能,正是Component提供的,Component起到了一个桥梁的作用,贯通Module和注入目标。我们来看看最开始那个例子,我们是怎么进行依赖注入的:

public class MainActivity extends AppCompatActivity {

@Inject
PersonService mService;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

PersonServiceComponent component = DaggerPersonServiceComponent.builder()
.personServiceModule(new PersonServiceModule())
.build();
// 注入,所有@Inject注解的成员变量都会同时注入
component.inject(MainActivity.this);

// 通过component获取实例,注意,这里只是演示用法,其实mService在component.inject的时候已经完成了注入
mService = component.getPersonService();
}
}

这个DaggerPersonServiceComponent是什么鬼?DaggerPersonServiceComponent其实是Dagger为我们自动生成的类,它实现了一个Component接口(这个接口是需要我们自己写的),我们来看下它实现的接口长什么样子:

/**
* 指定PersonServiceModule,当需要获取某实例的时候,会查找PersonServiceModule中是否有返回相应类型的方法,有的话就通过该方法获得实例
*
* @author HansChen
*/
@Component(modules = PersonServiceModule.class)
public interface PersonServiceComponent {

/**
* 查找activity中被@Inject注解的成员变量,并尝试获取相应的实例,把实例赋给activity的成员变量
* 注意函数格式:返回值为空、带有一个参数
*/
void inject(MainActivity activity);

/**
* Dagger会尝试从Module中获取PersonService实例,如果Module中不能获取对应实例,则通过PersonService的构造方法new出一个实例
* 注意函数格式:参数为空,返回值非空
*/
PersonService getPersonService();
}

这个接口被Component注解修饰,它里面可以定义3种类型的方法:

  • 返回值为空,有一个参数:查找参数中被@Inject注解的成员变量,并尝试获取相应的实例(通过Module的provide方法或@Inject注解的构造方法new出新的实例),把实例赋给参数的成员变量
  • 返回值非空,参数为空:获取相应实例并返回
  • 返回值是Component,参数是Moduld,通过该方法可以创建SubComponent实例

既然获取实例的时候,有可能用到Module,那么就必须为这个Component指定使用的Module是什么。具体做法就是在@Component注解中指定modules。
定义好Component之后,Dagger会自动帮我们生成实现类,这就是Dagger强大的地方!生成的类名格式是:Dagger+Component名。
Component提供了2种方法,一个是注入式方法,一个是获取实例方法。具体用什么方法,就看个人需求了。一个Component其实也对应了一个依赖图,因为Component使用哪个Module是确定不变的,依赖关系无非也就是跟Module和类的定义有关。一旦这些都确定下来了,在这个Component范围内,依赖关系也就被确定下来了。额外再说一点,在Dagger1中,Component的功能是由ObjectGraph实现的,Component是用来代替它的。

Component定义好之后,build一下工程,Dagger就会自动为我们生成实现类了,就可以使用自动生成的实现类来进行依赖注入了。到现在为止,我们已经通过Dagger完成了依赖注入。可能看起来比正常方法麻烦得多,但是Dagger框架可以让依赖的注入和配置独立于组件之外,它帮助你专注在那些重要的功能类上。通过声明依赖关系和指定规则构建整个应用程序。

熟悉完Dagger基本的使用之后,接下来我们来讲解一些稍微高级一点的用法:

Dagger的进阶使用

Components之间的关系

在Dagger中,Component之间可以有两种关系:Subcomponents和Component dependencies。他们有什么作用呢?比如在我们应用中,经常会有一些依赖我们在各个界面都使用得到,比如操作数据库、比如网络请求。假设我们有个ServerApi的接口,在页面A、B、C都使用到了,那么我们要在页面A、B、C的Component里面都能获取到ServerApi的实例,但显然,获取ServerApi实例的方法都是一样的,我们不想写重复的代码。于是我们可定义一个ApplicationComponent,在里面返回ServerApi实例,通过Component之间的关系便可以共享ApplicationComponent提供的依赖图。

下面通过Android中的一个小栗子来说明Subcomponents和Component dependencies如何使用

dependencies

先说明下各个模块之间的关系
首先,我们定义一个ApplicationComponent,它定义了一个方法,通过它来获得ServerApi实例。ApplicationComponent还关联了ApplicationModule,这个Module是ServerApi实例的提供者,注意,这个Moduld还可以返回Context实例
2019-9-2-11-41-18.png

@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

ServerApi getServerApi();
}
@Module
public class ApplicationModule {

private final Context mAppContext;

ApplicationModule(Context context) {
mAppContext = context.getApplicationContext();
}

@Provides
Context provideAppContext() {
return mAppContext;
}

@Provides
ServerApi provideServerApi(Context context) {
return new ServerApiImpl(context);
}
}
public class DemoApplication extends Application {

private ApplicationComponent mAppComponent;

@Override
public void onCreate() {
super.onCreate();
mAppComponent = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build();
}

public ApplicationComponent getAppComponent() {
return mAppComponent;
}
}

MainActivity使用MVP模式,在MainPresenter里面需要传入一个ServerApi对象
2019-9-2-11-43-13.png

// 注意,这里有个dependencies声明
@Component(dependencies = ApplicationComponent.class, modules = MainPresenterModule.class)
public interface MainPresenterComponent {

MainPresenter getMainPresenter();
}
@Module
public class MainPresenterModule {

private MainView mMainView;

public MainPresenterModule(MainView mainView) {
this.mMainView = mainView;
}

@Provides
MainView provideMainView() {
return mMainView;
}
}
public class MainPresenter {

private MainView mMainView;
private ServerApi mServerApi;

@Inject
public MainPresenter(MainView mainView, ServerApi serverApi) {
this.mMainView = mainView;
this.mServerApi = serverApi;
}
}

先抛开dependencies,我们分析这个这个依赖树是怎么样的
2019-9-2-11-43-43.png
Component中getMainPresenter的目的很简单,就是返回MainPresenter,而MainPresenter又依赖MainView和ServerApi,MainView还好说,在MainPresenterModule中有provide方法,但是ServerApi呢?就像上面说的那样,如果我们在这个Moduld中也添加相应的provide方法,那真是太麻烦了(当然,这样做完全是可以实现的),所以我们依赖了ApplicationComponent,通过dependencies,在被依赖的Component暴露的对象,在子Component中是可见的。这个是什么意思呢?意思有两个:

  1. 被依赖Component接口暴露的对象,可以添加到依赖者的依赖图中
  2. Component接口没有暴露的对象,依赖者是不可见的

对于第一点应该比较好理解,就像这个栗子,MainPresenterComponent生成MainPresenter需要ServerApi,而ApplicationComponent中有接口暴露了ServerApi,所以MainPresenterComponent可以获得ServerApi
对于第二点,假设MainPresenter还需要传入一个Context对象,我们注意到,ApplicationModule是可以提供Context的,那MainPresenterComponent能不能通过ApplicationComponent获取Context实例?答案是不行的,因为ApplicationComponent没有暴露这个对象。想要获取Context,除非ApplicationComponent中再添加一个getContext的方法。

他们之间的关系可以用下图描述:
2019-9-2-11-44-12.png

Subcomponents

Subcomponents 实现方法一:

  • 先定义子 Component,使 用@Subcomponent 标注(不可同时再使用 @Component)
  • 父 Component 中定义获得子 Component 的方法

让我们对上面的栗子改造改造:
去除MainPresenterComponent的Component注解,改为Subcomponent:

@Subcomponent(modules = MainPresenterModule.class)
public interface MainPresenterComponent {

void inject(MainActivity activity);

MainPresenter getMainPresenter();
}

在ApplicationComponent中新增plus方法(名字可随意取),返回值为MainPresenterComponent,参数为MainPresenterModule:

@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

MainPresenterComponent plus(MainPresenterModule module);
}

这样,就构建了一个ApplicationComponent的子图:MainPresenterComponent。子图和dependencies的区别就是,子图可以范围父图所有的依赖,也就是说,子图需要的依赖,不再需要在父Component中暴露任何对象,可以直接通过父图的Moduld提供!他们的关系变为了:
2019-9-2-11-44-34.png

这里需要注意的是,以上代码直接在父 Component 返回子 Component 的形式,要求子 Component 依赖的 Module 必须包含一个无参构造函数,用以自动实例化。如果 Module 需要传递参数,则需要使用 @Subcomponent.builder 的方式,实现方法二实现步骤如下:

  • 在子 Component,定义一个接口或抽象类(通常定义为 Builder),使用 @Subcomponent.Builder 标注
    • 编写返回值为 Builder,方法的参数为需要传入参数的 Module
    • 编写返回值为当前子 Component的 无参方法
  • 父 Component 中定义获得子 Component.Builder 的方法

代码如下:

@Module
public class TestModule {
public TestModule(String test) {
}

@Provides
AuthManager provideAuthManager() {
return AuthManager.getInstance();
}
}

@Subcomponent(modules = {TestModule.class})
public interface TestComponent {

AuthManager getAuthManager();

@Subcomponent.Builder
interface Builder {

Builder createBuilder(TestModule module);

TestComponent build();
}
}

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
...
TestComponent.Builder testComponentBuilder();
}


// 使用
TestComponent testComponent = mApplicationComponent.testComponentBuilder().createBuilder(new TestModule("test")).build();

Binds注解

在Dagger2中,一般都是使用@provide方法注入接口。在Android 中,一般我们会这样做,创建一个接口 Presenter 命名 为 HomePresenter

public interface HomePresenter {
Observable<List<User>> loadUsers()
}

然后创建一个这个接口的实例:HomePresenterImp

public class HomePresenterImp implements HomePresenter {
public HomePresenterImp(){
}
@Override
public Observable<List<User>> loadUsers(){
//Return user list observable
}
}

然后在 Module 中,提供实例化的 provide 方法:

@Module
public class HomeModule {
@Provides
public HomePresenter providesHomePresenter(){
return new HomePresenterImp();
}
}

但是,如果我们需要添加一个依赖到 presenter 叫 UserService,那就意味着,我们也要在 module 中添加一个 provide 方法提供这个 UserService,然后在 HomePresenterImp 类中加入一个 UserService 参数的构造方法。
有没有觉得这种方法很麻烦呢?我们还可以用 @Binds 注解,如:

@Module
public abstract class HomeModule {
// 变为 abstract 方法, 同时 Module 也必须声明为 abstract, 传入的参数必须为返回参数的实现类
// 当需要 HomePresenter 时,dagger 会自动实例化 HomePresenterImp 并返回
@Binds
public abstract HomePresenter bindHomePresenter(HomePresenterImp homePresenterImp);
}

除了方便,使用 @Binds 注解还可以让 dagger2 生成的代码效率更高。但是需要注意的是,由于 Module 变为抽象类,Module 不能再包含非 static 的带 @Provides 注解的方法。而且这时候,依赖此 Module 的 Component 也不需要传入此 Module 实例了(也实例化不了,因为它是抽象的)。相当于此 Module 仅仅作为描述依赖关系的一个类

Scopes

Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域。@Singleton是被Dagger预先定义的作用域注解。

  • 没有指定作用域的@Provides方法将会在每次注入的时候都创建新的对象
  • 一个没有scope的component不可以依赖一个有scope的组件component
  • 子组件和父组件的scope不能相同
  • Module中provide方法的scope需要与Component的scope一致

我们通常的ApplicationComponent都会使用Singleton注解,也就会是说我们如果自定义component必须有自己的scope。读者到这里,可能还不能理解Scopes的作用,我们先来看下默认提供的Singlton到底有什么作用,然后再讨论Scopes的意义:

Singlton

Singletons是java提供的一个scope,我们来看看Singletons能做什么事情。
为@Provides注释的方法或可注入的类添加添加注解@Singlton,构建的这个对象图表将使用唯一的对象实例,比如我们有个ServerApi
方法一:用@Singleton注解类:

@Singleton
public class ServerApi {

@Inject
public ServerApi() {
}

public boolean login(String username, String password) {
return "HansChen".equals(username) && "123456".equals(password);
}
}

方法二:用@Singleton注解Module的provide方法:

@Module
public class ApplicationModule {

@Singleton
@Provides
ServerApi provideServerApi() {
return new ServerApi();
}
}

然后我们有个Component:

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

ServerApi getServerApi();
}

然后执行依赖注入:

public class MainActivity extends AppCompatActivity {

@Inject
ServerApi mService;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ApplicationComponent component = DaggerApplicationComponent.create();
Log.d("Hans", component.getServerApi().toString());
Log.d("Hans", component.getServerApi().toString());
Log.d("Hans", component.getServerApi().toString());
}
}

使用了以上两种方法的任意一种,我们都会发现,通过component.getServerApi()获得的实例都是同一个实例。不过要注意一点的是,如果类用@Singleton注解了,但Module中又存在一个provide方法是提供该类实例的,但provide方法没有用@Singleton注解,那么Component中获取该实例就不是单例的,因为会优先查找Module的方法。
这个单例是相对于同一个Component而言的,不同的Component获取到的实例将会是不一样的。

自定义Scope

既然一个没有scope的component不可以依赖一个有scope的组件component,那么我们必然需要自定义scope来去注解自己的Component了,定义方法如下:

@Documented
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface FragmentScoped {
}

定义出来的FragmentScoped在使用上和Singleton是一样的,那它和Singleton除了是不一样的注解之外,还有什么不一样呢?答案是没有!我们自定义的scope和Singleton并没有任何不一样,不会因为Singleton是java自带的注解就会有什么区别。

那么,这个scope的设定是为了什么呢?

scope的作用

scope除了修饰provide方法可以让我们获得在同一个Component实例范围内的单例之外,主要的作用就是对Component和Moduld的分层管理以及依赖逻辑的可读性。
这里借用一个网络上的图片说明:
2019-9-2-11-44-58.png

ApplicationComponent一般会用singleton注解,相对的,它的Module中provide方法也只能用singleton注解。UserComponent是用UserSCope能直接使用ApplicationModule吗?不能!因为他俩的scope不一致,这就是这个设定带来的好处,防止不同层级的组件混乱。另外,因为有了scope的存在,各种组件的作用和生命周期也变得可读起来了

Lazy注入

有时可能会需要延迟获取一个实例。对任何绑定的 T,可以构建一个 Lazy 来延迟实例化直至第一次调用 Lazy 的 get() 方法。注入之后,第一次get的时会实例化出 T,之后的调用都会获取相同的实例。

public class MainActivity extends AppCompatActivity implements MainView {

// 懒加载
@Inject
Lazy<MainPresenter> mPresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

MainPresenterComponent component = DaggerMainPresenterComponent.builder()
.mainPresenterModule(new MainPresenterModule(this))
.applicationComponent(((DemoApplication) getApplication()).getAppComponent())
.build();
component.inject(this);
Log.d("Hans", mPresenter.get().toString()); // 实例化MainPresenter
Log.d("Hans", mPresenter.get().toString()); // 跟上次获取的实例是同一个实例
}
}

Provider注入

跟Lazy注入不一样的是,有时候我们希望每次调用get的时候,获取到的实例都是不一样的,这时候可以用Provider注入

public class MainActivity extends AppCompatActivity implements MainView {

// Provider
@Inject
Provider<MainPresenter> mPresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

MainPresenterComponent component = DaggerMainPresenterComponent.builder()
.mainPresenterModule(new MainPresenterModule(this))
.applicationComponent(((DemoApplication) getApplication()).getAppComponent())
.build();
component.inject(this);
Log.d("Hans", mPresenter.get().toString()); // 实例化MainPresenter
Log.d("Hans", mPresenter.get().toString()); // 获取新的MainPresenter实例
}
}

Qualifiers注入

到目前为止,我们的demo里,Moduld的provide返回的对象都是不一样的,但是下面这种情况就不好处理了:

@Module
public class ApplicationModule {

......

// 返回ServerApi实例
@Provides
ServerApi provideServerApiA(Context context) {
return new ServerApiImplA(context);
}

// 返回ServerApi实例
@Provides
ServerApi provideServerApiB(Context context) {
return new ServerApiImplB(context);
}
}

provideServerApiA和provideServerApiB返回的都是ServerApi,Dagger是无法判断用哪个provide方法的。这时候就需要添加Qualifiers了:

@Module
public class ApplicationModule {

......

@Provides
@Named("ServerApiImplA")
ServerApi provideServerApiA(Context context) {
return new ServerApiImplA(context);
}

@Provides
@Named("ServerApiImplB")
ServerApi provideServerApiB(Context context) {
return new ServerApiImplB(context);
}
}

通过这样一个限定,就能区分出2个方法的区别了,当然,在使用过程中,也同样要指明你用哪个name的实例,Dagger会根据你的name来选取对应的provide方法:

public class MainPresenter {

private MainView mMainView;
private ServerApi mServerApi;

@Inject
public MainPresenter(MainView mainView, @Named("ServerApiImplA") ServerApi serverApi) {
this.mMainView = mainView;
this.mServerApi = serverApi;
}
}

除了用Named注解,你也可以创建你自己的限定注解:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface YourQualifier {
String value() default "";
}

编译时验证

Dagger 包含了一个注解处理器(annotation processor)来验证模块和注入。这个过程很严格而且会抛出错误,当有非法绑定或绑定不成功时。下面这个例子缺少了 Executor:

@Module
class DripCoffeeModule {
@Provides Heater provideHeater(Executor executor) {
return new CpuHeater(executor);
}
}

当编译时,javac 会拒绝绑定缺少的部分:

[ERROR] COMPILATION ERROR :
[ERROR] error: java.util.concurrent.Executor cannot be provided without an @Provides-annotated method.

可以通过给方法 Executor 添加@Provides注解来解决这个问题,或者标记这个模块是不完整的。不完整的模块允许缺少依赖关系

@Module(complete = false)
class DripCoffeeModule {
@Provides Heater provideHeater(Executor executor) {
return new CpuHeater(executor);
}
}

第一次接触用Dagger框架写的代码时候,如果不了解各种注解作用的时候,那真会有一脸懵逼的感觉,而且单看文章,其实还是很抽象,建议大家用Dagger写个小demo玩玩,很快就上手了,这里提供几个使用Dagger的栗子,希望可以帮助大家上手Dagger

chenhang wechat

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK