近些時間,由于接手一個嶄新的項目,就考慮到了使用目前市面上較為流行的一些框架,再三權(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組件集
畫個圖看一下
@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ù)器給的各種接口,將接口分類整理之后可能會是這樣的
接口的聲明按照Retrofit的規(guī)范
至于為什么接口統(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ù)的,萬份感謝
我是新來的慨蓝,余生還請大家多多指教!????