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

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

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

Dagger2

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

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

怎么使用他呢?

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

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

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

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

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

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

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

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

@Module(模塊; 組件)

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

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

這樣,我們按理說(shuō)就可以使用retrofit這個(gè)對(duì)象了,
但是,我們還不可以運(yùn)行這個(gè)程序,因?yàn)镈agger2不知道怎么創(chuàng)建Retrofit對(duì)象,在容器中也找不到該對(duì)象,

接下來(lái)我們來(lái)介紹一下Dagger2容器創(chuàng)建依賴對(duì)象的兩種方式
1: 如果依賴對(duì)象的構(gòu)造方法上加了@Inject注解,那么Dagger2就會(huì)調(diào)用該構(gòu)造方法去創(chuàng)建對(duì)象
2: 如果你要說(shuō) 你的依賴對(duì)象,是第三方框架提供的,你根本沒(méi)權(quán)利修改他的代碼,那么我要怎么創(chuàng)建該對(duì)象?
    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();
    }

在組件中提供某一對(duì)象,在其方法上加入@Provides注解,就相當(dāng)于通知Dagger2說(shuō),我給你提供Retrofit的創(chuàng)建方式,你如果不知道怎么創(chuàng)建,就來(lái)Module中找找看
    繼續(xù)看,providerRetrofit該方法要求傳遞一個(gè)OkHttpClient 對(duì)象作為參數(shù),但是OkHttpClient 對(duì)象怎么創(chuàng)建呢? 他也是第三方的框架,我們是沒(méi)權(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;
    }
我們?cè)贛odule中加入OkHttpClient 的創(chuàng)建方式,那么providerRetrofit的參數(shù)就會(huì)由Dagger2去創(chuàng)建好在傳遞進(jìn)去
至此,我們的Retrofit對(duì)象方式也有了,我們需要在哪里使用,就在其聲明上加入@Inject注解
    @Inject
    public Retrofit retrofit;
    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().appModule(new AppModule(this)).build().injectApplication(this);
    }
這樣就可以使用Retrofit對(duì)象了,是不是感覺(jué)很復(fù)雜?但是在你真正使用到的時(shí)候,你會(huì)感覺(jué)為什么Dagger2的管理是這么的舒服,這么讓人放心

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

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

我們來(lái)重點(diǎn)談?wù)勥@倆

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

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

接下來(lái)的篇幅我會(huì)把自己最近學(xué)RxJava的心得全部分享出來(lái),如果我的代碼有問(wèn)題,想法有錯(cuò)誤,或者說(shuō)路是錯(cuò)的,希望大家能批評(píng)指正,萬(wàn)分感謝

Retrofit 和 RxAndroid的完美搭配

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

哇,好像不寫他們?cè)趺词褂梦揖蜔o(wú)法下手碼字了... 腦袋都是懵的...

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

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

接口分類.png

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

接口.png

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

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

這時(shí)候我們就要去考慮,既然所有的接口都會(huì)返回公共的部分,是每次都要寫一遍嗎? 這樣肯定是不正確的做法,于是就想到了繼承,想到了泛型,因?yàn)槲冶救讼刖毩?xí)一下泛型的使用就在此采用泛型了,ResponsetSet要有一個(gè)泛型,這個(gè)類型實(shí)際上就是服務(wù)器給返回的json字段轉(zhuǎn)換成的javabean類型,我們來(lái)解釋一下這樣做的好處.
既然我們選擇了在項(xiàng)目中使用RxAndroid,我們應(yīng)該知道filter這個(gè)方法了,過(guò)濾,對(duì),就是過(guò)濾,現(xiàn)在有這么一個(gè)需求,我需要將所有的非成功的方法全部攔截,不進(jìn)行處理,怎么做? 如果每次服務(wù)器返回?cái)?shù)據(jù)對(duì)應(yīng)你的一個(gè)javabean,你怎么做? 總不能寫一個(gè)個(gè)的過(guò)濾器吧?
這時(shí)候就體現(xiàn)我這么做的好處了,既然所有的請(qǐng)求最終都會(huì)返回一個(gè)ResponseSet,那么過(guò)濾器就唯一了,只需要這樣

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;
    }
}

以后只要我們需要過(guò)濾結(jié)果操作的時(shí)候用這個(gè)對(duì)象就可以了

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

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

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

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

我是這樣做的,定義了兩個(gè)消費(fèi)者 SimpleObserver 和 ProgressObserver
看一下代碼

//就是一個(gè)實(shí)現(xiàn)了Subscriber接口的方法,空實(shí)現(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) {

    }
}


//一個(gè)帶有進(jìn)度條的消費(fèi)者觀察者,是一個(gè)抽象類,抽象方法為injectContext需要在實(shí)例化的時(shí)候注入當(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ù)性的博客,不不,是第一次寫博客,也都是自己在平時(shí)寫代碼的時(shí)候總結(jié)的經(jīng)驗(yàn)和遇到的坑以及看自己糟糕透頂?shù)拇a的一次次改變,下次再寫吧,寫寫rxjava的變換,我遇到的實(shí)際情況是這樣的.

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


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

寫在最后:

第一次寫,有寫的不好的地方請(qǐng)大家批評(píng)指正,我會(huì)努力修改認(rèn)真吸取大家的經(jīng)驗(yàn)為我所用然后反哺大家的,評(píng)論我每天都會(huì)認(rèn)真看,認(rèn)真思考,認(rèn)真回復(fù)的,萬(wàn)份感謝
我是新來(lái)的,余生還請(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)容