Dagger2入門詳解

Dagger2入門詳解

參考文章

Dagger官網

Dagger Document API

從零開始的Android新項目4

http://www.bozhiyue.com/anroid/boke/2016/0719/273761.html


環境配置

這里以Gradle配置為例子,實用得是AndroidStudio:

  1. 打開project 的 build.gradle ,添加
dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
        
        //dagger2
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
  1. 打開 app module 的 build.gradle,添加
apply plugin: 'com.android.application'
//dagger2
apply plugin: 'com.neenbedankt.android-apt'

//...

dependencies {
    //...

    //dagger2
    compile 'com.google.dagger:dagger:2.4'
    apt 'com.google.dagger:dagger-compiler:2.4'
    compile 'org.glassfish:javax.annotation:10.0-b28'
}
  1. 然后sync gradle一下,環境就配置好了,在實用dagger2的時候,會自動生成一些類,所以最好記一下 build project的快捷鍵 ctrl+F9,寫好dagger代碼,然后build一下,就會自動生成 DaggerXXX 開頭的一些類。

入門實例

好了,下面我們來看一個入門實例,實用Dagger2到底是怎么依賴注入的。

現在又一個Person類(這里為了簡單起見),然后MainActivity中又一個成員變量person。

Person.java

public class Person {

    public Person() {
        System.out.println("a person created");
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {
   
    Person person;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        person = new Person();
    }
}

如果不適用依賴注入,那么我們只能在MainActivity中自己new一個Person對象,然后使用。

使用依賴注入:

public class MainActivity extends AppCompatActivity {

    @Inject
    Person person;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
   }
}

在Person對象上添加一個 @Inject注解,即可自動注入對象。

那么問題來了,就一個@Inject 注解,系統就會自動給我創建一個對象? 當然不是,這個時候我們需要一個Person類的提供者。估計叫它: MainModule

MainModule.java

@Module
public class MainModule {

    @Provides
    Person providesPerson() {
        System.out.println("a person created from MainModule");
        return new Person();
    }
}

里面兩個注解,@Module@Provides,Module標注的對象,你可以把它想象成一個工廠,可以向外提供一些類的對象。那么到底提供什么對象呢?

@Provides標注的方法就是提供對象的,這種方法一般會返回一個對象實例,例如上面返回一個 Person對象

那么好了,現在Perso類的提供者也有了,我們是不是可以運行起來了。ctrol+F9 build一下項目,然后運行。發現沒有任何輸出(如果創建Person對象,會打印消息)。為什么了?

這個時候需要引入第3個東東,component容器??梢园阉氤梢粋€容器, module中產出的東西都放在里面,然后將component與我要注入的MainActivity做關聯,MainActivity中需要的person就可以沖 component中去去取出來。

MainComponent.java

@Component(modules = {MainModule.class})
public interface MainComponent {

    void inject(MainActivity mainActivity);
}

看到一個新注入 @Component 表示這個接口是一個容器,并且與 MainModule.class 關聯,它生產的東西都在這里。
void inject(MainActivity mainActivity); 表示我怎么和要注入的類關聯。這個比較抽象!這個時候我們可以 build 一下項目。

然后在MainActivity中將component 關聯進去:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MainComponent component = DaggerMainComponent.builder()
                .mainModule(new MainModule()).build();
        component.inject(this);
    }

下面就是將 MainActivity和Module通過Component關聯起來的代碼,那么這個時候系統看到 有一個 @Inject修飾的Person,就知道在這個 MainComponent中去找,并且是有 MainModule 的 Provides修飾的方法提供的。

MainComponent component = DaggerMainComponent.builder()
    .mainModule(new MainModule()).build();
component.inject(this);

然后 build 項目,運行項目,發現打?。?/p>

person from module
a person created

說明確實系統創建了對象,并且注入到MainActivity中。

細心的同學會發現,MainComponent, MainModule, MainActivity 都是我們自己創建的,上面還有一個 DaggerMainComponent 是上面鬼?這就是你 build project 的時候,dagger自己為你生成的具體的component類(我們自己定義的是MainComponent接口)。感興趣的可以直接跟到代碼里面去看看。

好了,上面我們把 DI (Dependency Inject) 最基本的流程走了一遍,用到了幾個注解:

  1. @Inject
  2. @Module
  3. @Component
  4. @Provides

下面來介紹另外幾個常用的注解。

其他注解和情況

如果只有最簡單的情況,那么上面的幾個注解已經夠了,但是其實還有很多情形,我們稍微展示幾個

單例模式 @Singleton(基于Component)

基于Component的單例模式,怎么理解呢?也是是 在這個Component 對象中,一個對象是單例對象。如果又新創建了一個Component,那么兩個Component中的當你對象是不一樣的。具體的看后面介紹。

上面的MainActivity代碼不變,我們再在MainActivity中添加一個 @Inejct Person person2,并打印兩個 person對象,結果如下:

person from module
a person created
person from module
a person created
org.yxm.daggerlearn2.data.Person@64cf4a2
org.yxm.daggerlearn2.data.Person@899b533

發現person會被創建兩次,并且兩個person對象也不同,如果我們希望只有一個 person 和 person2 都指向同一個Person對象了? 使用 @Singleton 注解

兩個地方需要加:

  1. MainModule.java 的 provides方法上需要添加 @Singleton 注解
@Module
public class MainModule {

    private static final String TAG = "MainModule";

    @Singleton
    @Provides
    public Person providesPerson() {
        Log.d(TAG, "person from module");
        return new Person();
    }
}
  1. MainComponent.java 類上添加
@Singleton
@Component(modules = {MainModule.class})
public interface MainComponent {

    void inject(MainActivity mainActivity);
}

再運行,發現只創建了一次,并且兩個person指向同一個對象。

person from module
a person created
org.yxm.daggerlearn2.data.Person@64cf4a2
org.yxm.daggerlearn2.data.Person@64cf4a2

需要非常注意的是:單例是基于Component的,所以不僅 Provides 的地方要加 @Singleton,Component上也需要加。并且如果有另外一個OtherActivity,并且創建了一個MainComponent,也注入Person,這個時候 MainActivity和OtherActivity中的Person是不構成單例的,因為它們的Component是不同的。

帶有參數的依賴對象

如果構造Person類,需要一個參數Context,我們怎么注入呢? 要知道注入的時候我們只有一個 @Inject 注解,并不能帶參數。所以我們需要再 MainModule 中提供context,并且由 providesXXX 函數自己去構造。如:

Person.java

public class Person {

    private Context context;

    public Person(Context context) {
        Log.d(TAG, "a person created with context:"+context);
    }
}

修改MainModule.java

@Module
public class MainModule {

    private static final String TAG = "MainModule";

    private Context context;

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

    @Provides
    public Context providesContext() {
        return this.context;
    }

    @Singleton
    @Provides
    public Person providesPerson(Context context) {
        Log.d(TAG, "person from module");
        return new Person(context);
    }
}

這里需要強調的是, providesPerson(Context context)中的 context,不能直接使用 成員變量 this.context,而是要在本類中提供一個 Context providesContext()@Provides 方法,這樣在發現需要 context 的時候會調用 provideContext 來獲取,這也是為了解耦。

依賴一個組件

如果組件之間有依賴,比如 Activity 依賴 Application一樣,Application中的東西,Activity要直接可以注入,怎么實現呢?

例如,現在由 AppModule 提供Context對象, ActivityModule 自己無需提供Context對象,而只需要依賴于 AppModule,然后獲取Context 對象即可。

AppModule.java

@Module
public class AppModule {

    private Context context;

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

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

AppComponent.java

@Component(modules = {AppModule.class})
public interface AppComponent {
    // 向下層提供Context
    Context getContext();
}

ActivityModule.java

@Module
public class ActivityModule {

    @Provides
    Person providePerson(Context context) {
        return new Person(context);
    }
}

ActivityComponent.java

@Component(dependencies = {AppComponent.class}, modules = {ActivityModule.class})
public interface ActivityComponent {

    void inject(MainActivity mainActivity);
}

通過上面例子,我們需要注意:

  1. ActivityModule 也需要創建Person時的Context對象,但是本類中卻沒有 providesContext() 的方法,因為它通過 ActivityComponent依賴于 AppComponent,所以可以通過 AppComponent中的 providesContext() 方法獲取到Context對象。
  2. AppComponent中必須提供 Context getContext(); 這樣返回值是 Context 對象的方法接口,否則ActivityModule中無法獲取。

使用方法:一定要在 activityComponent中注入 appComponent 這個它依賴的組件。我們可以看到,由于AppComponent沒有直接和 MainActivity發生關系,所以它沒有 void inject(...);這樣的接口

AppComponent appComponent = DaggerAppComponent.builder()
        .appModule(new AppModule(this))
        .build();
ActivityComponent activityComponent = DaggerActivityComponent.builder()
        .appComponent(appComponent)
        .activityModule(new ActivityModule())
        .build();
activityComponent.inject(this);

自定義標記 @Qualifier 和 @Named

如果Person中有兩個構造方法,那么在依賴注入的時候,它怎么知道我該調用哪個構造方法呢?

修改Person類,兩個不同的構造方法
Person.java

public class Person {

    private static final String TAG = "Person";

    private Context context;
    private String name;

    public Person(Context context) {
        Log.d(TAG, "a person created with context:" + context);
    }

    public Person(String name) {
        this.name = name;
        Log.d(TAG, "a person created with name:" + name);
    }
}

有兩種方法可以解決這個問題:

@Named("...")

@Module
public class MainModule {

    private static final String TAG = "MainModule";

    private Context context;

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

    @Provides
    public Context providesContext() {
        return this.context;
    }

    @Named("context")
    @Provides
    public Person providesPersonWithContext(Context context) {
        return new Person(context);
    }


    @Named("string")
    @Provides
    public Person providesPersonWithName() {
        return new Person("yxm");
    }
}

分別在兩個提供Person的provides方法上添加 @Named標簽,并指定。

然后在要依賴注入的地方,同樣添加 @Name 標注表示要注入時使用哪一種

    @Named("string")
    @Inject
    Person p1;

    @Named("context")
    @Inject
    Person p2;

@Qualifier自定義標簽

使用@Named 會使用到 字符串 ,如果兩邊都必須寫對才能成功,并且字符串總是不那么優雅的,容易出錯,所以我們可以自定義標簽來解決上面的問題。

PersonWithContext.java

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonWithContext {
}

PersonWithName.java

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonWithName {
}

這樣就自定義了兩個標簽,然后在剛剛所有使用到 @Named 的地方替換成我們自定義的標簽即可:

MainModule.java

    // ...
    @PersonWithContext
    @Provides
    public Person providesPersonWithContext(Context context) {
        return new Person(context);
    }


    @PersonWithName
    @Provides
    public Person providesPersonWithName() {
        return new Person("yxm");
    }

依賴注入的地方:

    @PersonWithContext
    @Inject
    Person p1;

    @PersonWithContext
    @Inject
    Person p2;

輸入:調用了兩種構造方法

a person created with context:org.yxm.daggerlearn2.MainActivity@8018a6e
a person created with name:yxm

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

在注入時分別使用 Lazy 和 Provider 修飾要注入的對象:

@Inject
Lazy<Person> lazyPerson;

@Inject
Provider<Person> providerPerson;

在使用的地方,用 .get()方法獲?。?/p>

Person p1 = lazyPerson.get();
Person p2 = lazyPerson.get();
Person p3 = providerPerson.get();
Person p4 = providerPerson.get();

打印結果:

a person created with context:org.yxm.daggerlearn2.MainActivity@8018a6e
a person created with name:yxm
a person created with name:yxm

說明 lazyPerson 多次get 的是同一個對象,providerPerson多次get,每次get都會嘗試創建新的對象。

@Scope 自定義生命周期

通過前面的例子,我們遇到了 @Singleton 這個標簽,它可以保證在同一個Component中,一個對象是單例對象。其實可以跟進去看代碼:

Singleton.java

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

就這么多東西。利用單例和組件間依賴的關系,是不是我們也可以定義生命周期來滿足我們的需求呢?比如 Application,Activity 這樣的生命周期。下面我們來創建這兩個生命周期:

Application生命周期
ApplicationScope.java

@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}

Activity生命周期
ActivityScope.java

@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}

除了名字,其他都和 @Singleton 是一樣的。

然后用 ApplicationScope 來修飾 AppModule和AppComponent,ActivityScope 修飾 ActivityModule和ActivityComponent
AppModule.java

@Module
public class AppModule {

    private Context context;

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

    @ApplicationScope
    @Provides
    public Context providesContext() {
        return context;
    }
}

AppComponent.java

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

    // 向下層提供Context
    Context getContext();

}

ActivityModule.java

@Module
public class ActivityModule {

    @ActivityScope
    @PersonWithContext
    @Provides
    Person providesPersonWithContext(Context context) {
        return new Person(context);
    }

    @ActivityScope
    @PersonWithName
    @Provides
    Person providesPersonWithString() {
        return new Person("yxm");
    }
}

ActivityComponent.java

@ActivityScope
@Component(dependencies = {AppComponent.class}, modules = {ActivityModule.class})
public interface ActivityComponent {

    void inject(MainActivity mainActivity);
}

然后創建自定義Application:
App.java

public class App extends Application {

    public static AppComponent appComponent;

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

        appComponent = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .build();
    }
}

在MainActivity中注入:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @PersonWithContext
    @Inject
    Person p1;

    @PersonWithContext
    @Inject
    Person p2;

    @PersonWithName
    @Inject
    Person p3;

    @PersonWithName
    @Inject
    Person p4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        ActivityComponent activityComponent = DaggerActivityComponent.builder()
                .appComponent(App.appComponent)
                .activityModule(new ActivityModule())
                .build();
        activityComponent.inject(this);

        Log.d(TAG, "" + p1);
        Log.d(TAG, "" + p2);
        Log.d(TAG, "" + p3);
        Log.d(TAG, "" + p4);

    }
}

可以看到我們注入了4個Person對象,打印結果:

a person created with context:org.yxm.daggerlearn2.App@103efff
a person created with name:yxm
org.yxm.daggerlearn2.data.Person@711cc
org.yxm.daggerlearn2.data.Person@711cc
org.yxm.daggerlearn2.data.Person@a36ec15
org.yxm.daggerlearn2.data.Person@a36ec15

只創建了兩次,說明在ActivityScope生命周期中,創建了兩種 Person,并且它們保持各自的單例。

其實看到這里,再說單例我都覺得不是很準確了,它其實就是說的在這個Component中可以又幾個這樣的對象。如果是普通的就可能有很多個,如果是Scope修飾的,就只有一個。

總結

哎喲我去,還是寫了很長,這篇還是主要展示了Dagger2中最基本的環境配置,和常用的標簽以及特性,列舉一下:

  1. @Inject
  2. @Module
  3. @Component
  4. @Singleton
  5. @Scope
  6. @Named, @Qualifier
  7. Lazy, Provider
  8. modules, dependencys

然后也踩了一些坑,很無知的錯誤:

  1. 在Component中我們會使用:void inject(實體類),表示這個Component 可以與實體類關聯,然后為它注入。注意這里的實體類必須是直接關聯的那個類,如果你填它的父類,按照多態你也可以在 compoment.inject(...)的時候成功,但是卻無法注入 。糾結了好一會兒。
  2. 一旦使用 compoment.inject(...) 使某個實體類和Component發生了關系,那么對應Component的 Module 中必須提供 @Inject修飾的所有對象的 providesXXX 方法,而且如果有兩種構造方法,必須提供兩種 providesXXX 方法哦!又被坑了好一會兒。
  3. 如果被依賴的 Component 使用了Scope,那么依賴他的 Component 也必須使用Scope才能使用。典型例子就是:AppComponent使用了 Scope,那么ActivityComponent也必須使用Scope,否則會編譯出錯。

下一篇來介紹Android中怎么實際使用Dagger2來達到解耦的目的,敬請期待吧

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容