本文主要參考此篇文章力作,原文鏈接
[給 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ò)進(jìn)行賬號驗證,驗證成功就直接登錄,不過在登錄之前需要獲取到Token,根據(jù)Token和輸入的賬號密碼進(jìn)行登錄驗證。
所以我們需要兩個方法,一個獲取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,這邊是對其請求進(jìn)行簡單封裝
- 簡單使用
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進(jìn)行解析
...
//解析完得到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請求
有了以上這些,我們就可以進(jìn)行后面的工作了
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é)果進(jìn)行解析,如果是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);
}
我們在哪里進(jìn)行預(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對其進(jìn)行預(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ù)為空時會跑進(jìn)去了
還有一個小東西,我們發(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 線程切換
我們知道安卓是不能在主線程進(jìn)行耗時操作的,包括網(wǎng)絡(luò)請求,所以我們?nèi)绻瓷厦娴膩碜龅脑挿址昼姃伄惓?,所以還需要一把殺手锏,就是線程切換。
使用Rxjava可以很方便進(jìn)行線程切換,當(dāng)進(jìn)行網(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進(jìn)行靈活展示
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) {
//錯誤時的處理
}
}
這樣錯誤的進(jìn)行統(tǒng)一處理,我們就只要關(guān)注有數(shù)據(jù)的業(yè)務(wù)邏輯就行了。。。。
完