Dagger 2 基本用法

1. Dagger2是什么?  

Dagger2是一个依赖注入(Dependency Injection)框架。

什么又是依赖注入呢?

借别人的话来说,就是“目标类中所依赖的其他类的初始化过程,不是通过在目标类中编码的方式完成,而是通过其他手段把已经初始化好的实例自动注入到目标类中”。

再换种方式来说,就是把类实例的初始化过程都挪至一个地方统一管理,具体需要哪个实例时,就从这个地方取出(注入到目标类中)。

他的主要作用,就是对象的管理,其目的是为了降低程序耦合。

 

 

2.基本的概念

我们反向推到,当们使用

@Inject
A a

想要获取a对象的示例的时候,Dagger2 会先去找,当前Activity或者Fragment所连接的桥梁,例如上图中,连接的只有一个桥梁,实际上可以有多个,这个桥梁,会去寻找他所依赖的模块,如图中,依赖了模块A,和模块B,然后在模块中,会去寻找@Providers注解,去寻找A的实例化对象。

角色介绍

在讲用法前,先对几个重要角色进行了解。

实例需求端:

一个类中,它包含了实例成员,在使用这些实例前,需要对它们进行初始化,但前面已经说了初始化的过程挪至其他地方。所以这个类的需求是,已经完成初始化的实例对象。
暂且把这样的类称为“实例需求端”。

实例供应端:

进行实例初始化的地方,并对外供应所需的实例。

Dagger2中,供应端有两种方式可以供应实例:(后面会介绍)
第一种:使用Module
第二种:使用@Inject标注构造函数

桥梁:

实例供应端并不知道它要供应的实例交给谁。
这个时候就需要通过桥梁,将实例需求端与实例供应端联系在一起。

Dagger2中,Component就负责扮演桥梁的角色。

3.如何使用Dagger2

(1)引入依赖库

Dagger2官网
(https://github.com/google/dagger)

    compile 'com.google.dagger:dagger:2.11'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.11'

(2)创建Moudule

//第一步 添加@Module 注解
@Module
public class MainModule {
}

(3)创建具体的示例

//第一步 添加@Module 注解
@Module
public class MainModule {
    //第二步 使用Provider 注解 实例化对象
    @Provides
    A providerA() {
        return new A();
    }
}

(4)创建一个Component

//第一步 添加@Component
//第二步 添加module
@Component(modules = {MainModule.class})
public interface MainComponent {
    //第三步  写一个方法 绑定Activity /Fragment
    void inject(MainActivity activity);
}

(5)Rebuild Project

这一步是必须的

然后AS 会自动帮我们生成一个

开头都是以Dagger开始的

(6)将Component与Activity/Fragment绑定关系

package com.allens.daggerdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.allens.daggerdemo.Bean.A;
import com.allens.daggerdemo.component.DaggerMainConponent;
import javax.inject.Inject;

public class MainActivity extends AppCompatActivity {
    /***
     * 第二步  使用Inject 注解,获取到A 对象的实例
     */
    @Inject
    A a;

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

        /***
         * 第一步 添加依赖关系
         */
        //第一种方式
        DaggerMainConponent.create().inject(this);

        //第二种方式
        DaggerMainConponent.builder().build().inject(this);

        /***
         * 第三步  调用A 对象的方法
         */
        a.eat();
    }
}

4. 其他用法:

  1)构造方法需要其他参数时候

@Module
public class MainModule {

    /***
     * 构造方法需要其他参数时候
     *
     * @return
     */
    @Provides
    B providerB() {
        return new B();
    }

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

(2) 模块之间的依赖关系

模块与模块之间的联系,

@Module (includes = {BModule.class})// includes 引入)
public class AModule {
    @Provides
    A providerA() {
        return new A();
    }
}

这样的话,Dagger会现在A moudule 中寻找对象,如果没找到,会去找module B 中是否有被Inject注解的对象,如果还是没有,那么GG,抛出异常
一个Component 应用多个 module

@Component(modules = {AModule.class,BModule.class})
public interface MainComponent {
    void inject(MainActivity activity);
}

dependencies 依赖其他Component

@Component(modules = {MainModule.class}, dependencies = AppConponent.class)
public interface MainConponent {
    void inject(MainActivity activity);
}

(3)@Named注解使用

相当于有个表示,虽然大家都是同一个对象,但是实例化对象不同就不如

A a1 = new A();
A a2 = new A();

// a1  a2 能一样嘛

Module中 使用@Named注解

@Module
public class MainModule {

    private MainActivity activity;

    public MainModule(MainActivity activity) {
        this.activity = activity;
    }


    @Named("dev")
    @Provides
    MainApi provideMainApiDev(MainChildApi mainChildApi, String url) {
        return new MainApi(mainChildApi, activity,"dev");
    }

    @Named("release")
    @Provides
    MainApi provideMainApiRelease(MainChildApi mainChildApi, String url) {
        return new MainApi(mainChildApi, activity,"release");
    }


}

在Activity/Fragment中使用

public class MainActivity extends AppCompatActivity {

    @Named("dev")
    @Inject
    MainApi apiDev;

    @Named("release")
    @Inject
    MainApi apiRelease;

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

        DaggerMainComponent.builder()
                .mainModule(new MainModule(this))
                .mainChildModule(new MainChildModule())
                .build()
                .inject(this);
        apiDev.eat();
        apiRelease.eat();
        Log.i("TAG","apiDev--->" + apiDev);
        Log.i("TAG","apiRelease--->" + apiRelease);
    }

}

(4)@Singleton注解
单利模式,是不是超级方便,你想然哪个对象单利化,直接在他的Provider上添加@Singleton 就行了
例如

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

注意:  第一个坑!!!
如果 moudule所依赖的Comonent 中有被单利的对象,那么Conponnent也必须是单利的

@Singleton
@Component(modules = {MainModule.class})
public interface MainConponent {
}

然后 在Activity中使用,直接打印a1 a2 的地址,

    @Inject
    A a2;
    @Inject
    A a1;

可以看到Log

12-30 01:32:58.420 3987-3987/com.allens.daggerdemo E/TAG: A1---->com.allens.daggerdemo.Bean.A@11fa1ba
12-30 01:32:58.420 3987-3987/com.allens.daggerdemo E/TAG: A1---->com.allens.daggerdemo.Bean.A@11fa1ba

不相信的小伙伴可以吧@Singleton去掉试试
现在我们完成了单利,然后做了一个事情,就是点击某个按钮,跳转到一个新的Activiry,两边都引用同样一个A 对象,打印A 的地址,
说一下,一个Conponent 可以被对个Activity/Fragment 引用,如

@Singleton
@Component(modules = {MainModule.class})
public interface MainConponent {
    void inject(MainActivity activity);
    void inject(TestAct activity);
}

上面与两个Activity, MainActivity 和  TestAct  ,都引用相同的 对象,答应地址看看

12-30 00:48:17.477 2788-2788/com.allens.daggerdemo E/TAG: A1---->com.allens.daggerdemo.Bean.A@11fa1ba
12-30 00:48:17.517 2788-2788/com.allens.daggerdemo E/TAG: A2---->com.allens.daggerdemo.Bean.A@4f81861

竟然不同,说好的单利呢

注意: 第二个坑,单利对象只能在同一个Activity中有效。不同的Activity 持有的对象不同

5)Subcomponent

这个是系统提供的一个Component,当使用Subcomponent,那么默认会依赖Component
例如

@Subcomponent(modules = TestSubModule.class)
public interface TestSubComponent {
    void inject(MainActivity activity);
}

@Component(modules = {MainModule.class})
public interface MainConponent {
    TestSubComponent add(TestSubModule module);
}

在TestSubComponent中 我void inject(MainActivity activity);,便是这个桥梁,我是要注入到MainActivity,但是dagger 并不会给我生成一个Dagger开头的DaggerTestSubComponent 这个类,如果我想使用TestSubModule.class里面提供的对象,依然还是使用DaggerMainConponent例如

   DaggerMainConponent
                .builder()
                .mainModule(new MainModule())
                .build()
                .add(new TestSubModule())
                .inject(this);

可以看到这里有一个add的方法,真是我在MainConponent添加的TestSubComponent add(TestSubModule module);

(7)lazy 和 Provider

假如你并不希望在调用inject()方法后,就对@Inject标注的实例进行初始化注入,而是希望在用到该实例的时候再进行初始化,那么我们就可以使用Lazy<T>和Provider<T>来实现。

 

public class UploadActivity extends AppCompatActivity{
    @Inject
    Lazy<UploadPresenter> mPresenter1;
    @Inject
    Provider<UploadPresenter> mPresenter2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //注入实例
        DaggerUploadActivityComponent.builder()
            .build()
            .inject(this);
    }

    public void xxx(){
         //调用Lazy的get()方法后才开始初始化Presenter并得到该实例
         //并且后面每次调用get()方法得到的实例都将是同一个。
         mPresenter1.get().xxx();

         //调用Provider的get()方法后才开始初始化Presenter并得到该实例
         //并且后面每次调用get()方法,都会重新调用供应端的供应方法来获取新实例。
         mPresenter2.get().xxx();
    }
}

注意: 如果使用了前面介绍的作用域注解@Scope控制了实例的唯一性,那么即使多次调用Provider的get()方法,得到的依然是同一个实例

其中Lazy(懒加载)的作用好比component初始化了一个present对象,然后放到一个池子里,需要的时候就get它,所以你每次get的时候拿到的对象都是同一个;并且当你第一次去get时,它才会去初始化这个实例.

provider(强制加载)的作用:
1:同上当你第一次去get时,它才会去初始化这个实例
2:后面当你去get这个实例时,是否为同一个,取决于他Module里实现的方式

另外可以使用Provider实现强制加载,每次调用get都会调用Module的Provides方法一次,和懒加载模式正好相反。

8. 限定符注解 @Qualifier

@Qualifier主要是用于解决,因实例供应端存在多个返回类型相同的供应方法而引起歧义的问题。
下面举个例子,现在页面需有两个Dialog,一个用于登录,一个用于注册。

8.1 使用@Named注解区分

Dagger2默认提供了一个@Named注解,从代码可以看出属于@Qualifier的一种实现。

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

在供应端对两个Dialog进行构造。

@Module
public class TestActivityModule {
    private Context mContext;

    public TestActivityModule(Context context){
          mContext = context;
    }

    @Provides
    Context context(){
          return mContext;
    }
    
    @Named("login")
    @Provides
    Dialog loginDialog(Context context){
         Dialog dialog = new Dialog(context);
         dialog.setTitle("登录提示");
         ....
         return dialog;
    }

    @Named("register")
    @Provides
    Dialog registerDialog(Context context){
         Dialog dialog = new Dialog(context);
         dialog.setTitle("注册提示");
         ....
         return dialog;
    }
}
  • 可以看到,在实例供应端中,需在提供Dialog实例的方法上面加@Named注解以区分。如果不加则会报错,因为Dagger2不知道要使用哪个方法来获取Dialog实例。

 

@Component(modules = TestActivityModule.class)
public interface TestActivityComponent {
    void inject(TestActivity testActivity);
}

 

public class TestActivity extends AppCompatActivity{
    @Named("login")
    @Inject
    Dialog mDialogLogin;
    
    @Named("register")
    @Inject
    Dialog mDialogRegister;      

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //注入实例
        DaggerTestActivityComponent.builder()
            .testActivityModule(new TestActivityModule(this))
            .build()
            .inject(this);
    }    
}
  • 在实例需求端中,同样要使用@Named注解标注要注入的实例。让Dagger2知道,
    @Named("login")标注的mDialogLogin,需要通过@Named("login")标注的供应方法来获取。
    @Named("register")标注的mDialogRegister,需要通过@Named("register")标注的供应方法来获取。

8.2 使用自定义@Qualifier区分

使用@Named注解的话,需要加入字符串来区分,这样比较麻烦也容易出错。所以我们可以使用自定义的限定符注解。

 

@Qualifier
public @interface DialogLogin {
}

 

@Qualifier
public @interface DialogRegister {
}

然后把前面涉及的@Named("login")换成@DialogLogin,@Named("register")换成@DialogRegister即可~

9. 作用域注解 @Scope

@Scope的作用是使同一个Component中供应的实例保持唯一。

9.1 使用作用域注解实现局部单例

举例说明:

 

public class UploadActivity extends AppCompatActivity{
    @Inject
    UploadPresenter mPresenter1;
    @Inject
    UploadPresenter mPresenter2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //注入实例
        DaggerUploadActivityComponent.builder()
            .build()
            .inject(this);
    }
}
  • 如果不使用作用域注解,则代码中的mPresenter1,mPresenter2将会是两个不一样的实例,可通过打印内存地址查看。
  • 而如下使用作用域注解后,则两者将会是同一个实例,可通过打印内存地址查看。

步骤1 自定义@Scope注解

@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScope {}

步骤2 桥梁Component添加作用域注解

@ActivityScope 
@Component(modules = UploadActivityModule.class)
public interface UploadActivityComponent {
    void inject(UploadActivity uploadActivity);
}

步骤3 供应端中提供实例的方法添加作用域注解

@Module
public class UploadActivityModule {
    @ActivityScope
    @Provides
    UploadPresenter uploadPresenter() {
        return new UploadPresenter();
    }
}

如果是使用1.3.2方式提供实例,则在类上方添加作用域注解

@ActivityScope
public class UploadPresenter{
    @Inject
    public UploadPresenter() {
    }
}

经过@Scope处理后,UploadActivity中的UploadPresenter实例将保持唯一。

9.2 使用作用域注解实现全局单例

全局单例,相信大家就很熟悉了,就是平时用饿汉懒汉等等写的那种单例模式。
前面已经说过@Scope作用是使同一个Component中供应的实例保持唯一。
也就是说,如果我在另一个Activity中再创建了一个新的Component,那么它所提供的UploadPresenter实例也将是新的。这和我们理解的全局单例并不一样。
所以,要想实现全局单例,那就要确保获取实例的Component一直都是同一个
如何实现呢?
答案是创建一个Component用于提供全局单例的实例(创建过程和4.1基本一样),然后在Application中对该Component进行初始化,以后要获取单例时,都统一通过它来获取

全局性的实例供应端

 

@Module
public class AppModule {
    private Application mApplication;

    public AppModule (Application application){
          mApplication = application;
    }

    @Singleton
    @Provides
    Application application(){
          return mApplication;
    }

    @Singleton
    @Provides
    ActivityStackManager activityStackManager() {
        return new ActivityStackManager();
    }
}

全局性的桥梁

@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
    ActivityStackManager activityStackManager();
    Application application();
}

Application中初始化

 

public class MyApplication extends Application {

    public static AppComponent mAppComponent;

    @Override
    public void onCreate() {
        super.onCreate();
       
        mAppComponent= DaggerAppComponent.builder()
              .appModule(new AppModule(this))
              .build();
    }
}

每次都通过Application中的AppComponent获取某实例,即可保证全局单例

 

public class UploadActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
   
        MyApplication.mAppComponent.activityStackManager().pushOneActivity(this);     
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        MyApplication.mAppComponent.activityStackManager().popOneActivity(this);   
    }
}
  • 使用了Dagger2默认提供的作用域注解@Singleton,通过源码可以发现它的实现其实和前面的@ActivityScope是一样的。
  • 所以真正实现全局单例的不是并@Singleton,而是使用每次获取实例都通过同一个Component。
  • 但由于它的字面意思为单例,所以我们通常把它应用在全局性的桥梁和实例供应端中。
  • 全局性的Component中没有加入inject方法来自动注入(当然你也可以这么做,但全局性的比较少这么做),而是加入了activityStackManager()方法,供外部调用来获取实例。

10.@bind

@Binds与@Provides

相信大家经常会使用@Provides来在Module里面提供需要注入对象的构造, 但从来没有用过@Binds.

如果我们需要注入一个接口的实现,我们常常会这么做:

@Provides
public XXInterface providesXX(XXImp imp) {
    return imp;
}
其实这样的代码可以通过@Binds简化为

@Binds
public abstract XXInterface bindXX(XXImp imp)

 

在Dagger2中,一般都是使用@provide方法注入接口。
在我们使用MVP模式搭建Android app的时候,一般我们会这样做,创建一个接口presenter命名为HomePresenter。

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

然后创建一个这个接口的实例,叫做HomePresenterImp,

 

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

但是请注意,在HomePresenterImp这个类中,我并没有使用@Inject在构造方法上。所以,我们要在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 {

  @Binds
  public abstract HomePresenter bindHomePresenter(HomePresenterImp homePresenterImp);
}

这个就是告诉dagger,这个HomePresenter是用的HomePresenterImp类实现的。当然,你会注意到这个class是抽象类。这就意味着我们可以添加抽象方法。
那么现在。只要在HomePresenterImp类中的构造方法加上@Inject注解,这样,我们就不需要添加依赖参数来提供provide方法了。
我们只要简单的在构造方法中使用@Inject;

 

@PerActivity
public class HomePresenterImp implements HomePresenter {
  private UserService userService;
  @Inject
  public HomePresenterImp(UserService userService){
   this.userService = userService;
  }
  @Override
  public Observable<List<User>> loadUsers(){
    return userService.getUsers();
  }
}

如果你有提供实例类的方法只调用构造函数注入接口。在dagger中使用@Binds注解可以代替原有的样板模式。
英文好的就看英文吧,英文翻译过来,有些地方还是有点不能很好的解释清楚。
附上链接(需要翻墙):android.jlelse.eu/inject-interfaces-without-providing-in-dagger-2-618cce9b1e29


11. binds 相关

// from https://blog.csdn.net/weixin_33924312/article/details/91874838

@BindsOptionalOf

    使用dagger2的时候,会有个问题,假如我们在需要被注入的类中请求注入对象A和对象B,如果注入的时候Component没有提供对象B,那么就会编译不通过。

    Dagger2中使用了BindsOptionalOf来解决这个问题。

@Module

public abstract class TestModule1 {

    @BindsOptionalOf

    abstract TestClass1 optionalTestClass1();

}

    定义如上一个module,然后在原来的module中使用include包含这个module,或者原来component中modules增加这个module。

    然后在被注入对象中增加注入

@Inject
Optional<TestClass1> mTestClass1;

    注入的类型需要时Optional(一个容器,java 1.8提供,或者guava提供都行)。注意,这个时候我们原来的module并没有 @Provides 方法用来提供TestClass1对象,但是编译能通过。这个时候,注入后,通过optional的get等方法将会返回空值。

    我们也可以在module中提供@Provides方法来提供对象,这个时候Optional中就能取到非空的对象。

    注意无论是否提供@Provides,请求注入的Optinal对象本身一定不为空,可能为空的是Optional容器内部的内容。

PS 可以注入的类型有

  • Optional<CoffeeCozy>
  • Optional<Provider<CoffeeCozy>>
  • Optional<Lazy<CoffeeCozy>>
  • Optional<Provider<Lazy<CoffeeCozy>>>

@BindsInstance

    存在一种情况,我们需要注入一个对象,这个对象的构造函数需要提供参数。在Dagger2 使用(一)的注入变异3 和 注入变异4中已经有两种解决方案了。分别是通过module提供或者参数的构造函数添加@Inject。

    现在,还有一种方法使用@BindsInstance

public class GameMode {

    @Inject

    public GameMode(String gameName){


    }

}

    存在一个GameMode类,构造函数需要一个gameName对象。

@Component

public interface GameComponent {

    GameMode getGameMode();


    @Component.Builder

    interface Builder{

        @BindsInstance Builder gameModeName(String str);

        GameComponent build();

    }

}

    在GameComponent中,使用@Component.Builder定义一个接口,接口首先需要一个返回Component的方法。然后使用@BindsInstance 注解一个参数为String的方法。

GameComponent component = DaggerGameComponent.builder().gameModeName("hard").build();
component.getGameMode();

    通过如上方式使用。这里我们在构建GameComponent的时候手动注入了字符串"hard"。相当于在Module中@Provides了这个字符串。

    注意在调用build()创建 Component 之前,所有@BindsInstance方法必须先调用。如果@BindsInstance方法的参数可能为 null,需要再用@Nullable标记,同时标注 Inject 的地方也需要用@Nullable标记。这时 Builder 也可以不调用@BindsInstance方法,这样 Component 会默认设置 instance 为 null。

    使用@Nullable的方法


    public class GameMode {
        @Inject
        public GameMode(@Nullable String gameName) {

        }
    }

    @BindsInstance
    Builder gameModeName(@Nullable String str);

    进阶进阶!!

    加入GameMode的构造函数需要两个String怎么办?使用@BindInstance提供两个参数为String的方法,但是dagger如何选择用哪个方法注入到构造函数中?我们想到了@Qualifier

    之前介绍的Qualifier是使用不同的参数名来区分的。其实直接使用不同的@Qualifier也能够做到同样的事情。

    比如我定了两个Qualifier,然后给构造函数做上标记!

    @BindsInstance
    Builder gameModeName(@Nullable String str);
    
    public class GameMode {
        @Inject
        public GameMode(@GameModeName String gameName, @PlayerName String playerName) {


        }
    }

    在component中同样使用标记

    @Component
    public interface GameComponent {

        GameMode getGameMode();

        @Component.Builder
        interface Builder {

            @BindsInstance
            Builder gameModeName(@GameModeName String str);

            @BindsInstance
            Builder playerName(@PlayerName String str);

            GameComponent build();

        }
    }

    这样就能区分到底调用哪个参数了。

     注:Subcomponent同样提供了 Subcomponet.Builder来完成同样的事情。

    进阶进阶!!

    关于 在上一篇@Scope中我们讲到了一个关于作用域的例子,我们复用它。

@ActivityScope
@Subcomponent(modules = ActivityModule.class)
public interface ActivityComponent {
    ClassApplication getClassApplication();
    ClassActivity getClassActivity();
    ClassUser getClassUser();

    @Subcomponent.Builder
    interface ActivityBuilder {

        @BindsInstance
        ActivityBuilder activity(Activity activity);
        
        ActivityComponent build();

    }
}

    我们修改ActivityComponent,要求手动注入一个activity变量(在android中很正常,context还是需要的)。

    这个时候如果我们在UserComponent中还是 

ActivityComponent getActivityComponent(ActivityModule activityModule);

    会出现错误,因为这样我们没有注入activity! 怎么办? 

  1.     @UserScope
    
        @Subcomponent(modules = UserModule.class)
    
        public interface UserComponent {
    
            //ActivityComponent getActivityComponent(ActivityModule activityModule);
    
            ActivityComponent.ActivityBuilder newActivityComponentBuilder();
    
            ........
    
        }

     

  2.  

    改成返回Buidler,然后开发者自己在Builder上调用activity注入activity,然后调用builder构建component这就没问题了。

//        ActivityComponent activityComponent = mUserComponent.
//                          newActivityComponentBuilder().activity(XXX).build();
//        activityComponent.getClassApplication();
//        activityComponent.getClassUser();
//        activityComponent.getClassActivity();

    像这样生成。

 

Set注入

    之前介绍的内容都是单个对象的注入,那么我们是否能将多个对象注入到容器中呢?首先是Set

    @Module

    public class GameModule {

        @Provides

        @IntoSet

        public Player getPlayer1() {

            return new Player("1");

        }


        @Provides

        @IntoSet

        public Player getPlayer2() {

            return new Player("1");

        }

    }
  1.  

    我在module中使用@Provides提供了Player,不同的是,添加了@IntoSet的注解,表示我会把这个注入到Set中。

    然后在被注入的单位中请求注入,比如

@Inject
Set<Player> mPlayers;

    不需要在component中做其他修改,就能将player1和player2注入到mPlayers中。

    注:如果存在多个Set,类型相同,就需要使用@Qualifier。

    进阶!我们可以同时向Set注入多个对象

    @Provides

    @ElementsIntoSet

    public Set<Player> getPlayers() {

        HashSet set = new HashSet<>();

        set.add(new Player("3"));

        set.add(new Player("4"));

        return set;

    }

    使用@ElementsIntoSet 并且返回Set<Player>对象。

Map注入

    map的注入和Set有些区别,因为他需要提供key

    @Module

    public class GameModule {

        @Provides

        @IntoMap

        @StringKey(value = "1")

        public Player getPlayer1() {

            return new Player("1");

        }


        @Provides

        @IntoMap

        @StringKey(value = "2")

        public Player getPlayer2() {

            return new Player("2");
        }

    }

    @IntoSet 变成了@IntoMap,并且使用@StringKey注解提供了key值。

    PS:这里的key值是String类型,所以能够注入到 Map<String,Player> 对象中。

    dagger还提供了一些内置的key类型,包裹classKey,IntKey等,android辅助包中也提供了ActivityKey等。

    

@MapKey

        StringKey的源码,StringKey的value类型为String,应该是指定了Key的数据类型为String。而StringKey又被@MapKey注解,是不是表明该注解是Map的Key的注解呢?不妨试一下,我们自定义一个AppleKey:

    @Documented

    @Target(METHOD)

    @Retention(RUNTIME)

    @MapKey

    public @interface AppleKey {

        AppleBean value();

    } 

但是编译会报错哦!!    

注释类型中声明的方法的返回类型,如果不满足指定的返回类型,那么编译时会报错:

  • 基本数据类型
  • String
  • Class
  • 枚举类型
  • 注解类型
  • 以上数据类型的数组

    但是还可以指定Enum类型,或者特定的类的。

 enum MyEnum {

        ABC, DEF;

    }


    @MapKey
    @interface MyEnumKey {

        MyEnum value();

    }


    @MapKey
    @interface MyNumberClassKey {

        Class<? extends Number> value();

    }


    @Module
    class MyModule {

        @Provides
        @IntoMap

        @MyEnumKey(MyEnum.ABC)

        static String provideABCValue() {

            return "value for ABC";

        }


        @Provides
        @IntoMap

        @MyNumberClassKey(BigDecimal.class)

        static String provideBigDecimalValue() {

            return "value for BigDecimal";

        }

    }

     进阶进阶!!!!

    使用复合键值,这个厉害了,因为map的key又不能多个,如何复合键值?


    @MapKey(unwrapValue = false)

    @Retention(value=RUNTIME)

    public @interface GameInfo {

        String name();

        float price();

    }

    @Module

    public class GameModule {

        @Provides

        @IntoMap

        @GameInfo(name = "game",price = 100f)

        public String getGameInfo(){

            return "gameinfo";

        }

    }

    然后被注入对象请求注入

@Inject
Map<GameInfo,String> mGameInfoStringMap;

    看一下键类型,这个很关键,竟然是一个GameInfo类型的。

    如果你这么做了,并且编译失败了,请不要惊讶,因为你还缺少一些依赖库:    

compile 'com.google.auto.value:auto-value:1.5.1'
provided 'javax.annotation:jsr250-api:1.0'

    具体原因会在研习篇分析源码的时候分析。

@Binds

    假设存在如下场景

    public interface HomePresenter {

    }

    public class HomePresenterImpl implements HomePresenter{

        public HomePresenterImpl(UserService userService){

        }

    }

    非常常见,我们在使用的时候一般会使用接口类型,而不是使用实现类型。

@Inject
HomePresenter mHomePresenter;

    所以我们需要在Module中provides


    @Provides

    public HomePresenter getHomePresenter(UserService userService){

        return new HomePresenterImpl(userService);

    }


    @Provides UserService getUserService(){

        return new UserService();

    }

 

  实际上注入的是一个HomePresenterImpl没有问题。

    但是,关于这类注入子类的问题,还有一种解决方案,就是使用@binds

    @Module
    public abstract class BindsDataModule {
        @Binds
        public abstract HomePresenter bindHomePresenter(HomePresenterImpl homePresenterImpl);

    }

    module类改为abstract类型的,使用@Binds注解一个返回为HomePresenter的方法,并且需要想要注入的实现类的类型参数。

    然后需要在HomePresenterImple和UserService中使用@Inject来注解构造函数。

    @Binds的使用就是如此简单,那么它到底有什么意义呢?相比原来的@Provides有什么优势呢?

    仔细想想我们在最初介绍dagger的几个变种中,有一种情况是不提供provides方法,而是在构造函数上添加@Inject的方法。其实这种方法有利于书写,方便快捷的优势,但是当遇上如上这种情况,我们需要注入的是一个接口的时候,我们无法使用@Inject注解构造函数的方法来提供一个接口(我们可以把@Inject添加到实现类中,但是在注入时,dagger并不知道你想要注入的是哪个实现。)

    所以@Binds就这样诞生了,可以想象的是,@Binds的作用其实就是告诉dagger,当请求注入一个接口时,我们使用某个实现类何其绑定,用实现类来进行注入。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值