[譯] 全新 Android 注入器:Dagger 2(一)

MummyDing發表於2017-12-20

Dagger 2.10 新增了 Android Support 和 Android Compiler 兩大模組。對我們來說,本次改動非常之大,所有 Android 開發者都應儘早嘗試使用這個新的 Android 依賴注入框架。

在我開始介紹新的 AndroidInjector 類以及 Dagger 2.11 庫之前,如果你對 Dagger 2 還不熟悉甚至之前根本沒用過,那我強烈建議你先去看看 Dagger 入門指南,弄清楚什麼是依賴注入。為什麼這麼說呢?因為 Android Dagger 涉及到大量註解,學起來會比較吃力。在我看來,學 Android Dagger 之前你最好先去學學 Dagger 2 和依賴注入。這裡有一篇關於依賴注入的入門文章 Blog 1 以及一篇關於 Dagger 2 的文章 Blog 2

老用法

Dagger 2.10 之前,Dagger 2 是這樣用的:

((MyApplication) getApplication())        
.getAppComponent()        
.myActivity(new MyActivityModule(userId))       
.build()        
.inject(this);
複製程式碼

這會有什麼問題呢?我們想用依賴注入,但是依賴注入的核心原則是什麼?

一個類不應該關心它是如何被注入的

因此我們必須把這些 Builder 方法和 Module 例項建立部分去掉。

示例工程

我建立的示例工程中沒做什麼,我想讓它儘可能地簡單。它裡面僅包含 MainActivityDetailActivity 兩個 Activity,它們都注入到了相應的 Presenter 實現類並且請求了網路介面(並不是真的發起了 HTTP 請求,我只是寫了一個假方法)。

準備工作

在 build.gradle 中加入以下依賴:

compile 'com.google.dagger:dagger:2.11-rc2'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc2'
compile 'com.google.dagger:dagger-android-support:2.11-rc2'
複製程式碼

工程包結構

[譯] 全新 Android 注入器:Dagger 2(一)

Application 類利用 AppComponent 構建了一張圖譜。AppComponent 類的頭部都被加上 @Component 註解,當 AppComponent 利用它的 Module 進行構建的時候,我們將得到一張擁有所有所需例項物件的圖譜。舉個例子,當 App Module 提供了ApiService,我們在構建擁有 App Module 的 Component 時將會得到 ApiService 例項物件。

如果我們想將 Activity 加入到 Dagger 圖譜中從而能夠直接從父 Compponent 直接獲取所需例項,我們只需簡單地將 Activity 加上 @Subcomponent 註解即可。在我們的示例中,DetailActivityComponentMainActivityComponent 類都被加上了 @Subcomponent 註解。最後我們還有一個必需步驟,我們需要告訴父 Component 相關的子 Component 資訊,因此所有的根 Compponent 都能知道它所有的子 Component。

先彆著急,我後面會解釋 @Subcomponent@Component 以及 DispatchActivity 都是什麼的。現在只是想讓你對 @Component@Subcomponent 有一個大概瞭解。

@Component and @Component.Builder

**@Component**(modules = {
        AndroidInjectionModule.class,
        AppModule.class,
        ActivityBuilder.class})
public interface AppComponent {

    **@Component.Builder**
    interface Builder {
        **@BindsInstance** _Builder application(Application application);_
        _AppComponent build();_
    }

    void inject(AndroidSampleApp app);
}
複製程式碼

**@Component:**Component 是一個圖譜。當我們構建一個 Component時,Component 將利用 Module 提供被注入的例項物件。

**@Component.Builder:**我們可能需要繫結一些例項物件到 Component 中,這種情況我們可以通過建立一個帶 @Component.Builder 註解的介面,然後就可以向 builder 中任意新增我們想要的方法。在我的示例中,我想將 Application 加入到 AppComponent中。

注意:如果你想為你的 Component 建立一個 Builder,那你的 Builder 介面中需要有一個返回型別為你所建立的 Component 的 builder() 方法。

注入 AppComponent

DaggerAppComponent
        ._builder_()
        **.application(this)**
        .build()
        .inject(this);
複製程式碼

從上面的程式碼可以看出,我們將 Application 例項繫結到了 Dagger 圖譜中。

我想大家已經對 @Component.Builder@Component 有了一定的認識,下面我想說說工程的結構。

Component/Module 結構

使用 Dagger 的時候我們可以將 App 分為三層:

  • Application Component
  • Activity Components
  • Fragment Components

Application Component

@Component(modules = {
        AndroidInjectionModule.class,
        AppModule.class,
        ActivityBuilder.class})
public interface AppComponent {

    @Component.Builder
    interface Builder {
        @BindsInstance Builder application(Application application);
        AppComponent build();
    }

    void inject(AndroidSampleApp app);
}
複製程式碼

每個 Android 應用都有一個 Application 類,這就是為什麼我也有一個 Application Component 的原因。這個 Component 表示是為應用層面提供例項的 (例如 OkHttp, Database, SharedPrefs)。這個 Component 是 Dagger 圖譜的根,在我們的應用中 Application Component 提供了三個 Module

  • AndroidInjectionModule:這個類不是我們寫的,它是 Dagger 2.10 中的一個內部類,通過給定的 Module 為我們提供了 Activity 和 Fragment。
  • ActivityBuilder:我們自己建立的 Module,這個 Module 是給 Dagger 用的,我們將所有的 Activity 對映都放在了這裡。Dagger 在編譯期間能獲取到所有的 Activity,我們的 App 中有 MainActivity 和 DetailActivity 兩個 Activity,因此我將這兩個 Activity 都放在這裡。
@Module
public abstract class ActivityBuilder {

    @Binds
    @IntoMap
    @ActivityKey(MainActivity.class)
    abstract AndroidInjector.Factory<? extends Activity> bindMainActivity(MainActivityComponent.Builder builder);

    @Binds
    @IntoMap
    @ActivityKey(DetailActivity.class)
    abstract AndroidInjector.Factory<? extends Activity> bindDetailActivity(DetailActivityComponent.Builder builder);

}
複製程式碼
  • AppModule:我們在這裡提供了 retrofit、okhttp、持久化資料庫、SharedPrefs。其中有一個很重要的細節,我們必須將子 Component 加入到 AppModule 中,這樣 Dagger 圖譜才能識別。
@Module(subcomponents = {
        MainActivityComponent.class,
        DetailActivityComponent.class})
public class AppModule {

    @Provides
    @Singleton
    Context provideContext(Application application) {
        return application;
    }

}
複製程式碼

Activity Components

我們有兩個 Activity:MainActivity and DetailActivity。它們都擁有自己的 ModuleComponent,但是它們與我在上面 AppModule 中定義的一樣,也是子 Component

  • MainActivityComponent:這個 Component 是連線 MainActivityModule 的橋樑,但是有一個很關鍵的不同點就是不需要在 Component 中新增 inject() 和 build() 方法。MainActivityComponent 會從父類中整合這些方法。AndroidInjector 類是 dagger-android 框架中新增的。
@Subcomponent(modules = MainActivityModule.class)
public interface MainActivityComponent extends AndroidInjector<MainActivity>{
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<MainActivity>{}
}
複製程式碼
  • MainActivityModule:這個 ModuleMainActivity 提供了相關例項物件(例如 MainActivityPresenter)。你注意到 provideMainView() 方法將 MainActivity 作為引數了嗎?沒錯,我們利用 MainActivityComponent 建立了我們所需的物件。因此 Dagger 將我們的 Activity 加入到 圖譜中並因此能使用它。
@Module
public class MainActivityModule {

    @Provides
    MainView provideMainView(MainActivity mainActivity){
        return mainActivity;
    }

    @Provides
    MainPresenter provideMainPresenter(MainView mainView, ApiService apiService){
        return new MainPresenterImpl(mainView, apiService);
    }
}
複製程式碼

同樣的,我們可以像建立 MainActivityComponentMainActivityModule 一樣建立 DetailActivityComponentDetailActivityModule,因此具體步驟就略過了。

Fragment Components

如果在 DetailActivity 中有兩個 Fragment,那我們應該怎麼辦呢?實際上這一點都不難想到。先想想 Activity 和 Application 之間的關係,Application 通過對映的 Module(在我的示例中就是ActivityBuilder)知道所有的 Activity,並且將所有的 Activity 作為子 Component 加入到 AppModule 中。

Activity 和 Fragment 也是如此,首先建立一個 FragmentBuilder Module 加入到 DetailActivityComponent 中。

現在我們就可以像之前建立 MainActivityComponentMainActivityModule 一樣來建立 DetailFragmentComponentDetailFragmentModule了。

DispatchingAndroidInjector

最後我們需要做的便是注入到注入器中。注入器的作用是什麼?我想用一段簡單的程式碼解釋下。

public class AndroidSampleApp extends Application implements HasActivityInjector {

    @Inject
    DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;

    @Override
    public void onCreate() {
        super.onCreate();
        //simplified
    }

    @Override
    public DispatchingAndroidInjector<Activity> activityInjector() {
        return activityDispatchingAndroidInjector;
    }
}
複製程式碼

Application 擁有很多 Activity,這就是我們實現 HasActivityInjector 介面的原因。那 Activity 有多個 Fragment 呢?意思是我們需要在 Activity 中實現 HasFragmentInjector 介面嗎?沒錯,我就是這個意思!

public class DetailActivity extends AppCompatActivity implements HasSupportFragmentInjector {

    @Inject
    DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;

    //simplified
  
    @Override
    public AndroidInjector<Fragment> supportFragmentInjector() {
        return fragmentDispatchingAndroidInjector;
    }
}
複製程式碼

如果你沒有子 Fragment 你不需要注入任何東西到 Fragment,那你也不需要實現 HasSupportFragmentInjector 介面了。但是在我們的示例中需要在 DetailActivity 建立一個 DetailFragment

AndroidInjection.inject(this)

做這些都是為了什麼?這是因為 Activity 和 Fragment 都不應該是如何被注入的,那我們應該如何注入呢?

在 Activity 中:

@Override
protected void onCreate(Bundle savedInstanceState) {
 **AndroidInjection._inject_(this);**
    super.onCreate(savedInstanceState);
}
複製程式碼

在 Fragment 中:

@Override
public void onAttach(Context context) {
    **AndroidSupportInjection._inject_(this);
   ** super.onAttach(context);
}
複製程式碼

沒錯,恭喜你,所有工作都完成了!

我知道這有點複雜,學習曲線很陡峭,但是我們還是達到目的了。現在,我們的類是不知道如何被注入的。我們可以將所需例項物件通過 @Inject annotation 註解注入到我們的 UI 元素。

你可以在我的 GitHub 主頁找到這個工程,我建議你對照著 Dagger 2 的官方文件看。

iammert/dagger-android-injection _dagger-android-injection - Sample project explains Dependency Injection in Android using dagger-android framework._github.com

Dagger ‡ A fast dependency injector for Android and Java. A fast dependency injector for Android and Java.google.github.io

在第二部分,我想利用 Dagger 提供的新註解來簡化 android-dagger 注入,但是在簡化之前我想先給大家看看它原來的樣子。

第二部分在這裡了。

感謝閱讀,祝你編碼愉快!


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章