依赖注入利器 - Dagger ‡

概述

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

  • 构造方法注入:在构造方法中把依赖作为参数传递进去
  • 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<T> 来延迟实例化直至第一次调用 Lazy<T> 的 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容