本文主要參考此篇文章力作,原文鏈接
[給 Android 開發(fā)者的 RxJava 詳解]
(http://gank.io/post/560e15be2dca930e00da1083#toc_3)
寫在前面
最近在摸索著Rxjava筒主,學(xué)了一大半,但是深知要實踐與理論結(jié)合才能學(xué)得快也記得牢斩个,然而最好的實踐是什么呢?可能是我學(xué)得還不夠深,覺得它的好處在網(wǎng)絡(luò)請求這邊特別明顯雁仲,于是網(wǎng)絡(luò)請求網(wǎng)絡(luò)請求網(wǎng)絡(luò)請求疗我。咆畏。。
發(fā)現(xiàn)有做得更好的東西吴裤,那就是Retrofit與Rxjava這兩個小情侶特別好旧找,所以就再次看了一下這兩個的實踐,發(fā)現(xiàn)麦牺,真的有了一個新大陸~然后就沒什么好說的了钮蛛,搞起唄。
本次實踐基于androidstudio剖膳,所以很多庫的依賴都使用gradle來配置
實踐1 庫的安裝
首先依賴rxjava
compile 'io.reactivex:rxjava:x.y.z'
compile 'io.reactivex:rxandroid:1.0.1'
接下來依賴retrofit
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'//使用Gson解析
compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'//異常時的處理
實踐2 處理場景
我們假設(shè)使用的場景是輸入賬號密碼魏颓,請求網(wǎng)絡(luò)進行賬號驗證,驗證成功就直接登錄吱晒,不過在登錄之前需要獲取到Token琼开,根據(jù)Token和輸入的賬號密碼進行登錄驗證。
所以我們需要兩個方法枕荞,一個獲取token柜候,一個登錄返回結(jié)果。
假設(shè)我們返回的數(shù)據(jù)結(jié)構(gòu)是固定的躏精,就像以下:
{
"code":0,
"message":"success",
"data":{
...
}
}
實踐3 代碼實現(xiàn)
- 首先有基礎(chǔ)的Retrofit和rxjava的請求渣刷,這邊叫RxReService
public interface RxReService {
@POST("user/login")
Observable<String> login(
@Query("token") String token,
@Query("username") String name,
@Query("password") String psd);
}
@POST("token")
Observable<String> getToken();
這是一個接口,通用標(biāo)注的方式傳入url矗烛,使用query方式添加參數(shù)
- 接口寫好了辅柴,看一下實際的調(diào)用箩溃,叫RxReRequest
public class RxReRequest{
private static String BASEURL = "https://www.xxxx.com/";
private static RxReService mRxReService;
}
public static void initRxRe() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASEURL)
.build();
mRxReService = retrofit.create(RxReService.class);
}
public static Observable<String> getToken(){
return mRxReService.getToken();
}
public static Observable<String> login(String name,String psd,String token){
return mRxReService.login(token,name,psd);
}
其實就兩個方法,getToken和login碌嘀,這邊是對其請求進行簡單封裝
- 簡單使用
RxReRequestHelper.login("", "", new ProgressSubscribe<String>(new SubscriberOnNextListener<String>() {
@Override
public void onNext(String result) {
}
}, MainActivity.this));
RxReRequest.getToken().subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String userTokenResultData) {
//對String進行解析
...
//解析完得到toekn
String token = token.getToken();
RxReRequest.login("name","psd",token).subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String comResultResultData) {
//再次解析
...
}
});
}
});
一句我的天啊涣旨,日了狗,這代碼股冗。霹陡。。
莫急莫急止状,待我慢慢道來烹棉。
我們這邊是用比較復(fù)雜的請求才能看出rxjava和retrofit的便利之處
實踐4 封裝
我們首先來說一下
首先是使用gson解析的話,retrofit已經(jīng)做了很好的處理怯疤,只需在initRxRe這個地方添加一個參數(shù)
.addConverterFactory(GsonConverterFactory.create())//添加Gson解析庫
順便說一下這個
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加取消訂閱時取消http請求
這個是當(dāng)取消訂閱時自動取消http請求
有了以上這些浆洗,我們就可以進行后面的工作了
4-1 請求結(jié)束自動解析
添加以上兩個參數(shù)后,我們的RxReService就變成
Observable<ComResult> login(
@Query("token") String token,
@Query("username") String name,
@Query("password") String psd);
@POST("token")
Observable<UserToken> getToken();
返回的類型就直接轉(zhuǎn)換成我們要的最終類型
RxReRequest的兩個方法變成
public static Observable<UserToken> getToken(){
return mRxReService.getToken();
}
public static Observable<ComResult> login(String name, String psd, String token){
return mRxReService.login(token,name,psd);
}
因此最后的使用變成
RxReRequest.getToken().subscribe(new Subscriber<UserToken>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(UserToken userTokenResultData) {
RxReRequest.login("name","psd",userTokenResultData.getToken()).subscribe(new Subscriber<ComResult>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(ComResult comResultResultData) {
}
});
}
});
Gson解析省略掉了集峦,不過看了還是有些別扭我們發(fā)現(xiàn)其實需要的就onErr和onNext兩個方法而已伏社,還有我們解析的會將整個返回值都每次解析出來,但是我們的返回格式是固定的呀塔淤,我每次只要根據(jù)code拿數(shù)據(jù)實例就好了洛口,那就是提前預(yù)解析
4-2 提前預(yù)解析
再啰嗦一下,假設(shè)我們的返回結(jié)果是
{
"code":0,
"message":"success",
"data":{
...
}
}
那我們就可以每次先對結(jié)果進行解析凯沪,如果是code不等于0那就不解析了,所以我們需要這么一個類
public class ResultData<T> {
private int resultCode;
private String message;
private T data;
public int getResultCode() {
return resultCode;
}
public String getMessage() {
return message;
}
public T getData() {
return data;
}
}
因為這個實例數(shù)據(jù)是不固定的买优,所以只能用泛型來做妨马,所以我們的RxReService又變了
@POST("user/login")
Observable<ResultData<ComResult>> login(
@Query("token") String token,
@Query("username") String name,
@Query("password") String psd);
跟著變的RxReRequest
public static Observable<ResultData<UserToken>> getToken() {
return mRxReService.getToken();
}
public static Observable<ResultData<ComResult>> login(String name, String psd, String token) {
return mRxReService.login(token, name, psd);
}
我們在哪里進行預(yù)解析呢?當(dāng)然是用rxjava牛逼閃閃的map關(guān)鍵字了杀赢。我們預(yù)解析的目的是當(dāng)code不為0調(diào)用onErr方法烘跺,而不僅僅是訪問出錯,這樣我們在onNext那邊只需關(guān)心正確的數(shù)據(jù)就是了
我們知道m(xù)ap的參數(shù)
public final <R> Observable<R> map(Func1<? super T, ? extends R> func) {
return lift(new OperatorMap<T, R>(func));
}
所以我們需要定義一個func1來繼承這個Func1
public class HttpResultFunc<T> implements Func1<ResultData<T>, T> {
@Override
public T call(ResultData<T> tResultData) {
if (tResultData.getResultCode() != 0) {
throw new ResultException(tResultData.getResultCode(), tResultData.getMessage());
}
return tResultData.getData();
}
}
我們怎么做的呢脂崔?就是當(dāng)code不等于0就拋出一個異常滤淳,讓onErr接收到這個異常,但是這個異常又要包含到數(shù)據(jù)異常的信息砌左,所以我們還需要自己定義一個異常
public class ResultException extends RuntimeException {
private int errorCode;
private String errMessage;
public ResultException(int errorCode, String errMessage) {
this.errorCode = errorCode;
this.errMessage = errMessage;
}
public int getErrorCode() {
return errorCode;
}
public String getErrMessage() {
return errMessage;
}
}
里面包含了errCode和errMessage
好這邊我們改變一下RxReRequest
public static void getToken(Subscriber<UserToken> subscriber) {
mRxReService.getToken().map(new HttpResultFunc<UserToken>()).subscribe(subscriber);
}
public static void login(String name, String psd, String token, Subscriber<ComResult> subscriber) {
mRxReService.login(token, name, psd).map(new HttpResultFunc<ComResult>()).subscribe(subscriber);
}
我們使用了map對其進行預(yù)解析
好的脖咐,我們看看怎么使用
RxReRequest.getToken(new Subscriber<UserToken>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(UserToken userToken) {
RxReRequest.login("", "", userToken.getToken(), new Subscriber<ComResult>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(ComResult comResult) {
}
});
}
});
這邊onNext里面都是實在的數(shù)據(jù),不會再有數(shù)據(jù)為空時會跑進去了
還有一個小東西汇歹,我們發(fā)現(xiàn)onComplete是沒用的屁擅,那我們也給他去掉吧~~怎么做,自己定義咯
4-3 去掉onComplete
我們自定義一個觀察者产弹,也是抽象類
public abstract class ResutSubscriber<T> extends Subscriber<T>{
@Override
public void onCompleted() {
//結(jié)束
}
}
當(dāng)然繼承免不了派歌,這邊可以做個打印啊還是啥的,當(dāng)然如果這個有用到,就不能這樣做啦
好的胶果,跟著改變的是這邊RxReRequest
public static void getToken(ResutSubscriber<UserToken> subscriber) {
mRxReService.getToken().map(new HttpResultFunc<UserToken>()).subscribe(subscriber);
}
public static void login(String name, String psd, String token, ResutSubscriber<ComResult> subscriber) {
mRxReService.login(token, name, psd).map(new HttpResultFunc<ComResult>()).subscribe(subscriber);
}
這樣使用的話就簡便了一點點
RxReRequest.getToken(new ResutSubscriber<UserToken>() {
@Override
public void onError(Throwable e) {
if (e instanceof ResultException){
Log.e("err",((ResultException)e).getErrMessage() +((ResultException)e).getErrorCode() );
}else {
Log.e("err","請求異常");
}
}
@Override
public void onNext(UserToken userToken) {
RxReRequest.login("", "", userToken.getToken(), new ResutSubscriber<ComResult>() {
@Override
public void onError(Throwable e) {
if (e instanceof ResultException){
Log.e("err",((ResultException)e).getErrMessage() +((ResultException)e).getErrorCode() );
}else {
Log.e("err","請求異常");
}
}
@Override
public void onNext(ComResult comResult) {
}
});
}
});
這樣沒用的代碼就沒掉了匾嘱,不過這個嵌套的網(wǎng)絡(luò)請求看了總是不開心,怎么辦呢早抠。霎烙。。我們知道rxjava還有flatMap贝或,那就用上吧吼过。
4-4使用flatmap處理需要兩級請求的情況
flatmap的解釋很不好說很不好說也不知道怎么說,在這個場景具體的我們可以理解為咪奖,請求登錄的話會先要求獲取token盗忱,獲取到了在執(zhí)行登錄的請求,那就上代碼吧羊赵。
我們把兩個方法合成一個方法
于是RxReRequest的登錄方法里面要做兩件事趟佃,一件是獲取token,一件是登錄昧捷,所以
public static void login(final String name, final String psd, final ResutSubscriber<ComResult> subscriber) {
mRxReService.getToken().map(new HttpResultFunc<UserToken>()).flatMap(new Func1<UserToken, Observable<ComResult>>() {
@Override
public Observable<ComResult> call(UserToken userToken) {
return mRxReService.login(userToken.getToken(), name, psd).map(new HttpResultFunc<ComResult>());
}
}).subscribe(subscriber);
}
最后的使用
RxReRequest.login("", "", new ResutSubscriber<ComResult>() {
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(ComResult comResult) {
}
});
這下闲昭,整個世界都清凈了~~~~
4-5 線程切換
我們知道安卓是不能在主線程進行耗時操作的,包括網(wǎng)絡(luò)請求靡挥,所以我們?nèi)绻瓷厦娴膩碜龅脑挿址昼姃伄惓P蚓兀赃€需要一把殺手锏,就是線程切換跋破。
使用Rxjava可以很方便進行線程切換簸淀,當(dāng)進行網(wǎng)絡(luò)請求時,線程切換到子線程(另外新建一個線程)毒返,請求結(jié)束后切換到主線程
RxReRequest里面的請求
public static void getToken(ResutSubscriber<UserToken> subscriber) {
mRxReService.getToken().map(new HttpResultFunc<UserToken>()).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber);
}
public static void login(final String name, final String psd, final ResutSubscriber<ComResult> subscriber) {
mRxReService.getToken().map(new HttpResultFunc<UserToken>()).flatMap(new Func1<UserToken, Observable<ComResult>>() {
@Override
public Observable<ComResult> call(UserToken userToken) {
return mRxReService.login(userToken.getToken(), name, psd).map(new HttpResultFunc<ComResult>());
}
}).subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).subscribe(subscriber);
}
subscribeOn表示事件發(fā)生時所在的線程租幕,這邊指定在新建的線程,observeOn則指定觀察者發(fā)生的事件的線程拧簸,我們指定在主線程劲绪。
當(dāng)需要loading的時候
因為我們網(wǎng)絡(luò)請求的觀察者都發(fā)生在主線程,所以我們還是自己定義一個觀察者盆赤,里面包含開始和結(jié)束贾富,在開始的地方顯示dialog,在結(jié)束或者出錯的地方做響應(yīng)的取消dialog或者顯示錯誤信息牺六,還可以根據(jù)出錯的errcode進行靈活展示
public abstract class ProgressSubscribe<T> extends Subscriber<T> {
private Context mContext;
private Handler mHandler;
public ProgressSubscribe(Context mContext) {
this.mContext = mContext;
mHandler = new Handler();
}
@Override
public void onCompleted() {
//dismissDialog
}
@Override
public void onStart() {
//showDialog
}\ @Override
public void onError(Throwable e) {
//錯誤時的處理
}
}
這樣錯誤的進行統(tǒng)一處理祷安,我們就只要關(guān)注有數(shù)據(jù)的業(yè)務(wù)邏輯就行了。兔乞。汇鞭。凉唐。
完