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

一、啥是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的一種實現而已。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章