前面的概念可能开始看不懂,给点耐心,看到例子就懂了。 本篇文章需要注解方面的知识,不了解的可以先看:http://blog.csdn.net/niubitianping/article/details/60145128
Dagger2的内容有点多,一点得有耐心。
Dagger2是一个Android/Java平台上快速依赖注入框架,由谷歌开发,最早的版本Dagger1 由Square公司开发。依赖注入框架主要用于模块间解耦,提高代码的健壮性和可维护性。
几大优点:
全局对象实例的简单访问方式,@Inject复杂的依赖关系只需要简单的配置让单元测试和集成测试更加方便轻松指定作用域github地址: https://github.com/google/dagger
说明文档: https://google.github.io/dagger/
关系图:
主要元素有以下三个:
Container: 相当于Android的Activity,在activity里面获取其他类的实例
Component: 一个接口,告诉activty你要获取实例的类在哪里找
Module: activty要的东西就在这里初始化。
看不懂下面的注解可以先看例子使用了,然后回来看就懂了。还有其他注解,在后面会讲到。
1. @Inject
通常在需要依赖的地方使用这个注解。你用它告诉Dagger这个类或字段需要依赖注入,,Dagger就会构建一个这个类的实例并满足他们的依赖。
2. @Module
用来修饰modules类。 所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例时候,就知道从哪里去找到需要的依赖。modules的一个重要特征是它们设计为分区并组合在一起(比如说,我们的app中可以有很多在一起的modules)
3. @Provide
我们在modules中定义的方法就是是用这个注解来修饰,以此来告诉Dagger我们想要构造对象并提供这些依赖。
4. @Component
Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁,他的主要作用就是链接这两个部分。Components可以提供所有定义了的类型的实例,比如: 我们必须用@Component注解一个接口然后列出所有的
在项目的build.gradle添加:
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.0' //下面添加apt classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } } ....在需要的Module的build.gradle添加两个部分:
apply plugin: 'com.android.application' //添加的第一部分 apply plugin: 'com.neenbedankt.android-apt' android { ...... } dependencies { ...... //添加的第二部分,版本太高会导致编译失败,这里用2.8就可以了 compile 'com.google.dagger:dagger:2.8' //dagger的api apt "com.google.dagger:dagger-compiler:2.8" //指定注解处理器 compile 'org.glassfish:javax.annotation:10.0-b28' //Adnroid缺失的部分javax注解 }这里假如我想在Activity里面实例化一个LoginCtrl的类。于是创建一个LoginCtrl类
public class LoginCtrl { public void login(String name,String pass){ Log.e("tag@@", "name:"+name+" pass:"+pass); } }在这个类里面 真正的实例化LoginCtrl类。创建一个LoginModule类,类使用@Module修饰,然后里面添加方法provideLoginCtrl (方法名随便),返回类型为LoginCtrl,使用@Provides修饰方法名。如下:
@Module public class LoginModule { @Provides LoginCtrl provideLoginCtrl(){ return new LoginCtrl(); } }创建一个LoginComponent接口,用来告诉Activity你要实例化的东西在这里。 @Component的参数提供Module进行联系, 接口里面的方法和activty进行联系。 这样形成了桥梁
PS:注意接口里面的方法必须要有参数,不然会编译错误
@Component(modules = LoginModule.class) public interface LoginComponent { //要添加方法,方法必须添加参数,参数类型必须和调用时候一致 void inject(TestActivity activity); }搞完上面的步骤之后,点击菜单栏的Build -> Build Project 或者 Build Module。稍等一会儿。 等构建完成之后,就会在module -> 的build -> generated -> source -> apt -> debug -> 包名/路径 -> 看到生成对应的文件
接下来就可以使用了,在activity中使用@Inject修饰你要实例化的类,然后使用类Dagger+Compontent接口的类名初始化Dagger,就成功注入了,我这里是新建的一个TestAtivity。
public class TestActivity extends Activity { @Inject LoginCtrl loginCtrl; //注入的方式实例化LoginCtrl @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化注入,inject是LoginComponent接口里面自定义的方法; DaggerLoginComponent.create().inject(this); // 然后就可以调用loginCtrl里面的方法了 loginCtrl.login("tianping","pass"); } }就可以看到输出
com.tpnet.dagger2test E/tag@@: name:tianping pass:passPS注意:Component接口方法里面的参数,在Activity传递的时候必须类型一致,MainActivity就是MainActivity.this。如果参数类型是Context,你传递了MainActivity.this过去就会导致注入失败,实例化对象为空。
把上面的栗子修改一下,添加两个类,在LoginCrtl里面进行控制这两个类。
LoginStore.java类,看作为本地保存登录信息的类。
public class LoginStore { private Context mContext; public LoginStore(Context mContext) { this.mContext = mContext; } public void login(String name,String pass){ Log.e("@@", "LoginStore进行保存: name="+name+",pass="+pass); SharedPreferences.Editor editor = mContext.getSharedPreferences("login",Context.MODE_PRIVATE).edit(); editor.putString("name",name); editor.putString("pass",pass); editor.apply(); } }LoginService.java类,看作为链接网络登录的类。
public class LoginService { public void login(String name,String pass){ //网络请求登录.... Log.e("@@", "LoginService登录: name="+name+",pass="+pass); } }LoginCtrl修改为:
public class LoginCtrl { private LoginStore mLoginStore; private LoginService mLoginService; public LoginCtrl(LoginService service, LoginStore store) { this.mLoginStore = store; this.mLoginService = service; } public void login(String name,String pass){ mLoginService.login(name,pass); mLoginStore.login(name,pass); } }LoginModule修改为:
@Module public class LoginModule { private Context mContext; //供给LoginStore使用 public LoginModule(Context mContext) { this.mContext = mContext; } @Provides LoginCtrl provideLoginCtrl(LoginService service, LoginStore store){ return new LoginCtrl(service,store); } }看了上面修改完之后的代码,LoginModule类里面需要在构造方法里面传参,怎么传呢? 在activity初始化的时候使用builder:
public class TestActivity extends Activity { @Inject LoginCtrl loginCtrl; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //DaggerLoginComponent.create().inject(this); //当Module需要构造方法传参的时候,使用builder的方式初始化Dagger。 DaggerLoginComponent.builder() .loginModule(new LoginModule(this)) //loginModule这个方法是构建之后才有的 .build() .inject(this); loginCtrl.login("天平","密码"); } }总结就是: 当Module需要构造方法传参的时候,使用builder的方式初始化Dagger
上面的修改完的代码运行肯定会报错的,报错信息如下:
原因就是在LoginModule里面,provideLoginCtrl方法有两个参数:LoginService和LoginStore,这俩参数并没有注解实例化,所以这里就报错了。 解决方法是下面两个。
通过构造方法@Inject在Module类里面@Provide3.2.1 通过构造方法Inject
module里面的方法需要参数的解决方法1: 通过构造方法@Inject
又分两种情况,带参数的构造方法和不带参数的。
不带参数的如果Moudle里面的方法的参数这个类的构造方法不需要参数的,直接在构造方法添加@Inject即可。
例如Loginservice,在LoginService里添加一个Inject构造方法:
public class LoginService { //构造方法没有参数的,直接在构造方法用@Injext修饰即可 @Inject public LoginService() { } public void login(String name, String pass){ //网络请求登录.... Log.e("@@", "LoginService登录: name="+name+",pass="+pass); } } 带参数的如果Moudle里面的方法的参数这个类的构造方法需要带参数的。例如LoginStore,构造方法需要提供Context参数。Inject之后,还需要在Module类里面@Provide一个String类型的方法,作为LoginStore构造方法的参数
LoginStore.java修改为:
public class LoginStore { private Context mContext; //这里@Inject构造方法,然后在Module类里面还需要Provide一个String的方法 @Inject public LoginStore(Context mContext) { this.mContext = mContext; } public void login(String name,String pass){ Log.e("@@", "LoginStore进行保存: name="+name+",pass="+pass); SharedPreferences.Editor editor = mContext.getSharedPreferences("login",Context.MODE_PRIVATE).edit(); editor.putString("name",name); editor.putString("pass",pass); editor.apply(); } }LoginModule.java修改为i:
@Module public class LoginModule { private Context mContext; //供给LoginStore使用 public LoginModule(Context mContext) { this.mContext = mContext; } //为LoginStore提供构造参数 @Provides Context provideStoreContext(){ return mContext; } @Provides LoginCtrl provideLoginCtrl(LoginService service, LoginStore store){ return new LoginCtrl(service,store); } }好了,程序正常,这时候运行程序就会看到输出:
03-04 19:14:00.124 31170-31170/? E/@@: LoginService登录: name=天平,pass=密码 03-04 19:14:00.124 31170-31170/? E/@@: LoginStore进行保存: name=天平,pass=密码3.2.2 在Module类里面@Provide
module里面的方法需要参数的解决方法1: 在Module类里面@Provide一个参数类型。
我们把代码改回3.2.1之前那样修改LoginModule增加两个方法 @Module public class LoginModule { private Context mContext; //供给LoginStore使用 public LoginModule(Context mContext) { this.mContext = mContext; } /** * 为provideLoginCtrl方法的service参数提供实例化 * @return */ @Provides LoginService provideLoginService(){ return new LoginService(); } /** * 为provideLoginCtrl方法的store参数提供实例化 * @return */ @Provides LoginStore provideLoginStore(){ return new LoginStore(mContext); } @Provides LoginCtrl provideLoginCtrl(LoginService service, LoginStore store){ return new LoginCtrl(service,store); } }程序正常运行,看到输出内容为:
03-04 19:20:40.795 31739-31739/com.tpnet.dagger2test E/@@: LoginService登录: name=天平,pass=密码 03-04 19:20:40.795 31739-31739/com.tpnet.dagger2test E/@@: LoginStore进行保存: name=天平,pass=密码简单概括: 有需有求,activity的Inject需要什么对象,Module类就提供什么对象。
在1.1的关系图上面有说到多个Module , 说明了一个Component是可以依赖多个Module的,方法有三种:
多个@Module修饰类include @Moudle修饰类dependencies依赖Component来逐个理解。
Component的值为@Module修饰类, 在@Component的接口里面需要添加Module类,如果需要依赖多个module类,用数组就行了。
再新建一个getInfoModule.java类:
@Module public class getInfoModule { }在Logincomponent里面添加module数组即可
@Component(modules = {LoginModule.class,getInfoModule.class}) public interface LoginComponent { void inject(TestActivity activity); }这是第一种模块化方法
@Moudle修饰的类include @Moudle修饰类, 这里是在LoginModule类里面的@Module修饰符添加includes module
把4.1在LoginModule.java添加的代码删掉, 然后修改LoginModule的代码:
//这里includes了需要的Module @Module(includes = getInfoModule.class) public class LoginModule { private Context mContext; //供给LoginStore使用 public LoginModule(Context mContext) { this.mContext = mContext; } ....(下面的代码就不拷贝了) }Component 依赖dependencies Component, 新建一个GetInfoComponent.java 接口,在里面依赖需要的Module:
@Component(modules = getInfoModule.class) public interface GetInfoComponent { }然后在LoginComponent.java里面使用dependencies依赖GetInfoComponent.class
@Component(modules = LoginModule.class,dependencies = GetInfoComponent.class) public interface LoginComponent { void inject(TestActivity activity); }问题1. Activity里面@Inject的类是根据什么初始化的呢?
其实是根据Module类里面的方法的返回类型进行判断。问题2. 那么问题就来了,加入我在activity需要注入两个相同类型的类呢? 怎么区分呢?
有两种方法:
在Module里面的方法和Activity Inject的类上面添加@Named(value)修饰,value就是用以区分自定义注解。在3.2.2的代码上进行修改,在TestActivity再Inject一个LoginCtrl:
public class TestActivity extends Activity { @Named("one") //用以区分LoginCtrl实例 @Inject LoginCtrl loginCtrlOne; @Named("two") //用以区分LoginCtrl实例 @Inject LoginCtrl loginCtrlTwo; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //DaggerLoginComponent.create().inject(this); //当Module需要构造方法传参的时候,使用builder的方式初始化Dagger。 DaggerLoginComponent.builder() .loginModule(new LoginModule(this)) //loginModule这个方法是构建之后才有的 .build() .inject(this); loginCtrlOne.login("天平one","密码one"); loginCtrlTwo.login("天平two","密码two"); } }在LoginModule.java里面编辑添加一个返回LoginCtrl的方法
@Module public class LoginModule { private Context mContext; //供给LoginStore使用 public LoginModule(Context mContext) { this.mContext = mContext; } /** * 为provideLoginCtrl方法的service参数提供实例化 * @return */ @Provides LoginService provideLoginService(){ return new LoginService(); } /** * 为provideLoginCtrl方法的store参数提供实例化 * @return */ @Provides LoginStore provideLoginStore(){ return new LoginStore(mContext); } @Named("one") //用以区分LoginCtrl实例 @Provides LoginCtrl provideLoginCtrlOne(LoginService service, LoginStore store){ Log.e("@@", "provideLoginCtrlOne被调用: "); return new LoginCtrl(service,store); } @Named("two") //用以区分LoginCtrl实例 @Provides LoginCtrl provideLoginCtrlTwo(LoginService service, LoginStore store){ Log.e("@@", "provideLoginCtrlTwo被调用: "); return new LoginCtrl(service,store); } }可以看到输出内容为一下,程序正常:
03-04 22:30:24.051 13833-13833/? E/@@: provideLoginCtrlOne被调用: 03-04 22:30:24.051 13833-13833/? E/@@: provideLoginCtrlTwo被调用: 03-04 22:30:24.051 13833-13833/? E/@@: LoginService登录: name=天平one,pass=密码one 03-04 22:30:24.051 13833-13833/? E/@@: LoginStore进行保存: name=天平one,pass=密码one 03-04 22:30:24.061 13833-13833/? E/@@: LoginService登录: name=天平two,pass=密码two 03-04 22:30:24.061 13833-13833/? E/@@: LoginStore进行保存: name=天平two,pass=密码two我们按住Ctrl,然后鼠标点击@Named,可以看到他的注解源码为:
@Qualifier @Documented @Retention(RUNTIME) public @interface Named { /** The name. */ String value() default ""; }主要就是@Qualifier这个注解,我们也可以自己定义注解来区分。
5.2.1 使用value 创建一个注解,例如TPTest.java:
@Qualifier @Retention(RUNTIME) public @interface TPTest { String value() default ""; }然后把5.1的例子的@Named改为@TPTest,你会发现也是可以的。。
5.2.2 不使用value 5.2.1是定义了一个注解,利用里面的value进行区分,也可以用两个注解进行区分。
新建一个One注解:
@Qualifier @Retention(RUNTIME) public @interface One { //这里没有value }再新建一个Two注解
@Qualifier @Retention(RUNTIME) public @interface Two { //这里没有value }然后把之前的@TPTest("one")改为@One , @TPTest("two")改为@Two , 运行你会发现还是一样的。
在刚刚自定义注解的时候可以看到Qualifier这个关键词,这个关键词的作用就是: 用来区分不同的对象实例,@Named 是@Qualifier的一种实现而已。