1. 程式人生 > >深入淺出,一篇文章讓你學會Dagger2使用

深入淺出,一篇文章讓你學會Dagger2使用

本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出

前言

最近在做專案中,用到了Dagger2,所以找了一些部落格並參考了菜鳥窩Ivan的視訊,算是知道如何使用了,但是對其理解還相差很遠。所以這篇文章重點針對如何使用和使用中常見的一些問題。
本文由上下2篇文章整合在一起。可能有些長,但是讀起來會很爽

Dagger2

根據官網來說他就是一個快速的依賴注入框架。如果對依賴注入不太瞭解,那麼請看這篇文章依賴注入原理,在這裡我就不過多介紹了。其實他的作用重點就是解耦和管理例項物件。那我們看看他具體有什麼好處:

  1. 依賴的注入和配置獨立於元件之外,注入的物件在一個獨立、不耦合的地方初始化,這樣在改變注入物件時,我們只需要修改物件的實現方法,而不用大改程式碼庫。

  2. 依賴可以注入到一個元件中:我們可以注入這些依賴的模擬實現,這樣使得測試更加簡單。

  3. app中的元件不需要知道有關例項建立和生命週期的任何事情,這些由我們的依賴注入框架管理的

這是網上的一切說法。對於文字我們不好理解,還是看看在具體應用中我們該如何去使用。不過在使用之前我們先簡單的理解及概念。
@Inject:
通常在需要依賴的地方使用這個註解。換句話說,你用它告訴Dagger這個類或者欄位需要依賴注入。這樣,Dagger就會構造一個這個類的例項並滿足他們的依賴。

@Module:
Modules類裡面的方法專門提供依賴,所以我們定義一個類,用@Module註解,這樣Dagger在構造類的例項
時候,就知道從哪裡去找到需要的 依賴。modules的一個重要特徵是它們設計為分割槽並組合在一起(比如說,
我們的app中可以有多個組成在一起的modules)

@Provide:
在modules中,我們定義的方法是用這個註解,以此來告訴Dagger我們想要構造物件並提供這些依賴。

@Component:
Components從根本上來說就是一個注入器,也可以說是@Inject和@Module的橋樑,它的主要作用就是連線這兩個部分。
讀不懂也沒關係,我們接下來實戰程式碼。在講解後在結合概念我們就能很好的理解了

引入

引入方法有2種:
第一種:
在工程的build.gradle檔案中新增android-apt外掛(該外掛後面介紹)

buildscript {

    ....

    dependencies {

        classpath 'com.android.tools.build:gradle:2.1.0'
// 新增android-apt 外掛 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' } }

在app的中的build.gradle檔案中新增配置

apply plugin: 'com.android.application'
// 應用外掛
apply plugin: 'com.neenbedankt.android-apt'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.mahao.alex.architecture"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}


dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.3.0'

    // dagger 2 的配置
    compile 'com.google.dagger:dagger:2.4'
    apt 'com.google.dagger:dagger-compiler:2.4'
    compile 'org.glassfish:javax.annotation:10.0-b28'// 新增java 註解庫
}

以上兩個配置就可以了。

android-apt是Gradle編譯器的外掛,根據其官方文件,主要兩個目的:

  • 編譯時使用該工具,最終打包時不會將該外掛打入到apk中。

  • 能夠根據設定的源路徑,在編譯時期生成相應程式碼。

在匯入類庫時,

    compile 'com.google.dagger:dagger:2.4'
    apt 'com.google.dagger:dagger-compiler:2.4'

dagger是主要的工具類庫。dagger-compiler為編譯時期生成程式碼等相關的類庫。

在android-apt的文件中,也推薦使用這種方式。因為,編譯時期生成程式碼的類庫在執行期並不需要,那麼將其分為兩個庫,(執行類庫dagger)和(編譯器生成程式碼類庫(dagger-compiler)),那麼在打包時,就不需要將dagger-compiler打入其中(用不到),減小APK 的大小

第二種:
這種方法比較簡單(Android Studio2.2以上),直接引入依賴

 compile 'com.google.dagger:dagger:2.9'
 annotationProcessor 'com.google.dagger:dagger-compiler:2.9'

推薦第一種方式

實踐(基本用法)

public class ApiService {

    public void register() {
        //註冊的方法
         Log.i("TAG", "ApiService: ");
    }

}

public class UserStroe {

    public void login(){
        //登入的方法
    }
}

首先假設我們現在有兩個方法,一個是註冊,另一個是登入。如果我們現在MainActivity中呼叫register();我們一般會這麼寫:

ApiSeivie apiServce=new ApiService();
apiService.register();

如果們們使用Dagger2該如何使用呢?
- module
首先:我們先建立module(他是主要提供例項的類),這裡我們定義為UserModule:

@Module
public class UserModule {

    public UserModule() {
    }

    @Provides
    public ApiService provideApiService() {
        return new ApiService();
    }
}

@Module:
Modules類裡面的方法專門提供依賴,所以我們定義一個類,用@Module註解,這樣Dagger在構造類的例項
時候,就知道從哪裡去找到需要的依賴。

@Provide:
在modules中,我們定義的方法是用這個註解,以此來告訴Dagger我們想要構造物件並提供這些依賴。

現在我們回頭在看看概念是不就明白其中的含義了。

可以看到我們在MainActivity中需要ApiService,我們在module中建立他的例項。等他需要的時候我們就給他。現在module建立好了。我們還需要呼叫者(MainActivity)和被呼叫者(module)之間的橋樑,這就是Component。

注意,在module中,我們建立的方法必須是public不可以是privite。這個也很好理解。我們建立的方法本來就是給外界呼叫的,如果你用privite的話只能本類使用了。

Component:

@Component(modules = {UserModule.class})
public interface UserComponet {

    void inject(MainActivity activity);
}
注意:inject方法接收父型別引數,而呼叫時傳入的是子型別物件則無法注入。比如你想作用BaseActivity,inject()就傳入BaseActivity,但是隻能作用BaseActivity不能作用子類MainActivity。反之亦然;

@Component:
我們定義UserComponet並加上@Component註解。表明他是一個橋樑。首先他必須是一個介面。其次我們必須依賴一個module(當然,從{}這個符號我們就可以看到,他可以同時依賴多個module),它還有另一引數dependencies,這裡我們先不說他,等後面講到了再說他。這裡我們看到他裡面只有一個方法 void inject().這裡我們傳入MainActivity,因為我們想在這個類中使用我們例項ApiService。這樣module和MainActivity通過Component就關聯起來了。建立好這些類以後我們需要Rebuild Progect。
圖片.png
這是會生成一個DaggerUserComponet類,這個類命名是以Dagger開頭接上我們UserComponet類名。這個類方法很少,主要就是將我們的MainActivity和Component關聯起來,通過:

DaggerUserComponet.create().inject(this);

將我們的MainActivity傳入Component的inject()方法中,這樣這個橋樑就就通了,我們就可以使用了,如下:

public class MainActivity extends AppCompatActivity {


    @Inject
    ApiService mApiService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerUserComponet.create().inject(this);
        mApiService.register();
        Log.d("TAG", "onCreate: " + mApiService);
    }
}
注意 這裡我們 @Inject註解的物件不能用privite修飾。

在我們的想要的建立的物件加上@Inject註解並且呼叫DaggerUserComponet.create().inject(this);後我們就可以使用這個物件了。我們執行看看結果:

dagger2 D/TAG: ApiService: 
com.example.ggxiaozhi.dagger2 D/TAG: onCreate: com.example.ggxiaozhi.dagger2.ApiService@4a7c73b4

可以看到正常執行沒有問題了。例項物件已經被我們創建出來了。為們用2張圖總結一下:
圖片.png
這張圖從概念出發,我們再從我們的例子中總結出來經驗概括就是這樣的:

圖片.png

明白了流程後那他是怎麼解耦的呢?使用方法就這麼簡單就結束了嗎?答案是肯定的。我們一定不止這麼簡單,關於解耦的話我就舉一個簡單的例子。之前我們建立了2個類註冊和登入,現在我們再建立一個類去管理他們:

public class UserManager {

    UserStroe mUserStroe;
    ApiService mApiService;

    public UserManager() {
        mApiService = new ApiService();
        mUserStroe = new UserStroe();
    }

    public void register() {
        mApiService.register();
        mUserStroe.login();
    }
}

然後我們在MainActivity中去呼叫UserManager中的register方法。這麼一個看好像是沒什麼問題,但是如果我們mApiService.register();需要一個Context引數怎麼辦?你可能會這麼該:


public class UserManager {

    UserStroe mUserStroe;
    ApiService mApiService;

    public UserManager(Context context) {
        mApiService = new ApiService(context);
        mUserStroe = new UserStroe();
    }

    public void register() {
        mApiService.register();
        mUserStroe.login();
    }
}

呼叫處new UserManager(this);但是你注意看,我們只是需要一個context就要將UserManager和MainActivity中的程式碼都要改,要是在實際專案中,需要改更多的引數呢?所以我們來看下用Dagger2該如何解決。這裡我把需求再複雜話一些。首先我們還是先從module來看:
這裡我們將例子複雜一下假設ApiService需要一個context,userStroe需要一個url。我們就可以這樣寫

@Module
public class UserModule {

    Context context;

    public UserModule(Context context) {
        this.context = context;
    }

    @Provides
    public ApiService provideApiService() {
        return new ApiService(context);
    }
    @Provides
    public String providerUrl() {
        return "www.baidu.com";
    }

    @Provides
    public UserManager provideUserManager(ApiService apiService, UserStroe userStroe) {
        return new UserManager(userStroe, apiService);
    }
}
public class UserManager {
    UserStroe mUserStroe;
    ApiService mApiService;

    public UserManager(UserStroe mUserStroe, ApiService mApiService) {
        this.mUserStroe = mUserStroe;
        this.mApiService = mApiService;
    }

    public void register() {
        mApiService.register();
        mUserStroe.login();
    }
}

 public class UserStroe {

    @Inject
    public UserStroe(String url) {
        Log.d("TAG", "UserStroe: " + url);
    }

    public void login() {
        Log.d("TAG", "UserStroe: ");
    }
}  

public class MainActivity extends AppCompatActivity {


    @Inject
    UserManager mManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerUserComponet.builder().userModule(new UserModule(this)).build().inject(this);
        mManager.register();
        Log.d("TAG", "onCreate: " + mManager);
    }
}

這裡我們建立DaggerUserComponet用了方法:

 DaggerUserComponet.builder().userModule(new UserModule(this)).build().inject(this);

這個方法與之前用的其實內部都是一樣的,因為我們UserModule需要context引數,所以我們選擇這種構建方法

可以看到在建立物件時我們可以在module中像之前一樣提供一個建立的方法,第二種我們也可以通過在物件的建構函式上加上@Inject註解,這裡我們需要一個url和context,我們只需要提供一個建立String的url方法即可,並通過UserModule的建構函式將conetxnt傳入,最後提供一個建立UserManager的方法將兩個引數穿進去,最後執行一下:

dagger2 D/TAG: UserStroe: www.baidu.com
com.example.ggxiaozhi.dagger2 D/TAG: ApiService: 
com.example.ggxiaozhi.dagger2 D/TAG: UserStroe: 
com.example.ggxiaozhi.dagger2 D/TAG: onCreate: com.example.ggxiaozhi.dagger2.UserManager@4a7c6f00

可以看到我們修改了這麼多,又需要URL,又需要context但是我們建立UserManager和呼叫方法沒變,因為我們只是需要UserManager中的方法,裡面你需要什麼,怎麼實現的對MainActivity它並不關心。所以這樣就大大做到了解耦的目的。其實使用Dagger2就說白了就是呼叫者需要什麼例項我就在module中建立什麼例項,如果這個例項需要引數,需要什麼引數,我就在mudule給你建立你想要的引數。

實踐(高階用法)

上面在學習了Dagger2的基本用法後下面我們來看下它的高階用法

註解

  • @Qualifier: 要作用是用來區分不同物件例項
    @Named 其實是@Qualifier的一種實現
  • Scope
  • Subcomponent
  • Lazy與Provider

@Qualifier

上面也提到了,作用是用來區分不同物件的例項的。平時我們可能會對一個類建立不同的構造方法以滿足不同的需求,假設現在現在ApiSevice有2個構造方法,根據不同情況呼叫不同方法。這時就要用到@Named標籤(@Named是@Qualifier的一種實現).

public class ApiService {

    public ApiService(Context context) {

    }

    public ApiService(String url) {
        Log.d("TAG", "ApiService: " + url);
    }

    public void register() {
        Log.d("TAG", "ApiService: ");
    }

}

可以看到ApiService有2個不同的構造方法,並且引數不同。下面我們就要在UserModule分別建立這兩個物件的例項。

@Module
public class UserModule {

    Context context;

    public UserModule(Context context) {
        this.context = context;
    }

    @Provides
    @Named("dev")
    public ApiService provideApiServiceDev(String url) {
        ApiService apiService = new ApiService(url);
        Log.d("TAG", "provideApiServiceDev: " + apiService);
        return apiService;
    }

    @Provides
    @Named("release")
    public ApiService provideApiServiceRelease() {
        ApiService apiService = new ApiService(context);
        Log.d("TAG", "provideApiServiceRelease: " + apiService);

        return apiService;
    }

    @Provides
    public Context provideContext() {
        return context;
    }

    @Provides
    public String providerUrl() {
        return "www.baidu.com";
    }


    @Provides
    public UserManager provideUserManager(ApiService apiService, UserStroe userStroe) {
        return new UserManager(userStroe, apiService);
    }
}

可以看到我們為ApiService分別提供了2個構造方法但引數不同。然後分別用@Named(“dev”)和@Named(“release”)註解,表明這是2個不同的構造方法。

(提問:這裡為什麼我們可以直接引用引數引數中的context和url呢?因為我們提供了providerUrl()和provideContext()所以可以直接使用)

那麼我們看下MainActivity中如何呼叫的:

    @Named("dev")
    @Inject
    ApiService mApiService;

    @Named("release")
    @Inject
    ApiService mApiService1;
    private boolean is_Dev = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerUserComponet.builder().userModule(new UserModule(this)).build().inject(this);
        Log.d("TAG", "mApiService= " + mApiService);
        Log.d("TAG", "mApiService1= " + mApiService1);
        if (is_Dev) {
            mApiService.register();
        } else {
            mApiService.register();
        }
    }
}

可以看到我們在modle中用@Named區分,在使用是隻需在 @Inject時新增上@Named註解就會建立對應註解的例項,然後我們用一個is_Dev標籤,表明不同情況使用不同的物件。我們來看下列印結果:

 D/TAG: ApiService: www.baidu.com
 D/TAG: provideApiServiceDev: [email protected]
 D/TAG: provideApiServiceRelease: [email protected]
 D/TAG: mApiService= [email protected]
 D/TAG: mApiService= [email protected]
注意:我們在Moudle用了@Named標籤,在呼叫時也需要加上@Named標籤,如果在呼叫處不使用@Named註解就需要在Moudle中建立對應沒有用@Named註解的例項方法

通過字串標記一個物件,容易導致前後不匹配,所以除了使用這種方法,我們還可以通過自定義註解的方式。
那麼如何實現自定義註解@Qualifier呢?很簡單,@Named就是@Qualifier的一種實現,我看他是怎麼實現的,我們就照葫蘆畫瓢被:

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {

    /** The name. */
    String value() default "";
}
  • @Qualifier :註明是Qualifier(關鍵詞)

  • @Documented :標記在文件

  • @Retention(RUNTIME) :執行時級別
    可以看到@Qualifier決定它關鍵性的作用是用來區分不同物件的。裡面的String型別就是我們之前填寫的(“dev”)和(“release”)用來區分的,那麼我也照著寫我們的自定義的標籤:

@Qualifier
@Retention(RUNTIME)
public @interface Dev {

}

@Qualifier
@Retention(RUNTIME)
public @interface Release {

}

我們建立了2個自定義註解,當然你全賦值過來也是沒有問題的,這裡我去掉了不必要的部分。因為本身我們就不像用字串區分了,所以我把字串引數去掉了。


  @Provides
    @Release
    public ApiService provideApiServiceDev(String url) {
        ApiService apiService = new ApiService(url);
        Log.d("TAG", "provideApiServiceDev: " + apiService);
        return apiService;
    }

    @Provides
    @Dev
    public ApiService provideApiServiceRelease() {
        ApiService apiService = new ApiService(context);
        Log.d("TAG", "provideApiServiceRelease: " + apiService);
        return apiService;
    }  
    @Dev
    @Inject
    ApiService mApiService;

    @Release
    @Inject
    ApiService mApiService1;

用法和上面是結果是一樣的。

@Singleton

我們先看下他的裡面是什麼樣子的:

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}
友情提示:我剛學習的時候就總搞不懂總以為@Scope,@Singleton,@Qualifier,@Named是4個不同作用的操作符,其實他就是兩兩一對的,@Named是@Qualifier具體實現,@Singleton是@Scope的具體實現;@Scope和@Qualifier類似不同作用註解的關鍵字

他就是用@Scope註解修飾的註解。這樣我們就知道了。那麼首先我們來看下,如果不用單例什麼樣的:

    @Inject
    ApiService mApiService1;

    @Inject
    ApiService mApiService2;
    private boolean is_Dev = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerUserComponet.builder().userModule(new UserModule(this)).build().inject(this);

        Log.d("TAG", "mApiService1= " + mApiService1);
        Log.d("TAG", "mApiService2= " + mApiService2);
    }
}

結果:

D/TAG: mApiService1= [email protected]
D/TAG: mApiService2= [email protected]

可以看到確實建立了2個例項,那麼如何使用單例註解:
首先在Module中將建立例項的方法加上@Singleton

@Singleton
@Provides
public ApiService provideApiService() {
     ApiService apiService = new ApiService(context);
     Log.d("TAG", "provideApiService: " + apiService);
     return apiService;
    }    

然後在@Component中也加上@Singleton:

@Singleton
@Component(modules = {UserModule.class})
public interface UserComponet {

    void inject(MainActivity activity);
}

@Singleton在使用時呼叫處正常書寫:

@Inject
ApiService mApiService1;

@Inject
ApiService mApiService2;

列印結果:

provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7c5200   
mApiService1= com.example.ggxiaozhi.dagger2.ApiService@4a7c5200
mApiService2= com.example.ggxiaozhi.dagger2.ApiService@4a7c5200 

可以看到只調用了一次建立物件的方法。2個物件其實是一個例項。

注意:
  • module 的 provide 方法使用了 scope ,那麼 component 就必須使用同一個註解
  • @Singleton 的生命週期依附於component,同一個module被不同的@Component依賴結果也不一樣
    [email protected]分為Activity級別單例生命週期和全域性的生命週期單例
    這裡第一點注意我們通過上面的事例比較容易理解,那麼第二點是什麼意思呢?這句話的意思在於@Singleton 的生命週期依附於component。那麼實際測試下。我們在建立一個LoginAcyivity。並建立LogingConponent如下:
@Singleton
@Component(modules = UserModule.class)
public interface LoginComponent {
    void inject(LoginActivity activity);
}

LogingConponent也依賴UserModule.class。然後在LoginAcyivity中建立ApiService如下:

public class LoginActivity extends AppCompatActivity {

    @Inject
    ApiService mApiService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        DaggerLoginComponent.builder().userModule(new UserModule(this)).build().inject(this);
        Log.d("TAG", "LoginActivity-->mApiService : "+mApiService);
    }
}

執行結果:

 D/TAG: provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7c5994
 D/TAG: MainActivity-->mApiService1= com.example.ggxiaozhi.dagger2.ApiService@4a7c5994
 D/TAG: MainActivity-->mApiService2= com.example.ggxiaozhi.dagger2.ApiService@4a7c5994
 D/TAG: provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7d54f8
 D/TAG: LoginActivity-->mApiService : com.example.ggxiaozhi.dagger2.ApiService@4a7d54f8

可以看你到LoginComponent和UserComponent都依賴UserMoudle,並且建立ApiService已經加了@Singleton註解但是在MainActivity中是單例但是在LoginActivity又建立了不同的ApiService的例項,這就是上面提到的因為LoginComponent和UserComponent為兩個不同的@Component,@Singleton的生命週期依附於component,同一個module provide singleton ,不同component 也是不一樣。所以會看到這樣的結果。如果我們修改下程式碼呢?如下:

@Singleton
@Component(modules = {UserModule.class})
public interface UserComponet {

    void inject(MainActivity activity);
    void inject(LoginActivity activity);
}

然後在LoginActivity中也引用UserComponent而不去引用LogingComponent呢?

public class LoginActivity extends AppCompatActivity {

    @Inject
    ApiService mApiService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        UserComponet userComponet = DaggerUserComponet.builder().userModule(new UserModule(this)).build();
        userComponet.inject(this);
        Log.d("TAG", "LoginActivity-->mApiService : "+mApiService);
        Log.d("TAG", "LoginActivity-->UserComponet : "+userComponet);
    }
}

這也也是不行的。為什麼?看列印結果:

  D/TAG: provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7c454c
 D/TAG: MainActivity-->mApiService1= com.example.ggxiaozhi.dagger2.ApiService@4a7c454c
 D/TAG: MainActivity-->mApiService2= com.example.ggxiaozhi.dagger2.ApiService@4a7c454c
 D/TAG: MainActivity-->UserComponet= com.example.ggxiaozhi.dagger2.DaggerUserComponet@4a7c382c
 D/TAG: provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7d3ccc
 D/TAG: LoginActivity-->mApiService : com.example.ggxiaozhi.dagger2.ApiService@4a7d3ccc
 D/TAG: LoginActivity-->UserComponet : com.example.ggxiaozhi.dagger2.DaggerUserComponet@4a7d3c9c

可以看到,在UserComponet在LoginActivity和MainActivity中會建立2個不同的例項,當然會建立2個不同的mApiService了。如果像實現全域性單例我就就要用到自定義@Scope註解。

自定義@Scope註解

上面是屬於Activity生命週期單例。下面我們就建立全域性生命週期單例。

1. 建立全域性AppModule:
@Module
public class AppMoudle {
    private MyApplication context;

    public AppMoudle(MyApplication context) {
        this.context = context;
    }

    @Singleton
    @Provides
    public ApiService provideApiService() {
        ApiService apiService = new ApiService(context);
        Log.d("TAG", "provideApiService: " + apiService);
        return apiService;
    }
}
2. 建立全域性AppComponent:
@Singleton
@Component(modules = AppMoudle.class)
public interface AppComponent {

    /**
     * 全域性單例。所以不用Inject Activity
     *
     * @return 向下返回ApiService例項
     */
    ApiService getApiService();
}
3. 在MyApplication例項化AppComponent:

單例的依託於他所在的Component中,所以需要在Application中進行例項化。

public class MyApplication extends Application {

    private AppComponent mAppComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mAppComponent = DaggerAppComponent.builder().appMoudle(new AppMoudle(this)).build();
    }

    public AppComponent getAppComponent() {
        return mAppComponent;
    }
}
4.自定義@Scope:
@Scope
@Retention(RUNTIME)
public @interface PerActivity {
}
5. 讓其他Component依賴:
@PerActivity
@Component(modules = {UserModule.class},dependencies = AppComponent.class)
public interface UserComponet {

    void inject(MainActivity activity);
}
@PerActivity
@Component(modules = UserModule.class,dependencies = AppComponent.class)
public interface LoginComponent {
    void inject(LoginActivity activity);
}
6. 呼叫

MainActivity:

DaggerUserComponet.builder().
                appComponent(((MyApplication)getApplicationContext()).getAppComponent()).
                build().inject(this);

LoginActivity:

DaggerLoginComponent.builder().
                 appComponent(((MyApplication)getApplicationContext()).getAppComponent()).
                 build().inject(this);
列印結果:
 D/TAG: provideApiService: com.example.ggxiaozhi.dagger2.ApiService@4a7c3e8c
 D/TAG: MainActivity-->mApiService1= com.example.ggxiaozhi.dagger2.ApiService@4a7c3e8c
 D/TAG: MainActivity-->mApiService2= com.example.ggxiaozhi.dagger2.ApiService@4a7c3e8c
 D/TAG: LoginActivity-->mApiService= com.example.ggxiaozhi.dagger2.ApiService@4a7c3e8c

可以看到這次全域性都是用的一個單例了。

注意:
  • 可以看到第4步我們自定義@Scope註解PerActivity,因為component的dependencies與component自身的scope不能相同,即元件之間的scope不同。所以我們自己定義。
  • Singleton的元件不能依賴其他scope的元件,只能其他scope的元件依賴Singleton的元件 如下:
    AppComponent已經用@Singleton修飾就不能再去依賴(dependencies=XXX.class)別的Component。
    clipboard.png
  • 但是其他scope的元件 可以依賴其他元件:
    clipboardd.png

@Subcomponent

作用有些類似Component中的dependencies作用。特點:
1. Subcomponent同時具備兩種不同生命週期的scope, SubComponent具備了父Component擁有的Scope,也具備了自己的Scope。
2. SubComponent的Scope範圍小於父Component

我們用程式碼使用體會下:
FComponent

//第一步
@Module
public class FModule {

    @Singleton
    @Provides
    public User provideUser() {
        return new User();
    }
}
//第二步
@Singleton
@Component(modules = FModule.class)
public interface FComponent {
  //需要將SubComponent 追加到被依賴的Component中
    CComponent addCComponent();
}

//第三步
public class MyApplication extends Application {

    private AppComponent mAppComponent;

    private FComponent mFComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        mAppComponent = DaggerAppComponent.builder().appMoudle(new AppMoudle(this)).build();
        mFComponent = DaggerFComponent.builder().build();
    }

    public AppComponent getAppComponent() {
        return mAppComponent;
    }

    public FComponent getFComponent() {
        return mFComponent;
    }
}

CComponent:

@Module
public class CModule {

    @PerActivity
    @Provides
    public UserStroe provideUser(User user) {
        return new UserStroe(user);
    }
}

@PerActivity
@Subcomponent(modules = CModule.class)
public interface CComponent {
    void Inject(Main2Activity activity);
}  

呼叫:

public class Main2Activity extends AppCompatActivity {
    private static final String TAG = "Main2Activity";
    @Inject
    UserStroe mUserStroe;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        ((MyApplication)getApplicationContext()).getFComponent().getCComponent().Inject(this);
        Log.d(TAG, "onCreate: "+mUserStroe);
    }
}

首先我們先建立FComponent,他屬於App級別的。我們在MyApplication建立它。FComponent中呼叫者提供CComponent。然後有CComponent。這和我們之前使用有些不同。之前我們都是通過Avtivity級別建立,然後填入App級別的引數。這個使用正好相反。優勢:

不需要在被依賴的Component顯示提供依賴
不需要使用更多的DaggerXXXXComponent物件來建立依賴,僅需要在被依賴Component中增加 XXXComponent addXXComponent(XXXModule) 方法

這個如果不太理解也沒有關係,會使用就行。

懶載入Lazy和強制重新載入Provider

這個比較簡單,我就列舉一個簡單的例子。

public class Container{
    @Inject Lazy<User> lazyUser; //注入Lazy元素
    @Inject Provider<User> providerUser; //注入Provider元素
    public void init(){
        DaggerComponent.create().inject(this);
        User user1=lazyUser.get();  
//在這時才建立user1,以後每次呼叫get會得到同一個user1物件

        User user2=providerUser.get(); 
//在這時建立user2,以後每次呼叫get會再強制呼叫Module的Provides方法一次,
//根據Provides方法具體實現的不同,可能返回跟user2是同一個物件,也可能不是。
    }
}

注意事項(重要)分析

  1. componet 的 inject 方法接收父型別引數,而呼叫時傳入的是子型別物件則無法注入
  2. component關聯的modules中不能有重複的provide
  3. module 的 provide 方法使用了 scope ,那麼 component 就必須使用同一個註解
  4. module 的 provide 方法沒有使用 scope ,那麼 component 和 module 是否加註解都無關緊要,可以通過編譯
  5. component的dependencies與component自身的scope不能相同,即元件之間的scope不同
  6. Singleton的元件不能依賴其他scope的元件,只能其他scope的元件依賴Singleton的元件
  7. 沒有scope的component不能依賴有scope的component
  8. 一個component不能同時有多個scope(Subcomponent除外)
  9. @Singleton 的生命週期依附於component,同一個module provide singleton ,不同component 也是不一樣
  10. Component注入的Activity 在其他Component中不能再去注入
  11. dagger2是跟著生命週期的繫結Activity(Fragment)onDestory 物件也會銷燬
  12. 建立例項的方法和引用例項都不能用private修飾
剛開始使用一定總會遇到很多錯誤,遇到錯誤不要著急。如果注意事項中的錯誤沒有犯的話一定會減少很多錯誤。

結語

不知不覺寫的有點多。對我這種菜鳥,看原始碼寫部落格。真的很費心,學過的技術忘的很快,很多東西理解不透徹,想把部落格寫好寫深還是很有難度的。不過如果你看到了這篇文章,希望有錯誤很問題請留言一起探討。Dagger2也是在我用MVP構建專案時候使用的,可能學習的不是很深入。不過相信把這兩篇文章的程式碼敲一邊。平常的使用一定沒有問題的。最後希望大家一起加油!!!