Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Android快速依赖注入框架Dagger2使用1

$
0
0

一、啥是Dagger2

前面的概念可能开始看不懂,给点耐心,看到例子就懂了。 本篇文章需要注解方面的知识,不了解的可以先看:http://blog.csdn.net/niubitianping/article/details/60145128

Dagger2的内容有点多,一点得有耐心。

1.1 简介

Dagger2是一个Android/Java平台上快速依赖注入框架,由谷歌开发,最早的版本Dagger1 由Square公司开发。依赖注入框架主要用于模块间解耦,提高代码的健壮性和可维护性。

几大优点:

  • 全局对象实例的简单访问方式,@Inject
  • 复杂的依赖关系只需要简单的配置
  • 让单元测试和集成测试更加方便
  • 轻松指定作用域

github地址:
https://github.com/google/dagger

说明文档:
https://google.github.io/dagger/

1.1 主要元素

关系图:
这里写图片描述

主要元素有以下三个:

  • Container: 相当于Android的Activity,在activity里面获取其他类的实例

  • Component: 一个接口,告诉activty你要获取实例的类在哪里找

  • Module: activty要的东西就在这里初始化。

1.2 主要注解

看不懂下面的注解可以先看例子使用了,然后回来看就懂了。还有其他注解,在后面会讲到。

1. @Inject

通常在需要依赖的地方使用这个注解。你用它告诉Dagger这个类或字段需要依赖注入,,Dagger就会构建一个这个类的实例并满足他们的依赖。

2. @Module

用来修饰modules类。 所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例时候,就知道从哪里去找到需要的依赖。modules的一个重要特征是它们设计为分区并组合在一起(比如说,我们的app中可以有很多在一起的modules)

3. @Provide

我们在modules中定义的方法就是是用这个注解来修饰,以此来告诉Dagger我们想要构造对象并提供这些依赖。

4. @Component

Components从根本上来说就是一个注入器,也可以说是@Inject@Module的桥梁,他的主要作用就是链接这两个部分。Components可以提供所有定义了的类型的实例,比如: 我们必须用@Component注解一个接口然后列出所有的

二、Dagger2基本使用

2.1 导包

在项目的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注解
}

2.2 创建你要实例化的类

这里假如我想在Activity里面实例化一个LoginCtrl的类。于是创建一个LoginCtrl类

public class LoginCtrl {


    public void login(String name,String pass){
        Log.e("tag@@", "name:"+name+" pass:"+pass);
    }

}

2.3 创建module类

在这个类里面 真正的实例化LoginCtrl类。创建一个LoginModule类,类使用@Module修饰,然后里面添加方法provideLoginCtrl (方法名随便),返回类型为LoginCtrl,使用@Provides修饰方法名。如下:

@Module
public class LoginModule {

    @Provides LoginCtrl provideLoginCtrl(){
        return new LoginCtrl();
    }

}

2.4 创建Component接口

创建一个LoginComponent接口,用来告诉Activity你要实例化的东西在这里。 @Component的参数提供Module进行联系, 接口里面的方法和activty进行联系。 这样形成了桥梁

PS:注意接口里面的方法必须要有参数,不然会编译错误

@Component(modules = LoginModule.class)
public interface LoginComponent {

    //要添加方法,方法必须添加参数,参数类型必须和调用时候一致
    void inject(TestActivity activity);

}

2.5 构建

搞完上面的步骤之后,点击菜单栏的Build -> Build Project 或者 Build Module。稍等一会儿。 等构建完成之后,就会在module -> 的build -> generated -> source -> apt -> debug -> 包名/路径 -> 看到生成对应的文件
这里写图片描述

2.6 在Activity注入

接下来就可以使用了,在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:pass

PS注意:Component接口方法里面的参数,在Activity传递的时候必须类型一致,MainActivity就是MainActivity.this。如果参数类型是Context,你传递了MainActivity.this过去就会导致注入失败,实例化对象为空。

三、Module参数传递

把上面的栗子修改一下,添加两个类,在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);
    }

}

3.1 Module构造方法传参

看了上面修改完之后的代码,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

3.2 module里面的方法参数

上面的修改完的代码运行肯定会报错的,报错信息如下:

原因就是在LoginModule里面,provideLoginCtrl方法有两个参数:LoginService和LoginStore,这俩参数并没有注解实例化,所以这里就报错了。 解决方法是下面两个。

  • 通过构造方法@Inject
  • 在Module类里面@Provide

3.2.1 通过构造方法Inject

module里面的方法需要参数的解决方法1: 通过构造方法@Inject

又分两种情况,带参数的构造方法和不带参数的。

  1. 不带参数的

如果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);

    }

}
  1. 带参数的

如果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一个参数类型。

  1. 我们把代码改回3.2.1之前那样
  2. 修改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类就提供什么对象。

四、Dagger2模块化

在1.1的关系图上面有说到多个Module , 说明了一个Component是可以依赖多个Module的,方法有三种:

  • 多个@Module修饰类
  • include @Moudle修饰类
  • dependencies依赖Component

来逐个理解。

4.1 多个@Module修饰类

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);

}

这是第一种模块化方法

4.2 include @Moudle修饰类

@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;
    }


    ....(下面的代码就不拷贝了)

}

4.3 dependencies依赖Component

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就是用以区分
  • 自定义注解。

5.1 方法1:@Named(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

5.2 方法2:自定义注解

我们按住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 , 运行你会发现还是一样的。

5.3 @Qualifier注解

在刚刚自定义注解的时候可以看到Qualifier这个关键词,这个关键词的作用就是: 用来区分不同的对象实例@Named@Qualifier的一种实现而已。

作者:niubitianping 发表于2017/3/8 18:22:30 原文链接
阅读:11 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>