Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 最簡單的Dagger2入門教程

最簡單的Dagger2入門教程

編輯:關於Android編程

依賴注入就是將調用者需要的另一個對象實例不在調用者內部實現,而是通過一定的方式從外部傳入實例,解決了各個類之間的耦合。

那麼這個外部,到底指的是哪裡,如果指的是另一個類,那麼,另一個類內部不就耦合了。能不能有一種方式,將這些構造的對象放到一個容器中,具體需要哪個實例時,就從這個容器中取就行了。那麼,類的實例和使用就不在有聯系了,而是通過一個容器將他們聯系起來。實現了解耦。這個容器,便是Dagger2

Dagger2是Google出的依賴注入框架。肯定有小伙伴疑問,為什麼會有個 2 呢。該框架是基於square開發的dagger基礎上開發的。

Dagger2的原理是在編譯期生成相應的依賴注入代碼。這也是和其他依賴注入框架不同的地方,其他框架是在運行時期反射獲取注解內容,影響了運行效率。

導入Dagger2

使用Dagger2之前需要一些配置,該配置是在Android Studio中進行操作。

在工程的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-aptGradle編譯器的插件,根據其官方文檔,主要兩個目的:

編譯時使用該工具,最終打包時不會將該插件打入到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 的大小。

Dagger2的簡單使用

Dagger2的使用,需要大量的學習成本,不是很能夠容易的上手並使用。該博客將從簡單入手,盡可能的使用簡單的例子演示Dagger2的功能。

一個東西需要先會用,然後才更好的學習原理。該篇博客的目的主要是講解如何使用。後面會有專門的分析源碼的博客。

在之前的分析中,通過Dagger2的目的是將程序分為三個部分。
- 實例化部分:對象的實例化。類似於容器,將類的實例放在容器裡。
- 調用者:需要實例化對象的類。
- 溝通橋梁:利用Dagger2中的一些API 將兩者聯系。

先看實例化部分(容器),在此處是Module


@Module   //提供依賴對象的實例
public class MainModule {

    @Provides // 關鍵字,標明該方法提供依賴對象
    Person providerPerson(){
        //提供Person對象
        return new Person();
    }


}

溝通部分Component

@Component(modules = MainModule.class)  // 作為橋梁,溝通調用者和依賴對象庫
public interface MainComponent {

    //定義注入的方法
    void inject(MainActivity activity);

}

使用者Actvity中調用。

public class MainActivity extends AppCompatActivity{

    @Inject   //標明需要注入的對象
    Person person;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 構造橋梁對象
        MainComponent component = DaggerMainComponent.builder().mainModule(new MainModule()).build();

        //注入
        component.inject(this);

    }
}

看一下Person

public class Person {

    public Person(){
        Log.i("dagger","person create!!!");
    }


}

最後結果不在演示。其過程如下:

創建Component(橋梁),並調用注入方法。
        // 構造橋梁對象
        MainComponent component = DaggerMainComponent.builder().mainModule(new MainModule()).build();

        //注入
        component.inject(this);
查找當前類中帶有@Inject的成員變量。
    @Inject   //標明需要注入的對象
    Person person;
根據成員變量的類型從Module中查找哪個有@Provides注解的方法返回值為當前類型。
    @Provides // 關鍵字,標明該方法提供依賴對象
    Person providerPerson(){
        //提供Person對象
        return new Person();
    }

這裡寫圖片描述

在使用過程出現了很多注解:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxjb2RlPkBNb2R1bGU8L2NvZGU+Otf3zqrKtcD9ttTP87XEyN3G96GjIDxjb2RlPkBQcm92aWRlczwvY29kZT46serXosTcubvM4bmpyrXA/buvttTP87XEt723qKGjIDxjb2RlPkBDb21wb25lbnQ8L2NvZGU+Otf3zqrHxcG6o6zXosjrttTP87XEzai1wKGjIDxjb2RlPkBJbmplY3Q8L2NvZGU+o7rQ6NKq16LI67XEt723qA0KPHA+yOfJz8q508PT0NK71tax5M2oo6zQ3rjEPGNvZGU+TWFpbk1vZHVsZTwvY29kZT66zTxjb2RlPlBlcnNvbjwvY29kZT7A4KGjPC9wPg0KPHByZSBjbGFzcz0="brush:java;"> @Module //提供依賴對象的實例 public class MainModule { /* @Provides // 關鍵字,標明該方法提供依賴對象 Person providerPerson(){ //提供Person對象 Log.i("dagger"," from Module"); return new Person(); } */ }

public class Person {

    @Inject  // 添加注解關鍵字
    public Person(){
        Log.i("dagger","person create!!!");
    }

}

Module中的providePerson()方法注釋,在Person中添加@Inject注解,依然能夠實現。

邏輯如下:
- 先判斷Module中是否有提供該對象實例化的方法。
- 如果有則返回。結束。
- 如果沒有,則查找該類的構造方法,是否有帶有@Inject的方法。如過存在,則返回。

@Singleton 單例注解

假如,對於同一個對象,我們需要注入兩次,如下方式

 public class MainActivity extends AppCompatActivity{

    @Inject
    Person person;

    @Inject
    Person person2;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 構造橋梁對象
        MainComponent component = DaggerMainComponent.builder().mainModule(new MainModule()).build();

        //注入
        component.inject(this);

        // 打印兩個對象的地址
        Log.i("dagger","person = "+ person.toString()+"; person2 = "+ person2.toString());
    }
}

看一下結果:

person = com.mahao.alex.architecture.dagger2.Person@430d1620; person2 = com.mahao.alex.architecture.dagger2.Person@430d17c8

可見兩個對象不一致。也就是說創建了兩個對象。

可以在提供實例化對象的方法上添加@Singleton注解

 @Provides // 關鍵字,標明該方法提供依賴對象
    @Singleton
    Person providerPerson(){

        return new Person();
    }

同時,對於MainComponent也需要添加注解,不添加會無法編譯

@Singleton
@Component(modules = MainModule.class)  // 作為橋梁,溝通調用者和依賴對象庫
public interface MainComponent {
    //定義注入的方法
    void inject(MainActivity activity);

}

此時在Log,會發現兩個對象的地址一樣,可見是同一個對象。

person = com.mahao.alex.architecture.dagger2.Person@4310f898; person2 = com.mahao.alex.architecture.dagger2.Person@4310f898

那麼不同的Activity之間,能否保持單例呢?

創建一個新的Activity,代碼如下:

public class Main2Actvity extends AppCompatActivity {

    @Inject
    Person person;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 構造橋梁對象
        MainComponent component = DaggerMainComponent.builder().mainModule(new MainModule()).build();

        //注入
        component.inject(this);

        Log.i("dagger","person = "+ person.toString());
    }
}

結果如下:

 person create!!!
 person = com.mahao.alex.architecture.dagger2.Person@4310f898; person2 = com.mahao.alex.architecture.dagger2.Person@4310f898
 person create!!!
 person = com.mahao.alex.architecture.dagger2.Person@43130058

可見,@Singleton只對一個Component有效,即其單例所依賴Component對象。

需要參數的實例化對象

Person的構造方法發生了變化,需要傳入一個Context,代碼如下:

public class Person {

    private Context mContext;

    public Person(Context context){
        mContext = context;
        Log.i("dagger","create");
    }

}

這樣的話,我們需要修改MainModule


@Module   //提供依賴對象的實例
public class MainModule {

    private Context mContext;

    public MainModule(Context context){
        mContext = context;
    }


    @Provides
    Context providesContext(){
        // 提供上下文對象
        return mContext;
    }

    @Provides // 關鍵字,標明該方法提供依賴對象
    @Singleton
    Person providerPerson(Context context){

        return new Person(context);
    }

}
修改providerPerson方法,傳入Context對象。 添加providesContext(),用以提供Context對象。

看一下使用

 // 構造橋梁對象
        MainComponent component = DaggerMainComponent.builder().mainModule(new MainModule(this)).build();

        //注入
        component.inject(this);

邏輯:

根據@Inject注解,查找需要依賴注入的對象。 從MainModule中根據返回值,找到providerPerson(Context context)對象。 發現其需要傳入參數Context,找到moudule中具有返回值為Context的方法providesContext()。 最後就成功的構建了實例化對象。

可能會有疑問,我既然module中已經保存了Context對象,那麼為什麼不直接使用Context對象呢,因為解耦,如果使用了保存的對象,會導致下次Context獲取發生變化時,需要修改providerPerson(Context context)中的代碼。

在編寫Module中,不能出現傳入參數和返回參數一致的情況,會導致死循環。

很容易理解,需要的和獲取的是同一個方法,循環調用。

依賴一個組件

在使用中,往往會有依賴另一個組件的情況。比如,在AppMoudle中能夠提供Context對象,如下:

@Module
public class AppModule {

    private Context mContext;

    public AppModule(Context context){
        mContext = context;
    }

    @Provides
    Context providesContext(){
        // 提供Context對象 
        return mContext;
    }

}

而在另一個Module中需要依賴Context對象,那麼怎麼寫呢?

首先編寫當前AppModuleComponent

/**
 *
 * 全局的Component 組件
 * Created by MH on 2016/7/18.
 */

@Component(modules = AppModule.class)
public interface AppComponent {

    // 向其下層提供Context 對象
    Context proContext();
}

在此種,因為Module中需要向下層提供Context對象,而其與下層的聯系時通過Component
,所以需要在這裡聲明一個其所提供對象的方法。以便下層Module獲取。

/**
 *
 * 下層Module類
 * Created by MH on 2016/7/18.
 */
@Module
public class ActivityMoudule {

    @Provides
    Person providePerson(Context context){
        // 此方法需要Context 對象
        return new Person(context);
    }
}

/**
 *  子的Component
 * Created by MH on 2016/7/18.
 */
@Component(dependencies = AppComponent.class,modules = ActivityMoudule.class)
public interface ActivityComponent {

    // 注入
    void inject(MainActivity activity);
}

在子Component中,有一句關鍵的注解dependencies = AppComponent.class,添加了上層依賴。

看一下使用

        // 依賴對象 Component
        AppComponent appCom = DaggerAppComponent.builder().appModule(new AppModule(this)).build();

        // 子類依賴對象 ,並注入
        DaggerActivityComponent.builder()
                .appComponent(appCom)
                .activityMoudule(new ActivityMoudule())
                .build()
                .inject(this);

在其中使用過程中,有很重的兩點。

父依賴的Component中需要添加提供對象的接口。 子依賴的Component中的注解中添加dependencies = AppComponent.class

@Qualifier 自定義標記

在使用中,會出現兩個方法返回對象相同時的情況,那麼如何區分呢。

Person對象具有兩個構造方法,根據不同的參數值構造不同的方法。

public class Person {

    private Context mContext;

    public Person(Context context){
        mContext = context;
        Log.i("dagger","create");
    }

    public Person(String name){
        Log.i("dagger",name);
    }
}

ActivityModule中添加@Named標記

@Module
public class ActivityMoudule {

    @Named("Context")  // 通過context創建Person 對象
    @Provides
    Person providePersonContext(Context context){
        // 此方法需要Context 對象
        return new Person(context);
    }


    @Named("name")  // 通過name創建Person 對象
    @Provides
    Person providePersonName(){
        // 此方法需要name
        return new Person("1234");
    }
}

使用時,也需要添加此標記

    public class MainActivity extends AppCompatActivity{

    @Named("context") // 標記
    @Inject
    Person person;

    @Named("name")  // 標記
    @Inject
    Person person2;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        //注入
        component.inject(this);*/

        // 依賴對象 Component
        AppComponent appCom = DaggerAppComponent.builder().appModule(new AppModule(this)).build();

        // 子類依賴對象 ,並注入
        DaggerActivityComponent.builder()
                .appComponent(appCom)
                .activityMoudule(new ActivityMoudule())
                .build()
                .inject(this);
    }

    }

使用時,使用者的@Inject上,必須要加入注解@Named("xxx"),不然編譯期會報錯。

這樣使用過程中,雖然解決了問題,但是通過字符串標記一個對象,容易導致前後不匹配,可以通過自定義注解的方式解決。

添加兩個注解,分別對應Contextname

@Qualifier  // 關鍵詞
@Retention(RetentionPolicy.RUNTIME)  // 運行時仍可用
public @interface PersonForContext {
    // Context 對象的注解
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonForName {
    // name 對象的注解
}

在使用@Named("")的地方替換為上面的注解


   @PersonForContext  // 通過context創建Person 對象
    @Provides
    Person providePersonContext(Context context){
        // 此方法需要Context 對象
        return new Person(context);
    }


    @PersonForName  // 通過name創建Person 對象
    @Provides
    Person providePersonName(){
        // 此方法需要Context 對象
        return new Person("123");
    }

注入時:


    @PersonForContext // 標記
    @Inject
    Person person;


    @PersonForName // 標記
    @Inject
    Person person2;

Scope

在前面中提到@Singleton注解,該注解能夠使同一個Component中的對象保持唯一,即單例。

回憶一下,如下方式:

    @Provides // 關鍵字,標明該方法提供依賴對象
    @Singleton
    Person providerPerson(Context context){

        return new Person(context);
    }

Module中,對應方法中添加@Singleton注解,同時其所在的Component中,類生命上也需要添加注解


@Singleton
@Component(modules = MainModule.class)  // 作為橋梁,溝通調用者和依賴對象庫
public interface MainComponent {
}

如果我們看這個意思,感覺其內部應該做了很多的實現,用以達到單例。其實,沒我們想的那麼復雜。

看一下@Singleton的實現

@Scope //注明是Scope 
@Documented  //標記在文檔 
@Retention(RUNTIME)  // 運行時級別
public @interface Singleton {}

通過@Scope定義的一個新的注解。

在之前的,我們知道該單例是依托於他所在的Component組件。那麼我們是否可以這樣理解,因為方法上添加的@Scope標記的注解和Component上添加的@Scope標記的注解相同(確實相同,同為@Singleton),就表明了該方法提供的實例對象在Component保持唯一。保持唯一的條件是通過@Scope標記的注解相同。

通過在上面的依賴層級上,Android中通常定義兩個生命周期。

全局的生命周期PerApp

/**
 * 全局的生命周期單例
 */
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PerApp {

}

在使用中完全和@Singleton相同。

@Module
public class AppModule {

    private Context mContext;

    public AppModule(Context context){
        mContext = context;
    }

    @Provides
    @PerApp  // 添加該標記表明該方法只產生一個實例
    Context providesContext(){
        // 提供上下文對象
        return mContext;
    }

}
@PerApp // 因為Module 中使用了該標記,所以需要在此添加
@Component(modules = AppModule.class)
public interface AppComponent {

    // 向其下層提供Context 對象
    Context proContext();
}

因為單例的依托於他所在的Component中,所以需要在Application中進行實例化。

public class App extends Application {

    // 為什麼可以使用靜態
    public static AppComponent appComponent;


    @Override
    public void onCreate() {
        super.onCreate();

        // 實例化
        appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();

    }
}

為什麼可以使用靜態的,因為該AppComponent對象的生命周期是整個App。那麼在使用中,其所在Module中的實例化對象,可以保持全局單例。

一個Activity的生命周期PerActivity

有全局的單例,而對於一個Activity,他也有些對象需要保持單例。我們需要定義該注解。

/**
 * Activity 單例生命周期
 */
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {
}

會發現,除了定義名不一樣,其余都和PerApp一樣。在前面,說過這樣一句話:保持唯一的條件是通過@Scope標記的注解相同。

@Module
public class ActivityMoudule {

    @PersonForContext
    @Provides
    @PerActivity  // 添加標記,生命其所構造的對象單例
    Person providePersonContext(Context context){
        // 此方法需要Context 對象
        return new Person(context);
    }

    .....
}

@PerActivity  // ActivityMoudule 中使用了該標記
@Component(dependencies = AppComponent.class,modules = ActivityMoudule.class)
public interface ActivityComponent {

    // 注入
    void inject(MainActivity activity);
}

使用方式,因為其所保持的單例是在Activity中,具體使用如下。

public class MainActivity extends AppCompatActivity{

    @PersonForContext // 標記
    @Inject
    Person person;


    @PersonForName // 標記
    @Inject
    Person person2;


    /**
     * 不使用靜態的,因為該Component只是針對於該Activity,而不是全局的
     */
    ActivityComponent  activityComponent;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);


        activityComponent = DaggerActivityComponent.builder()
                .appComponent(App.appComponent)  // 添加了全局的AppComponent組件,可以使用全局的實例化對象
                .activityMoudule(new ActivityMoudule())
                .build();


        activityComponent.inject(this);

對於具有依賴關系的Component,不能使用相同的Scope,如果使用相同的會帶來語意不明

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

public class MainActivity extends AppCompatActivity{

    @PersonForContext // 標記
    @Inject
    Lazy lazyPerson; // 注入Lazy元素


    @PersonForName // 標記
    @Inject
    Provider providerPerson; // 注入Provider


    /**
     * 不使用靜態的,因為該Component只是針對於該Activity,而不是全局的
     */
    ActivityComponent  activityComponent;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);


        activityComponent = DaggerActivityComponent.builder()
                .appComponent(App.appComponent)  // 添加了全局的AppComponent組件
                .activityMoudule(new ActivityMoudule())
                .build();


        activityComponent.inject(this);


        Person person = lazyPerson.get();// 調用該方法時才會去創建Person,以後每次調用獲取的是同一個對象


        // 調用該方法時才回去創建Person1,以後每次調用都會重新加載Module中的具體方法,根據Module中的實現,可能相同,可能不相同。
        Person person1 = providerPerson.get();
    }
}

該博客中使用的代碼已經上傳到github,有需要者請移步。https://github.com/AlexSmille/alex_mahao_sample/tree/master/architecture

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved