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

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

轉(zhuǎn)載請注明出處 簡書-liqinpeng

Dagger2

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

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

怎么使用他呢?

在你的項目gradle中導(dǎo)入

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

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

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

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

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

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

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

@Module(模塊; 組件)

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

對應(yīng)上我們剛才的AppComponent組件集,我們就要考慮一件事了,我怎么將組件集和組件進(jìn)行綁定,盡管我們的AppComponent聲明了自己是依賴于AppModule的
在代碼中這樣寫
DaggerAppComponent.builder().appModule(new AppModule(this)).build();
我們來還原一下這行代碼
DaggerAppComponent.Builder builder = DaggerAppComponent.builder();
            builder = builder.appModule(new AppModule(this));
            builder.build();
我們來解釋一下這三行代碼
第一句 構(gòu)建出來DaggerAppComponent組件集這個Builder對象,
第二句,給builder加入組件,加入AppModule組件
第三句,真正的創(chuàng)建好組件集對象
在這句話執(zhí)行完之后,就實現(xiàn)了容器的創(chuàng)建,
DaggerAppComponent.builder().appModule(new AppModule(this)).build().injectApplication(this);
這句話執(zhí)行完之后就將當(dāng)前對象告訴Dagger2容器,我需要用到你容器中的某些組件,他會自動的去容器中尋找或創(chuàng)建,只需要在你需要的對象上加@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不知道怎么創(chuàng)建Retrofit對象,在容器中也找不到該對象,

接下來我們來介紹一下Dagger2容器創(chuàng)建依賴對象的兩種方式
1: 如果依賴對象的構(gòu)造方法上加了@Inject注解,那么Dagger2就會調(diào)用該構(gòu)造方法去創(chuàng)建對象
2: 如果你要說 你的依賴對象,是第三方框架提供的,你根本沒權(quán)利修改他的代碼,那么我要怎么創(chuàng)建該對象?
    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注解,就相當(dāng)于通知Dagger2說,我給你提供Retrofit的創(chuàng)建方式,你如果不知道怎么創(chuàng)建,就來Module中找找看
    繼續(xù)看,providerRetrofit該方法要求傳遞一個OkHttpClient 對象作為參數(shù),但是OkHttpClient 對象怎么創(chuàng)建呢? 他也是第三方的框架,我們是沒權(quán)利修改他的構(gòu)造方法的,怎么解決?
      // 提供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 的創(chuàng)建方式,那么providerRetrofit的參數(shù)就會由Dagger2去創(chuàng)建好在傳遞進(jìn)去
至此,我們的Retrofit對象方式也有了,我們需要在哪里使用,就在其聲明上加入@Inject注解
    @Inject
    public Retrofit retrofit;
    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().appModule(new AppModule(this)).build().injectApplication(this);
    }
這樣就可以使用Retrofit對象了,是不是感覺很復(fù)雜?但是在你真正使用到的時候,你會感覺為什么Dagger2的管理是這么的舒服,這么讓人放心

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

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

我們來重點談?wù)勥@倆

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

現(xiàn)在假設(shè)你已經(jīng)理解了Dagger2的一些簡單用法,我就不再深入說了,以后如果我們有時間,在來詳細(xì)分析,大家討論學(xué)習(xí),共同進(jìn)步

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

Retrofit 和 RxAndroid的完美搭配

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

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

好了言歸正傳,我們繼續(xù)

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

接口分類.png

接口的聲明按照Retrofit的規(guī)范

接口.png

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

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

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

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

解決了數(shù)據(jù)過濾之后,我們繼續(xù)
就拿登錄來當(dāng)例子吧

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

這個是切換線程
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())
                        ;
            }
        };
    }
}

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

在所有數(shù)據(jù)邏輯處理完成之后,是時候注冊消費了,我們又考慮到好像有很多業(yè)務(wù)需要有一個加載框,在網(wǎng)絡(luò)請求結(jié)束后關(guān)閉,怎么做?

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

//就是一個實現(xiàn)了Subscriber接口的方法,空實現(xiàn)我們不關(guān)心的方法,我們關(guān)心的方法重寫一下就行
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) {

    }
}


//一個帶有進(jìn)度條的消費者觀察者,是一個抽象類,抽象方法為injectContext需要在實例化的時候注入當(dāng)前界面的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("服務(wù)器懵逼了...");
        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();
    }

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

}

這樣就解決了需要進(jìn)度條的情況.

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

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


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

寫在最后:

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末琳拭,一起剝皮案震驚了整個濱河市顿膨,隨后出現(xiàn)的幾起案子篇亭,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宣鄙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)默蚌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門冻晤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绸吸,你說我怎么就攤上這事鼻弧。” “怎么了锦茁?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵攘轩,是天一觀的道長。 經(jīng)常有香客問我码俩,道長度帮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任稿存,我火速辦了婚禮够傍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘挠铲。我一直安慰自己冕屯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布拂苹。 她就那樣靜靜地躺著安聘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瓢棒。 梳的紋絲不亂的頭發(fā)上浴韭,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天麦牺,我揣著相機(jī)與錄音严就,去河邊找鬼。 笑死卵慰,一個胖子當(dāng)著我的面吹牛连霉,可吹牛的內(nèi)容都是我干的榴芳。 我是一名探鬼主播嗡靡,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼窟感!你這毒婦竟也來了讨彼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤柿祈,失蹤者是張志新(化名)和其女友劉穎哈误,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躏嚎,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蜜自,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了卢佣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袁辈。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖珠漂,靈堂內(nèi)的尸體忽然破棺而出晚缩,到底是詐尸還是另有隱情,我是刑警寧澤媳危,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布荞彼,位于F島的核電站,受9級特大地震影響待笑,放射性物質(zhì)發(fā)生泄漏鸣皂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一暮蹂、第九天 我趴在偏房一處隱蔽的房頂上張望寞缝。 院中可真熱鬧,春花似錦仰泻、人聲如沸荆陆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽被啼。三九已至,卻和暖如春棠枉,著一層夾襖步出監(jiān)牢的瞬間浓体,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工辈讶, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留命浴,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓贱除,卻偏偏與公主長得像生闲,于是被迫代替她去往敵國和親媳溺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容