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 注解 实例化对象@ProvidesA providerA() {return new A();}
}

(4)创建一个Component

//第一步 添加@Component
//第二步 添加module
@Component(modules = {MainModule.class})
public interface MainComponent {//第三步  写一个方法 绑定Activity /Fragmentvoid 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 对象的实例*/@InjectA a;@Overrideprotected 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 {@ProvidesA 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")@ProvidesMainApi provideMainApiDev(MainChildApi mainChildApi, String url) {return new MainApi(mainChildApi, activity,"dev");}@Named("release")@ProvidesMainApi provideMainApiRelease(MainChildApi mainChildApi, String url) {return new MainApi(mainChildApi, activity,"release");}}

在Activity/Fragment中使用

public class MainActivity extends AppCompatActivity {@Named("dev")@InjectMainApi apiDev;@Named("release")@InjectMainApi apiRelease;@Overrideprotected 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@ProvidesA providerA(B b) {return new A(b);}

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

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

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

    @InjectA a2;@InjectA 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{@InjectLazy<UploadPresenter> mPresenter1;@InjectProvider<UploadPresenter> mPresenter2;@Overrideprotected 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;}@ProvidesContext context(){return mContext;}@Named("login")@ProvidesDialog loginDialog(Context context){Dialog dialog = new Dialog(context);dialog.setTitle("登录提示");....return dialog;}@Named("register")@ProvidesDialog 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")@InjectDialog mDialogLogin;@Named("register")@InjectDialog mDialogRegister;      @Overrideprotected 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{@InjectUploadPresenter mPresenter1;@InjectUploadPresenter mPresenter2;@Overrideprotected 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@ProvidesUploadPresenter uploadPresenter() {return new UploadPresenter();}
}

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

@ActivityScope
public class UploadPresenter{@Injectpublic 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@ProvidesApplication application(){return mApplication;}@Singleton@ProvidesActivityStackManager 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;@Overridepublic void onCreate() {super.onCreate();mAppComponent= DaggerAppComponent.builder().appModule(new AppModule(this)).build();}
}

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

public class UploadActivity extends AppCompatActivity{@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);MyApplication.mAppComponent.activityStackManager().pushOneActivity(this);     }@Overrideprotected 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(){}  @Overridepublic Observable<List<User>> loadUsers(){//Return user list observable}
}

但是请注意,在HomePresenterImp这个类中,我并没有使用@Inject在构造方法上。所以,我们要在module中我们经常会使用一个叫provide的注解提供这个类的对象,就像这样。

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

但是,如果我们需要添加一个依赖到presenter叫UserService,那就意味着,我们也要在module中添加一个provide方法提供这个UserService,然后在HomePresenterImp类中加入一个UserService参数的构造方法;
或者
我们可以使用@Binds这个注解就想这样:

@Module
public abstract class HomeModule {@Bindspublic abstract HomePresenter bindHomePresenter(HomePresenterImp homePresenterImp);
}

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

@PerActivity
public class HomePresenterImp implements HomePresenter {private UserService userService;@Injectpublic HomePresenterImp(UserService userService){this.userService = userService;}@Overridepublic 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来解决这个问题。

@Modulepublic abstract class TestModule1 {@BindsOptionalOfabstract 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 {@Injectpublic GameMode(String gameName){}}

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

@Componentpublic interface GameComponent {GameMode getGameMode();@Component.Builderinterface 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 {@Injectpublic GameMode(@Nullable String gameName) {}}@BindsInstanceBuilder gameModeName(@Nullable String str);

进阶进阶!!

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

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

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

    @BindsInstanceBuilder gameModeName(@Nullable String str);public class GameMode {@Injectpublic GameMode(@GameModeName String gameName, @PlayerName String playerName) {}}

在component中同样使用标记

    @Componentpublic interface GameComponent {GameMode getGameMode();@Component.Builderinterface Builder {@BindsInstanceBuilder gameModeName(@GameModeName String str);@BindsInstanceBuilder 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.Builderinterface ActivityBuilder {@BindsInstanceActivityBuilder 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();........}

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

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

像这样生成。

Set注入

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

    @Modulepublic class GameModule {@Provides@IntoSetpublic Player getPlayer1() {return new Player("1");}@Provides@IntoSetpublic Player getPlayer2() {return new Player("1");}}

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

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

@Inject
Set<Player> mPlayers;

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

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

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

    @Provides@ElementsIntoSetpublic 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

    @Modulepublic 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)@MapKeypublic @interface AppleKey {AppleBean value();} 

但是编译会报错哦!!

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

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

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

 enum MyEnum {ABC, DEF;}@MapKey@interface MyEnumKey {MyEnum value();}@MapKey@interface MyNumberClassKey {Class<? extends Number> value();}@Moduleclass 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();}@Modulepublic 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

@Providespublic HomePresenter getHomePresenter(UserService userService){return new HomePresenterImpl(userService);}@Provides UserService getUserService(){return new UserService();}

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

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

    @Modulepublic abstract class BindsDataModule {@Bindspublic abstract HomePresenter bindHomePresenter(HomePresenterImpl homePresenterImpl);}

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

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

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

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

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

Dagger 2 基本用法相关推荐

  1. 单元测试以及dagger的使用

    最近对测试比较敏感,因为涉及了代码重构,虽然我是很有自信的一个人,但是过度严谨的强迫症很多时候让我很尴尬. 所以还是希望面对源代码有一定的测试代码,以方便验证代码重构之后的正确与否. 所以,游戏开始了 ...

  2. Android 依赖注入可以更简单 —— 新版本 Dagger 2 使用教学

    今年 3 月 21 号 Dagger 2 在 2.10 版本之后针对 Android 方面做了很大的优化,使用方法也随之有了不少变化.本次改动除了让 Dagger 2 的使用更加符合控制反转原则,还针 ...

  3. 依赖注入利器 - Dagger ‡

    转载请标明出处:http://blog.csdn.net/shensky711/article/details/53715960 本文出自: [HansChen的博客] 概述 声明需要注入的对象 如何 ...

  4. dagger2 注入_如何使用Dagger 2在您的应用程序中实现依赖注入

    dagger2 注入 Kriptofolio应用程序系列-第4部分 (Kriptofolio app series - Part 4) Dependency injection will signif ...

  5. android注解Dagger2,Dagger 2 在 Android 上的使用(二)

    本文介绍了Dagger 2 中@Inject.@Component.@Module和@Provides以及@Module和@Binds的使用. <七步诗> 煮豆燃豆萁,豆在釜中泣. 本是同 ...

  6. Dagger学习 -- 基础概念

    目标 了解 Dagger 基本概念 官方定义 Google 对 Dagger 的定义如下: Dagger is a fully static, compile-time dependency inje ...

  7. dagger android,在Android开发中使用Dagger2的方法

    在Android开发中使用Dagger2的方法 发布时间:2020-12-08 17:12:38 来源:亿速云 阅读:236 作者:Leah 在Android开发中使用Dagger2的方法?相信很多没 ...

  8. Dagger的使用一

    文章目录 一.前言 二.添加依赖 三.简单示例 四.作用域 五.自定义作用域 六.@Binds 和 @Provides 七.关于Dagger为什么在Android中要比Hilt复杂 八.inject( ...

  9. Koin--适用于Kotlin的超好用依赖注入框架,Dagger替代者,Koin史上最详细解说,一篇就够了,妈妈再也不用担心我不会依赖注入了

    今年呆在家中实在无聊,外面太危险了,还是在家学习比较安全可持续. 过年期间,我又复习了几遍依赖注入框架Dagger. 诶,什么是依赖注入? 说白了就是降低跟类对象之间的耦合,当需要修改类对象的时候,能 ...

最新文章

  1. Python操作 RabbitMQ、Redis、Memcache、SQLAlchemy
  2. 前端开发需要了解的JS插件
  3. Python基础:第一个Python程序(2)
  4. 如何在友好的情景下向用户索取手机权限?
  5. ad09机械层说明_悉数PCB上的各种层
  6. 河马php一句话木马,一句话木马的套路
  7. wps如何自己制作流程图_自己如何制作APP软件
  8. extmail从数据库导出通讯录
  9. python定时下载FTP指定文件
  10. 虚拟机不能上网以及无法ping通百度的解决方案
  11. 说明使用tc编程的一般步骤 c语言,TC编程手册详解-完整版.doc
  12. 微信小程序-如何解决onShareAppMessage转发gif格式图片不展示?【亲测有效】
  13. egret 使用EUI开发UI界面
  14. Prometheus资源监控工具配置小结
  15. 5.深度解密五:网站被百度搜索引擎降权的原因及百度算法汇总
  16. 开发一个渐进式Web应用程序(PWA)前都需要了解什么?
  17. 【自用】Vue项目中使用自定义字体样式
  18. 【操作系统的目标和作用】
  19. php简易留言板功能,php简单的留言板与回复功能具体实现
  20. 全国计算机等级考试二级cpp试题,2017年全国计算机二级C++考试试题附答案

热门文章

  1. u盘拷贝服务器文件,服务器向u盘拷贝数据库
  2. 《会计学》账户与复式记账笔记的思维导图
  3. 突破生命法则极限!它会是外星生命的遗传密码?
  4. C语言 vs要安装什么_水电安装装修知识|水电安装要注意什么?
  5. React-Native全球化语言切换工具库react-native-i18n
  6. 手把手带你安装PyTorch指定版本嘿!
  7. 两个指针变量不可以做什么
  8. CVE-2016-2183 解决方法
  9. Java之访问修饰符
  10. IT运维工单高效协同,助力打造一站式运维方案