當復仇者聯盟遇上Dagger2、RxJava和Retrofit的巧妙結合

When the Avengers meet Dagger2, RxJava and Retrofit in a clean way

最近, 許多文章, 框架, 和android社區的討論, 都出現關於測試和軟件架構的討論, 就像上次Droidcon Spain上說的, 我們專注於做出健壯的程序而不是去開發特定的功能. 這表明android框架和當前的android社區漸漸成熟。

今天, 如果你是一個android開發者但是仍沒有聽說過 Dagger 2,RxJava or Retrofit這些詞, 那麼你正在錯過一些東西了, 這個系列的(文章)會把一些注意力放在如何用一個整潔清晰的架構觀點綜合使用這些框架。

我的第一個念頭是隻寫一篇文章,但是看到這些框架提供的大量內容我決定寫最少三篇系列文章。

像往常一樣,所有的代碼發佈在 GitHub, 建議、錯誤和代碼提交都是歡迎的,抱歉可能沒有那麼多時間回覆所有人:)


Dependency Injectors & Dagger 2

弄明白這個框架如何工作需要一些時間, 所以我會用我已經瞭解的的內容使之變的清晰。

Dagger 2 基於 依賴注入 模式.

請看下面的代碼片段:

    // Thor is awesome. He has a hammer!
    public class Thor extends Avenger {
        private final AvengerWeapon myAmazingHammer;

        public Thor (AvengerWeapon anAmazingHammer) {
            myAmazingHammer = anAmazingHammer;
        }

        public void doAmazingThorWork () {
            myAmazingHammer.hitSomeone();
        }
    }

Thor(雷神) 需要一個 AvengerWeapon (復仇者武器)才能工作正常, 依賴注入最基本的思想是 Thor 如果自己創建AvengerWeapon 而不是通過構造器傳入就會得到很少的好處。如果 Thor 自己創建 hammer 將會增加耦合。

AvengerWeapon 可以成爲一個接口,根據我們的邏輯被可以有不同的實現和注入方式。

在android中, 因爲框架已經設計完成。 我們不總是容易訪問構造器, Activity 和 Fragment 就是例子。

這些依賴注入框架像 (http://google.github.io/dagger/), Daggeror Guice 可以帶來便利好處。

使用 Dagger 2 我們可以這樣改變之前的代碼:

    // Thor is awesome. He has a hammer!
    public class Thor extends Avenger {
        @Inject AvengerWeapon myAmazingHammer;

        public void doAmazingThorWork () {
            myAmazingHammer.hitSomeone();
        }
    }

我們沒有直接訪問Thor 的構造器, 注入器使用幾個指令去構造Thor's hammer

    public class ThorHammer extends AvengerWeapon () {

        @Inject public AvengerWeapon() {

            initGodHammer();
        }
    }

@Inject 註解用來向 Dagger 2 指明那個構造器用來創建 Thor's hammer.


Dagger 2

Dagger 2 由Google 推廣和維護,是 Square 的Dagger 分支。

首先必須配置註解器,android-apt 插件負責這個角色, 允許使用註解器但不需要插入到最終的APK文件中。 註解器同時配置由註解器生成的代碼。

build.gradle (項目根目錄)

    dependencies {
        ...
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
    }

build.gradle (模塊目錄)

    apply plugin: 'com.neenbedankt.android-apt'

    dependencies {
        ...
        apt 'com.google.dagger:dagger-compiler:2.0'
    }


Components, modules & Avengers

模塊負責提供依賴,組件負責注入它們

舉個栗子:

@Module
public class AppModule {
    private final AvengersApplication mAvengersApplication;

    public AppModule(AvengersApplication avengersApplication) {
        this.mAvengersApplication = avengersApplication;
    }

    @Provides @Singleton 
    AvengersApplication provideAvengersAppContext () { 
        return mAvengersApplication; 
    }

    @Provides @Singleton 
    Repository provideDataRepository (RestRepository restRepository) { 
        return restRepository; 
    }
}

這個是主模塊,我們感興趣的是它的依賴存在於程序的生命週期中,一個通用的上下文和取回信息的倉庫。

很簡單,對吧?

我們在Dagger 2中所說的 @Provides 註解,如果有需要則必會去創建其依賴,因此如果我們沒有給定一個特定的依賴,Dagger 2 將會尋找有@Inject註解的構造方法

模塊被組件用來注入依賴,看這個模塊的組件:

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

    AvengersApplication app();
    Repository dataRepository();
}

這個模塊並不由 activity or fragment調用,相反,被更加複雜模塊獲得,提供我們需要得到的依賴。

AvengersApplication app();
Repository dataRepository();

組件必須公開他們的依賴關係圖(模塊提供的依賴關係),同理, 這個模塊提供的依賴關係必須對其它組件可見,其它組件把當前組件作爲依賴。如果這些依賴對Dagger 2不可見就不能在需要的時候被注入。

這裏是我們的依賴關係樹:

@Module
public class AvengersModule {

    @Provides @Activity
    List<Character> provideAvengers() {

        List<Character> avengers = new ArrayList<>(6);

        avengers.add(new Character(
            "Iron Man", R.drawable.thumb_iron_man, 1009368));

        avengers.add(new Character(
            "Thor", R.drawable.thumb_thor, 1009664));

        avengers.add(new Character(
            "Captain America", R.drawable.thumb_cap,1009220));

        avengers.add(new Character(
            "Black Widow", R.drawable.thumb_nat, 1009189));

        avengers.add(new Character(
            "Hawkeye", R.drawable.thumb_hawkeye, 1009338));

        avengers.add(new Character(
            "Hulk", R.drawable.thumb_hulk, 1009351));

        return avengers;
    }
}

這個模塊會使用一個特別的activity依賴注入,實際上負責繪製Avengers list:

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

    void inject (AvengersListActivity activity);
    List<Character> avengers();
}

再次我們暴露了我們的依賴,List<Character> 給其它組件,在這種情況下出現一個新方法: void inject (AvengersListActivity activity)。在這個方法被調用時,這些依賴將會被找到並注入到 AvengerListActivity.


All mixed

我們的類 AvengersApplication,將負責提供應用到其它組件的組件,注意,這並不以任何依賴項注入,只是提供組件。

另外需要注意的是 Dagger 2 在編譯時生成必要元素,如果你沒有構建項目,是找不到DaggerAppComponent 類的.

Dagger 2 通過下面的格式從你的組件中生成類: Dagger$${YourComponent}.

AvengersApplication.java

public class AvengersApplication extends Application {

    private AppComponent mAppComponent;

    @Override
    public void onCreate() {

        super.onCreate();
        initializeInjector();
    }

    private void initializeInjector() {

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

    public AppComponent getAppComponent() {

        return mAppComponent;
    }
}

AvengersListActivity.java

public class AvengersListActivity extends Activity 
    implements AvengersView {

    @InjectView(R.id.activity_avengers_recycler) 
    RecyclerView mAvengersRecycler;

    @InjectView(R.id.activity_avengers_toolbar) 
    Toolbar mAvengersToolbar;

    @Inject 
    AvengersListPresenter mAvengersListPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_avengers_list);
        ButterKnife.inject(this);

        initializeToolbar();
        initializeRecyclerView();
        initializeDependencyInjector();
        initializePresenter();
    }

    private void initializeDependencyInjector() {

        AvengersApplication avengersApplication = 
            (AvengersApplication) getApplication();

        DaggerAvengersComponent.builder()
            .avengersModule(new AvengersModule())
            .activityModule(new ActivityModule(this))
            .appComponent(avengersApplication.getAppComponent())
            .build().inject(this);
    }

當 initializeDependencyInjector() 執行到 .inject(this) Dagger 2 開始工作並提供必要的依賴,請記住 Dagger 2 在注入時是嚴格的,我的意思是,可以調用組件的 inject() 方法的類必須是和傳入參數完全相同的類型(譯者注:不能是參數類的父類和接口)。

AvengersComponent.java

...
public interface AvengersComponent extends ActivityComponent {

    void inject (AvengersListActivity activity);
    List<Character> avengers();
}

否則依賴將不會被解決。在這種情況,presenter 會與Dagger 2提供的 Avengers 一起初始化:

public class AvengersListPresenter implements Presenter, RecyclerClickListener {

    private final List<Character> mAvengersList;
    private final Context mContext;
    private AvengersView mAvengersView;
    private Intent mIntent;

    @Inject public AvengersListPresenter (List<Character> avengers, Context context) {

        mAvengersList = avengers;
        mContext = context;
    }

Dagger 2 將會解決這個 presenter,因爲它有 @Inject 註解。這個構造方法的參數由 Dagger 2解決,因爲它知道怎樣去構建它,這得益於模塊中 @Provides 方法。


Conclusion

像 Dagger 2 用好了依賴注入器,其力量是無需爭辯的,想象下根據框架提供的API級別你可以有不同的策略,它的可能性是無盡的。



Resources:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章