如何優雅的在項目中引入Dagger2+Retrofit+RxJava(RxAndroid)(一)

近些時間,由于接手一個嶄新的項目,就考慮到了使用目前市面上較為流行的一些框架,再三權衡之下,在項目中引入了標題說的幾個框架,在此會對其用法,整合進行闡述,盡自己所能幫助大家.

轉載請注明出處 簡書-liqinpeng

Dagger2

具體的Dagger2描述請大家自行百度,這里只闡述自己對其的一些理解
在查看下文的時候,我們需要了解以下幾個概念:
1:什么是依賴注入?
    在此對依賴注入進行解釋,我們將依賴 | 注入分開,
    依賴
        就是找你當前對象中所依賴的其他對象,比如說學生對象中存在著教師的引用,調用學生的聽課方法,就必然
    需要用到教師這個對象,不然誰去給學生講課呢?這里,學生對象就依賴了教師對象
    注入
        既然學生對象的創建要依賴于教師對象,或者說學生對象的某些方法依賴了教師對象,那么在學生中教師我們可以通過如下方式來獲取
        1:構造方法傳入 2,需要用到教師的方法,參數傳入
        我們來考慮一下問題,我們需要在調用方法的時候創建學生,創建教師,然后將教師傳遞過去,是,這種思路沒毛病,接下來我們在說一下極端的問題,
        假如說學生依賴了教師,教師依賴了教室,教室又依賴于黑板,等等等等,那么我們在調用學生方法的時候,是不是需要創建出教師,教室,黑板等對象,這種方式看似沒毛病,實際上如果對象一級級的依賴于其他對象,
        那么對我們的工作量是十分巨大的
        依賴注入就這么誕生了,既然你要求說你A對象依賴于B對象,甚至說B依賴于C,一層層的嵌套下去,對于開發者來說,都不關心,因為我們有了Dagger2,他會自動幫我們注入對象所需要的依賴對象(的依賴對象).

2:我為什么要使用他?
        在上個問題中我大概已經解釋了為什么要使用這么一個框架,他會注入你要操作對象依賴的對象(的依賴對象),不管依賴了多少級,都交給他去做吧

怎么使用他呢?

在你的項目gradle中導入

compile 'com.google.dagger:dagger:2.4'
apt 'com.google.dagger:dagger-compiler:2.4'

接下來我們介紹一下Dagger2提供給我們的注解(目前我們會用到的)

@Component(組成;成分) 模塊(組件)的集合

作為Dagger2的注入核心,在代碼編寫完成之后,dagger-compiler會為我們編譯生成對應的類,命名如下 DaggerXxxComponent
下面是我在項目中用到的
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
    void injectApplication(MApplication application);
}

我用@Componet對該接口進行聲明,說明其實Dagger2的一個模塊集,使用@Single告訴Dagger2,該組件是單例的,全局只有這一個
@Component既然是一個模塊集,就要求我們對應有其他一系列模塊,當然一個模塊也是可行的,寫法如下
@Component (modules = XxxModule.class)
如果有多個模塊,用大括號括起來
這時候不知道你有沒有想到這么一個問題?
既然是模塊集,那A模塊集是否可以引用或者說依賴于另一個模塊集呢?
有這種想法就太棒了,模塊集肯定也是模塊了,那一個模塊集引用其他模塊集當然是可行的,用代碼體現的話是這樣的

@PreFragment
@Component(dependencies = AppComponent.class , modules = {FragmentModule.class} )
public interface FragmentComponent {
   //個人設置
   void injectSettingFragment(MySettingFragment mySettingFragment);
}

大家可以先行跳過@PreFragment這個注解,我們往下看
@Component聲明FrgamentComponent是組件集, modules聲明引用了FragmentModule.class這個組件,而dependencies就是我們需要注意的,依賴于另一個組件集,在此我們依賴的是上面的AppComponent組件集
畫個圖看一下
組件和組件集.png
組件集依賴組件和組件集.png

@Module(模塊; 組件)

組件提供給我們的功能就很強大了,需要在類首加上@Module以及對應的作用域(就是聲明該module是一個單例的).編寫成代碼是這樣的
@Singleton
@Module
public class AppModule {
    public Application context;
    public AppModule(Application context) {
        this.context = context;
    }
}

對應上我們剛才的AppComponent組件集,我們就要考慮一件事了,我怎么將組件集和組件進行綁定,盡管我們的AppComponent聲明了自己是依賴于AppModule的
在代碼中這樣寫
DaggerAppComponent.builder().appModule(new AppModule(this)).build();
我們來還原一下這行代碼
DaggerAppComponent.Builder builder = DaggerAppComponent.builder();
            builder = builder.appModule(new AppModule(this));
            builder.build();
我們來解釋一下這三行代碼
第一句 構建出來DaggerAppComponent組件集這個Builder對象,
第二句,給builder加入組件,加入AppModule組件
第三句,真正的創建好組件集對象
在這句話執行完之后,就實現了容器的創建,
DaggerAppComponent.builder().appModule(new AppModule(this)).build().injectApplication(this);
這句話執行完之后就將當前對象告訴Dagger2容器,我需要用到你容器中的某些組件,他會自動的去容器中尋找或創建,只需要在你需要的對象上加@Inject注解

就像這樣
public class MApplication extends Application {
    @Inject
    public Retrofit retrofit;
    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().appModule(new AppModule(this)).build().injectApplication(this);
    }

這樣,我們按理說就可以使用retrofit這個對象了,
但是,我們還不可以運行這個程序,因為Dagger2不知道怎么創建Retrofit對象,在容器中也找不到該對象,

接下來我們來介紹一下Dagger2容器創建依賴對象的兩種方式
1: 如果依賴對象的構造方法上加了@Inject注解,那么Dagger2就會調用該構造方法去創建對象
2: 如果你要說 你的依賴對象,是第三方框架提供的,你根本沒權利修改他的代碼,那么我要怎么創建該對象?
    Dagger2給我們提供了一種方式
    就像這樣
    // 提供retrofit
    @Provides
    @Singleton
    public Retrofit providerRetrofit(OkHttpClient client){
        return new Retrofit.Builder()
                .client(client)
                .baseUrl(GlobalConstract.ip)
                .addConverterFactory(ScalarsConverterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
    }

在組件中提供某一對象,在其方法上加入@Provides注解,就相當于通知Dagger2說,我給你提供Retrofit的創建方式,你如果不知道怎么創建,就來Module中找找看
    繼續看,providerRetrofit該方法要求傳遞一個OkHttpClient 對象作為參數,但是OkHttpClient 對象怎么創建呢? 他也是第三方的框架,我們是沒權利修改他的構造方法的,怎么解決?
      // 提供OkHttpClient
    @Singleton
    @Provides
    public OkHttpClient providerOkHttpClient(){

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(10, TimeUnit.SECONDS);
        OkHttpClient client = builder.build();
        return client;
    }
我們在Module中加入OkHttpClient 的創建方式,那么providerRetrofit的參數就會由Dagger2去創建好在傳遞進去
至此,我們的Retrofit對象方式也有了,我們需要在哪里使用,就在其聲明上加入@Inject注解
    @Inject
    public Retrofit retrofit;
    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().appModule(new AppModule(this)).build().injectApplication(this);
    }
這樣就可以使用Retrofit對象了,是不是感覺很復雜?但是在你真正使用到的時候,你會感覺為什么Dagger2的管理是這么的舒服,這么讓人放心

這倆注解我們先不談,其實見名知意,大概也能了解個差不多了

@Singlet(單一)  單例需要用到的
@Scope(范圍)  作用域

我們來重點談談這倆

@Inject 注入
    既然是要注入,那么很明顯自己是需要將容器提供的對象拿出來,并且注入到自己的對象中直接使用,不用自己去創建那一級級的對象關系鏈,簡化工作
    但是你自己創建的對象怎么知道哪些是需要自己去創建或者說哪些是需要在容器中尋找的呢?@Injcet提供給我們的就是這么一個功能,只要你需要的對象頭上加了@Inject注解,就聲明了該對象需要在容器中去尋找,不用自己手動創建
@Provides 提供
    我在上面也說過了,Dagger2提供給我們兩種創建對象的方式,而@Provides則是在第二種情況時使用的,第三方框架得類你沒權限修改,但是你可以告訴Dagger2該框架的某些對象是怎么創建的,這么告訴呢,就是@Provides了,只需要在model中寫下providerXXX()方法,返回XXX,在方法聲明頭用@Provides標注一下,就完成了.

現在假設你已經理解了Dagger2的一些簡單用法,我就不再深入說了,以后如果我們有時間,在來詳細分析,大家討論學習,共同進步

接下來的篇幅我會把自己最近學RxJava的心得全部分享出來,如果我的代碼有問題,想法有錯誤,或者說路是錯的,希望大家能批評指正,萬分感謝

Retrofit 和 RxAndroid的完美搭配

我暫時不去講Retrofit和RxAndroid的使用,網上的教程很多,也都寫的很棒,大家先自行百度了解學習,之后我會一點點更新

哇,好像不寫他們怎么使用我就無法下手碼字了... 腦袋都是懵的...

好了言歸正傳,我們繼續

我們都知道,在項目中,有著服務器給的各種接口,將接口分類整理之后可能會是這樣的

接口分類.png

接口的聲明按照Retrofit的規范

接口.png

至于為什么接口統一返回ResponseSet<T>呢,我是這么想的,

首先我們來看一下Responset這個類
public class ResponseSet<T> {
    public Integer state;
    public String msg;
    public String action;
    public T json;
}
我們在來看一下服務器返回的數據格式
{
  "json": {
    "have_home_info": false,
    "have_baby": false,
    "user_id": "f39da9fa5c3ff1bd015c3ff4584b0000"
  },
  "action": "login",
  "state": 0
}
這個數據格式是不會變化的,在請求成功之后會返回action(你請求的接口),state(請求狀態碼,對應請求結果),而json則是變化的,可能是一個user,一個vaccine,一個station.
如果請求失敗了會有服務端返回的失敗原因msg

這時候我們就要去考慮,既然所有的接口都會返回公共的部分,是每次都要寫一遍嗎? 這樣肯定是不正確的做法,于是就想到了繼承,想到了泛型,因為我本人想練習一下泛型的使用就在此采用泛型了,ResponsetSet要有一個泛型,這個類型實際上就是服務器給返回的json字段轉換成的javabean類型,我們來解釋一下這樣做的好處.
既然我們選擇了在項目中使用RxAndroid,我們應該知道filter這個方法了,過濾,對,就是過濾,現在有這么一個需求,我需要將所有的非成功的方法全部攔截,不進行處理,怎么做? 如果每次服務器返回數據對應你的一個javabean,你怎么做? 總不能寫一個個的過濾器吧?
這時候就體現我這么做的好處了,既然所有的請求最終都會返回一個ResponseSet,那么過濾器就唯一了,只需要這樣

public class RxResponseSetFilter implements Func1<ResponseSet,Boolean> {
    @Override
    public Boolean call(ResponseSet resultSet) {
        Boolean flag = false;
        if(resultSet.state == ResultCode.SUCCESS){
            flag = true;
        }else {
            flag = false;
            ToastUtil.show(resultSet.msg);
        }
        return flag;
    }
}

以后只要我們需要過濾結果操作的時候用這個對象就可以了

解決了數據過濾之后,我們繼續
就拿登錄來當例子吧

        Observable<ResponseSet<LoginBean>> ob = mModel.login(username, pwd);
        ob
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .filter(new RxResponseSetFilter())
        .subscribe(new SimpleObserver<Boolean>() {
            @Override
            public void onCompleted() {
                updateJPushRegisterId();
            }

            @Override
            public void onNext(Boolean aBoolean) {
                startMainHome(aBoolean);
            }

        });

我們暫且不談model層做了什么,現在讀一遍易讀性非常好的代碼.
先在io線程請求數據,請求到時候切換回主線程,然后對結果進行過濾,過濾掉非成功的操作,之后注冊消費者SimpleObserver,做進入主頁面操作,后臺更新推送id操作
現在我們把目光聚焦到這一段代碼,思考一下有什么是多余的,或者說是以后的操作都需要重復的編寫,
我們發現網絡請求的io線程,切換回主線程,過濾操作都是需要重復做的,現在我們就要考慮一件事,怎么model層返回的事件進行統一的變換,不需要重復寫代碼,即便是少寫一行也算事吧?
Rxandroid提供給我們這樣一組對象和方法 Transformer及compose(Transformer) 而compose會返回給我們一個觀察者(經過一系列變換的觀察者),這樣我們就想清楚了,上代碼看一下

這個是切換線程
babyServes.addBabyCycle(babyId,content,pictures).compose(RxTransformUtils.<ResponseSet>defaultSchedulers());

public class RxTransformUtils {
    public static <T> Observable.Transformer<T, T> defaultSchedulers() {
        return new Observable.Transformer<T, T>() {

            @Override
            public Observable<T> call(Observable<T> tObservable) {
                return tObservable
                        .unsubscribeOn(Schedulers.io())
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        ;
            }
        };
    }
}

你可能會問了,你現在想過濾結果,怎么辦,過濾結果這個操作不一定是所有的方法都需要過濾,而線程的切換卻是必須的,所以我在這沒有寫統一的過濾

在所有數據邏輯處理完成之后,是時候注冊消費了,我們又考慮到好像有很多業務需要有一個加載框,在網絡請求結束后關閉,怎么做?

我是這樣做的,定義了兩個消費者 SimpleObserver 和 ProgressObserver
看一下代碼

//就是一個實現了Subscriber接口的方法,空實現我們不關心的方法,我們關心的方法重寫一下就行
public class SimpleObserver<T> extends Subscriber<T> {

    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable e) {
            e.printStackTrace();
    }

    @Override
    public void onNext(T o) {

    }
}


//一個帶有進度條的消費者觀察者,是一個抽象類,抽象方法為injectContext需要在實例化的時候注入當前界面的Context,
public abstract class ProgressObserver<T> extends Subscriber<T> {

    private static final String TAG = "ProgressObserver";
    private Context mContext;
    private ProgressDialog dialog;

    @Override
    public void onCompleted() {
        dialog.dismiss();
    }

    @Override
    public void onError(Throwable e) {
        e.printStackTrace();
        Log.i(TAG,"onError");
        ToastUtil.show("服務器懵逼了...");
        dialog.dismiss();
    }


    @Override
    public abstract void onNext(T t);
    
    @Override
    public void onStart() {
        super.onStart();
        mContext = injectContext();
        dialog = new ProgressDialog(mContext);
        dialog.setMessage("加載中...");
        dialog.setCancelable(false);
        dialog.show();
    }

    //進度條需要依賴于context,注入當前界面的context就行
    public abstract Context injectContext();

}

這樣就解決了需要進度條的情況.

頭快炸了,第一次寫技術性的博客,不不,是第一次寫博客,也都是自己在平時寫代碼的時候總結的經驗和遇到的坑以及看自己糟糕透頂的代碼的一次次改變,下次再寫吧,寫寫rxjava的變換,我遇到的實際情況是這樣的.

例:
    現在用戶在注冊完成之后要進行登錄,當然是在后臺偷偷進行登錄,怎么做?用rxjava怎么做?
    很多人都是嵌入嵌入在嵌入,迷之縮進,我剛開始也是這樣
    動動腦子想一下,我們下一篇博客繼續討論


    我們始終都堅信著一件事:
    有問題不可怕,可怕的是我們知道了問題的所在還不去解決問題

寫在最后:

第一次寫,有寫的不好的地方請大家批評指正,我會努力修改認真吸取大家的經驗為我所用然后反哺大家的,評論我每天都會認真看,認真思考,認真回復的,萬份感謝
我是新來的,余生還請大家多多指教!????

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

推薦閱讀更多精彩內容