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! 怎么办?
-
@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
@Module
public class GameModule {
@Provides
@IntoSet
public Player getPlayer1() {
return new Player("1");
}
@Provides
@IntoSet
public Player getPlayer2() {
return new Player("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,当请求注入一个接口时,我们使用某个实现类何其绑定,用实现类来进行注入。