Dagger2實(shí)戰(zhàn)

當(dāng)前比較流行的Android開發(fā)框架當(dāng)屬M(fèi)VP、RxJava、Retrofit2、Dagger2了,而在這些框架之中,學(xué)習(xí)成本最高也是最難上手的應(yīng)該就是Dagger2了
但是作為程序員這種需要與時(shí)俱進(jìn)的行業(yè),畢竟不進(jìn)步就是落后嘛,了解并掌握Dagger2還是是很有必要的。

Dagger2介紹

1. Dagger2是什么?

Dagger2在Github主頁(yè)上的自我介紹是:“A fast dependency injector for Android and Java“(一個(gè)提供給Android和Java使用的快速依賴注射器。)
Dagger2是由谷歌接手開發(fā),最早的版本Dagger1 是由Square公司開發(fā)的。

2. Dagger2相較于Dagger1的優(yōu)勢(shì)是什么?

更好的性能:相較于Dagger1,它使用的預(yù)編譯期間生成代碼來(lái)完成依賴注入,而不是用的反射。大家知道反射對(duì)手機(jī)應(yīng)用開發(fā)影響是比較大的,因?yàn)榉瓷涫窃诔绦蜻\(yùn)行時(shí)加載類來(lái)進(jìn)行處理所以會(huì)比較耗時(shí),而手機(jī)硬件資源有限,所以相對(duì)來(lái)說會(huì)對(duì)性能產(chǎn)生一定的影響。
容易跟蹤調(diào)試:因?yàn)閐agger2是使用生成代碼來(lái)實(shí)現(xiàn)完整依賴注入,所以完全可以在相關(guān)代碼處下斷點(diǎn)進(jìn)行運(yùn)行調(diào)試。

3. 使用依賴注入的最大好處是什么?

[圖片上傳失敗...(image-186007-1528449355056)]

沒錯(cuò),就是模塊間解耦! 就拿當(dāng)前Android非常流行的開發(fā)模式MVP來(lái)說,使用Dagger2可以將MVP中的V 層與P層進(jìn)一步解耦,這樣便可以提高代碼的健壯性和可維護(hù)性。

[圖片上傳失敗...(image-8adff1-1528449355056)]

如果在 Class A 中,有 Class B 的實(shí)例,則稱 Class A 對(duì) Class B 有一個(gè)依賴。
如果不用Dagger2的情況下我們應(yīng)該這么寫:

public class A {
    ...
    B b;
    ...
    public A() {
        b = new B();
    }
}

上面例子面臨著一個(gè)問題,一旦某一天B的創(chuàng)建方式(如構(gòu)造參數(shù))發(fā)生改變,那么你不但需要修改A中創(chuàng)建B的代碼,還要修改其他所有地方創(chuàng)建B的代碼。
如果我們使用了Dagger2的話,就不需要管這些了,只需要在需要B的地方寫下:

@Inject
B b;

Dagger2使用

上面我們對(duì)Dagger2有了個(gè)初步的了解,接下來(lái)我們來(lái)看看Dagger2的基本使用內(nèi)容。

1. 注解

Dagger2 通過注解來(lái)生成代碼,定義不同的角色,主要的注解如下:
@Module: Module類里面的方法專門提供依賴,所以我們定義一個(gè)類,用@Module注解,這樣Dagger在構(gòu)造類的實(shí)例的時(shí)候,就知道從哪里去找到需要的依賴。
**@Provides: **在Module中,我們定義的方法是用這個(gè)注解,以此來(lái)告訴Dagger我們想要構(gòu)造對(duì)象并提供這些依賴。
**@Inject: **通常在需要依賴的地方使用這個(gè)注解。換句話說,你用它告訴Dagger這個(gè)類或者字段需要依賴注入。這樣,Dagger就會(huì)構(gòu)造一個(gè)這個(gè)類的實(shí)例并滿足他們的依賴。
**@Component: **Component從根本上來(lái)說就是一個(gè)注入器,也可以說是@Inject和@Module的橋梁,它的主要作用就是連接這兩個(gè)部分。將Module中產(chǎn)生的依賴對(duì)象自動(dòng)注入到需要依賴實(shí)例的Container中。
@Scope: Dagger2可以通過自定義注解限定注解作用域,來(lái)管理每個(gè)對(duì)象實(shí)例的生命周期。
**@Qualifier: **當(dāng)類的類型不足以鑒別一個(gè)依賴的時(shí)候,我們就可以使用這個(gè)注解標(biāo)示。例如:在Android中,我們會(huì)需要不同類型的context,所以我們就可以定義 qualifier注解“@perApp”和“@perActivity”,這樣當(dāng)注入一個(gè)context的時(shí)候,我們就可以告訴 Dagger我們想要哪種類型的context。

2. 結(jié)構(gòu)

Dagger2要實(shí)現(xiàn)一個(gè)完整的依賴注入,必不可少的元素有三種:Module,Component,Container。

[圖片上傳失敗...(image-ab1fa6-1528449355056)]

為了便于理解,其實(shí)可以把component想象成針管,module是注射瓶,里面的依賴對(duì)象是注入的藥水,build方法是插進(jìn)患者(Container),inject方法的調(diào)用是推動(dòng)活塞。

3. 配置

Java Gradle

// Add plugin https://plugins.gradle.org/plugin/net.ltgt.apt
plugins {
  id "net.ltgt.apt" version "0.5"
}

// Add Dagger dependencies
dependencies {
  compile 'com.google.dagger:dagger:2.x'
  apt 'com.google.dagger:dagger-compiler:2.x'
}

Android Gradle

// Add Dagger dependencies
dependencies {
  compile 'com.google.dagger:dagger:2.x'
  annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}

如果使用的Android gradle plugin 版本低于2.2請(qǐng)參考這里

4. 簡(jiǎn)單的例子

實(shí)現(xiàn)Module

繼續(xù)上面的例子:

@Module // 注明本類是Module
public class MyModule{
    @Provides  // 注明該方法是用來(lái)提供依賴對(duì)象的方法
    public B provideB(){
        return new B();
    }
}

實(shí)現(xiàn)Component

@Component(modules={ MyModule.class}) // 指明Component查找Module的位置
public interface MyComponent{    // 必須定義為接口,Dagger2框架將自動(dòng)生成Component的實(shí)現(xiàn)類,對(duì)應(yīng)的類名是Dagger×××××,這里對(duì)應(yīng)的實(shí)現(xiàn)類是DaggerMyComponent 
    void inject(A a);   // 注入到A(Container)的方法,方法名一般使用inject
 }

實(shí)現(xiàn)Container

A就是可以被注入依賴關(guān)系的容器。

public A{
     @Inject   //標(biāo)記b將被注入
     B b;   // 成員變量要求是包級(jí)可見,也就是說@Inject不可以標(biāo)記為private類型。 
     public void init(){
         DaggerMyComponent.create().inject(this); // 將實(shí)現(xiàn)類注入
     }
 }

當(dāng)調(diào)用A的init()方法時(shí), b將會(huì)自動(dòng)被賦予實(shí)現(xiàn)類的對(duì)象。

5. 更多用法

方法參數(shù)

上面的例子@Provides標(biāo)注的方法是沒有輸入?yún)?shù)的,Module中@Provides標(biāo)注的方法是可以帶輸入?yún)?shù)的,其參數(shù)值可以由Module中的其他被@Provides標(biāo)注的方法提供。

@Module
public class MyModule{
    @Provides
    public B provideB(C c){         
        return new B(c);
    }
    @Provides
    pulic C provideC(){
        return new C();
    }
}

如果找不到被@Provides注釋的方法提供對(duì)應(yīng)參數(shù)對(duì)象的話,將會(huì)自動(dòng)調(diào)用被@Inject注釋的構(gòu)造方法生成相應(yīng)對(duì)象。

@Module
public class MyModule{
    @Provides
    public B provideB(C c){
        return new B(c);
    }
}
public class C{
    @Inject
    Public C(){
    }
}

添加多個(gè)Module

一個(gè)Component可以添加多個(gè)Module,這樣Component獲取依賴時(shí)候會(huì)自動(dòng)從多個(gè)Module中查找獲取。添加多個(gè)Module有兩種方法,一種是在Component的注解@Component(modules={××××,×××})中添加多個(gè)modules

@Component(modules={ModuleA.class,ModuleB.class,ModuleC.class}) 
public interface MyComponent{
    ...
}

另外一種添加多個(gè)Module的方法可以使用@Module的 includes的方法(includes={××××,×××})

@Module(includes={ModuleA.class,ModuleB.class,ModuleC.class})
public class MyModule{
    ...
}
@Component(modules={MyModule.class}) 
public interface MyComponent{
    ...
}

創(chuàng)建Module實(shí)例

上面簡(jiǎn)單例子中,當(dāng)調(diào)用DaggerMyComponent.create()實(shí)際上等價(jià)于調(diào)用了DaggerMyComponent.builder().build()??梢钥闯?,DaggerMyComponent使用了Builder構(gòu)造者模式。在構(gòu)建的過程中,默認(rèn)使用Module無(wú)參構(gòu)造器產(chǎn)生實(shí)例。如果需要傳入特定的Module實(shí)例,可以使用

DaggerMyComponent.builder()
.moduleA(new ModuleA()) 
.moduleB(new ModuleB())
.build()

區(qū)分@Provides 方法

這里以Android Context來(lái)舉例。當(dāng)有Context需要注入時(shí),Dagger2就會(huì)在Module中查找返回類型為Context的方法。但是,當(dāng)Container需要依賴兩種不同的Context時(shí),你就需要寫兩個(gè)@Provides方法,而且這兩個(gè)@Provides方法都是返回Context類型,靠判別返回值的做法就行不通了。這就可以使用@Named注解來(lái)區(qū)分

//定義Module
@Module
public class ActivityModule{
private Context mContext    ;
private Context mAppContext = App.getAppContext();
    public ActivityModule(Context context) {
        mContext = context;
    }
    @Named("Activity")
    @Provides
    public Context provideContext(){  
        return mContext;
    }
    @Named("Application")
    @Provides
    public Context provideApplicationContext (){
        return mAppContext;
    }
}

//定義Component
@Component(modules={ActivityModule.class}) 
interface ActivityComponent{   
    void inject(Container container);   
}

//定義Container
class Container extends Fragment{
    @Named("Activity") 
    @Inject
    Context mContext; 

    @Named("Application") 
    @Inject
    Context mAppContext; 
    ...
    public void init(){
         DaggerActivityComponent.
     .activityModule(new ActivityModule(getActivity()))
     .inject(this);
     }

}

這樣,只有相同的@Named的@Inject成員變量與@Provides方法才可以被對(duì)應(yīng)起來(lái)。
更常用的方法是使用注解@Qualifier來(lái)自定義注解。

@Qualifier   
@Documented   //起到文檔提示作用
@Retention(RetentionPolicy.RUNTIME)  //注意注解范圍是Runtime級(jí)別
public @interface ContextLife {
    String value() default "Application";  // 默認(rèn)值是"Application"
}

接下來(lái)使用我們定義的@ContextLife來(lái)修改上面的例子

//定義Module
@Module
public class ActivityModule{
private Context mContext    ;
private Context mAppContext = App.getAppContext();
    public ActivityModule(Context context) {
        mContext = context;
    }
    @ContextLife("Activity")
    @Provides
    public Context provideContext(){  
        return mContext;
    }
    @ ContextLife ("Application")
    @Provides
    public Context provideApplicationContext (){
        return mAppContext;
    }
}

//定義Component
@Component(modules={ActivityModule.class}) 
interface ActivityComponent{   
    void inject(Container container);   
}

//定義Container
class Container extends Fragment{
    @ContextLife ("Activity") 
    @Inject
    Context mContext; 

    @ContextLife ("Application") 
    @Inject
    Context mAppContext; 
    ...
    public void init(){
         DaggerActivityComponent.
     .activityModule(new ActivityModule(getActivity()))
     .inject(this);

     }

}

組件間依賴

假設(shè)ActivityComponent依賴ApplicationComponent。當(dāng)使用ActivityComponent注入Container時(shí),如果找不到對(duì)應(yīng)的依賴,就會(huì)到ApplicationComponent中查找。但是,ApplicationComponent必須顯式把ActivityComponent找不到的依賴提供給ActivityComponent。

//定義ApplicationModule
@Module
public class ApplicationModule {
    private App mApplication;

    public ApplicationModule(App application) {
        mApplication = application;
    }

    @Provides
    @ContextLife("Application")
    public Context provideApplicationContext() {
        return mApplication.getApplicationContext();
    }

}

//定義ApplicationComponent
@Component(modules={ApplicationModule.class})
interface ApplicationComponent{
    @ContextLife("Application")
    Context getApplication();  // 對(duì)外提供ContextLife類型為"Application"的Context
}

//定義ActivityComponent
@Component(dependencies=ApplicationComponent.class, modules=ActivityModule.class)
 interface ActivityComponent{
    ...
}

6. 進(jìn)階用法

單例的使用

創(chuàng)建某些對(duì)象有時(shí)候是耗時(shí)、浪費(fèi)資源的或者需要確保其唯一性,這時(shí)就需要使用@Singleton注解標(biāo)注為單例了。

@Module
class MyModule{
    @Singleton   // 標(biāo)明該方法只產(chǎn)生一個(gè)實(shí)例
    @Provides
    B provideB(){
        return new B();
    }
}
@Singleton  // 標(biāo)明該Component中有Module使用了@Singleton
@Component(modules=MyModule.class)
class MyComponent{
    void inject(Container container)
}

※注意:Java中,單例通常保存在一個(gè)靜態(tài)域中,這樣的單例往往要等到虛擬機(jī)關(guān)閉時(shí)候,該單例所占用的資源才釋放。但是,Dagger通過注解創(chuàng)建出來(lái)的單例并不保持在靜態(tài)域上,而是保留在Component實(shí)例中。所以說不同的Component實(shí)例提供的對(duì)象是不同的。

自定義Scope

@Singleton就是一種Scope注解,也是Dagger2唯一自帶的Scope注解,下面是@Singleton的源碼

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

可以看到定義一個(gè)Scope注解,必須添加以下三部分:
@Scope :注明是Scope
@Documented :標(biāo)記文檔提示
@Retention(RUNTIME) :運(yùn)行時(shí)級(jí)別
對(duì)于Android,我們通常會(huì)定義一個(gè)針對(duì)整個(gè)APP全生命周期的@PerApp的Scope注解和針對(duì)一個(gè)Activity生命周期的@PerActivity注解,如下

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

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

@PerApp的使用例:
@Module
public class ApplicationModule {
    private App mApplication;

    public ApplicationModule(App application) {
        mApplication = application;
    }

    @Provides
    @PerApp
    @ContextLife("Application")
    public Context provideApplicationContext() {
        return mApplication.getApplicationContext();
    }

}

@PerApp
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
    @ContextLife("Application")
    Context getApplication();

}

// 單例的有效范圍是整個(gè)Application
public class App extends Application {
private static  ApplicationComponent mApplicationComponent; // 注意是靜態(tài)
    public void onCreate() {
        mApplicationComponent = DaggerApplicationComponent.builder()
                .applicationModule(new ApplicationModule(this))
                .build();
    }

    // 對(duì)外提供ApplicationComponent
    public static ApplicationComponent getApplicationComponent() {
        return mApplicationComponent;
    }
}

@PerActivity的使用例:

// 單例的有效范圍是Activity的生命周期
public abstract class BaseActivity extends AppCompatActivity {
    protected ActivityComponent mActivityComponent; //非靜態(tài),除了針對(duì)整個(gè)App的Component可以靜態(tài),其他一般都不能是靜態(tài)的。
    // 對(duì)外提供ActivityComponent
    public ActivityComponent getActivityComponent() {
        return mActivityComponent;
    }

    public void onCreate() {
        mActivityComponent = DaggerActivityComponent.builder()
                .applicationComponent(App.getApplicationComponent())
                .activityModule(new ActivityModule(this))
                .build();
    }
}

通過上面的例子可以發(fā)現(xiàn),使用自定義Scope可以很容易區(qū)分單例的有效范圍。

子組件

可以使用@Subcomponent注解拓展原有component。Subcomponent其功能效果優(yōu)點(diǎn)類似component的dependencies。但是使用@Subcomponent不需要在父component中顯式添加子component需要用到的對(duì)象,只需要添加返回子Component的方法即可,子Component能自動(dòng)在父Component中查找缺失的依賴。

//父Component:
@Component(modules=××××)
public AppComponent{    
    SubComponent subComponent ();  // 這里返回子Component
}
//子Component:
@Subcomponent(modules=××××)   
public SubComponent{
    void inject(SomeActivity activity); 
}

// 使用子Component
public class SomeActivity extends Activity{
    public void onCreate(Bundle savedInstanceState){
        App.getComponent().subCpmponent().inject(this); // 這里調(diào)用子Component
    }    
}

懶加載模式

可以使用Lazy來(lái)包裝Container中需要被注入的類型為延遲加載。

public class Container{
    @Inject Lazy<B> b; 
    public void init(){
        DaggerComponent.create().inject(this);
        B b=b.get();  //調(diào)用get時(shí)才創(chuàng)建b
    }
}

另外可以使用Provider實(shí)現(xiàn)強(qiáng)制加載,每次調(diào)用get都會(huì)調(diào)用Module的Provides方法一次,和懶加載模式正好相反。

7. 結(jié)合MVP模式使用例

接下來(lái)看一下我們是如何在Activity中注入一個(gè)Presenter變量,來(lái)實(shí)現(xiàn)MVP的V層與P層之間的解耦。

// Module
@Module
public class ActivityModule {
    private Activity mActivity;

    public ActivityModule(Activity activity) {
        mActivity = activity;
    }

    @Provides
    @PerActivity
    @ContextLife("Activity")
    public Context ProvideActivityContext() {
        return mActivity;
    }
}

// Component
@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {

    @ContextLife("Activity")
    Context getActivityContext(); 

    @ContextLife("Application")
    Context getApplicationContext();

    void inject(NewsActivity newsActivity);
}

// Presenter
public class NewsPresenter {
    ...
    @Inject
    public NewPresenter () {
    }
     private void loadNews () {
        ...
    }
    ....
}

// Activity
public class NewsActivity extends BaseActivity
        implements NewsView {
    @Inject
    NewPresenter mNewsPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
    ActivityComponent activityComponent;
        activityComponent = DaggerActivityComponent.builder()
         .applicationComponent(App.getApplicationComponent())
                .activityModule(new ActivityModule(this))
                .build();
        activityComponent.inject(this);

    mNewsPresenter.loadNews();

    }
}

在這個(gè)例子中,我們注入了一個(gè)名叫NewsPresenter的類,假設(shè)它負(fù)責(zé)在后臺(tái)處理新聞數(shù)據(jù)。但是我們并沒有在Module中提供生產(chǎn)NewsPresenter實(shí)例的Provides方法。這時(shí)根據(jù)Dagger2的注入規(guī)則,用@Inject注釋的成員變量的依賴會(huì)首先從Module的@Provides方法集合中查找。如果查找不到的話,則會(huì)查找成員變量類型是否有@Inject構(gòu)造方法,并會(huì)調(diào)用該構(gòu)造方法注入該類型的成員變量。這時(shí)如果被@Inject注釋的構(gòu)造方法有參數(shù)的話,則將會(huì)繼續(xù)使用注入規(guī)則進(jìn)行遞歸查找。

完整的例子用法大家可以參考我在GitHub上的開源項(xiàng)目:ColorfulNews

最后

推薦大家有時(shí)間去看一下Dagger幫我們自動(dòng)生成的代碼,感覺就像是自己手敲的一樣,非常便于理解,也很方便我們進(jìn)行排錯(cuò)調(diào)試。Dagger2的生成源碼里面也是用到了很多設(shè)計(jì)模式,如裝飾模式、建造者模式等,非常值得我們學(xué)習(xí)借鑒。比如當(dāng)我們調(diào)用inject(container)方法進(jìn)行注入時(shí),其實(shí)對(duì)應(yīng)的生成代碼里面用的就是裝飾者模式,將container傳進(jìn)去進(jìn)行包裝賦值,這也就是為什么我們不能使用private修飾被@Inject注釋的變量的原因,如果不這樣做的話外界將不能訪問Container中的變量進(jìn)行賦值。

作者:咖枯
鏈接:http://www.itdecent.cn/p/4db940c8da97
來(lái)源:簡(jiǎn)書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容