從年前一兩個(gè)月開始,就開始慢慢接觸RxJava+Retrofit,針對以往開發(fā)中遇到的情況,慢慢寫了一個(gè)框架Demo。文章不在進(jìn)行入門介紹,需要了解的同學(xué),可以查看筆者總結(jié)的文章RxJava、Retrofit
分割Response
一般來說,網(wǎng)絡(luò)請求結(jié)果包括以下信息:
{ "message": "操作成功", "code": "1", "object": {} }
我們可以定義一個(gè)對象Response<T>,其中泛型T來表示object,可能是數(shù)組,也可能是對象。code為1(或者其他值,和后臺(tái)商議)表示接口調(diào)用成功,如:登錄成功,注冊成功等;code為其他值,則表示失敗,如登錄失敗等,此時(shí)message便返回對應(yīng)的錯(cuò)誤信息,如密碼錯(cuò)誤等。
如果返回結(jié)果為Response<T>,則每次網(wǎng)絡(luò)請求都要判斷接口是否調(diào)用成功,比較麻煩,我們希望的是:如果接口調(diào)用成功,返回泛型T,即object;如果調(diào)用失敗,則返回code、message信息。因此,需要對返回結(jié)果進(jìn)行分割處理。
分割操作代碼如下:
/**
* 對網(wǎng)絡(luò)接口返回的Response進(jìn)行分割操作
*
* @param response
* @param <T>
* @return
*/
public <T> Observable<T> flatResponse(final Response<T> response) {
return Observable.create(new Observable.OnSubscribe<T>() {
@Override
public void call(Subscriber<? super T> subscriber) {
if (response.isSuccess()) {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext(response.object);
}
} else {
if (!subscriber.isUnsubscribed()) {
subscriber.onError(new APIException(response.code, response.message));
}
return;
}
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
}
});
}
其中response.isSuccess()的代碼如下:
public boolean isSuccess() {
return code.equals(Constant.OK);
}
通過以上代碼,便可實(shí)現(xiàn)分割操作,這樣每次返回結(jié)果都不用通過code來判斷是否成功。
打印請求地址+參數(shù)
有些時(shí)候,為了方便調(diào)試,我們需要將網(wǎng)絡(luò)請求的地址和參數(shù)log出來。由于Retrofit是基于OKHttp的,所以我們需要通過Interceptors來攔截OKHttp來log所需信息。
關(guān)于Interceptors,不再多說,直接附上代碼。代碼來自HttpLoggingInterceptor ,做了簡化。
package com.sunflower.rxandroiddemo.utils;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.internal.Platform;
import okio.Buffer;
/**
* Created by Sunflower on 2016/1/12.
*/
public class HttpLoggingInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
public enum Level {
/**
* No logs.
*/
NONE,
/**
* Logs request and response lines.
* <p/>
* Example:
* <pre>{@code
* --> POST /greeting HTTP/1.1 (3-byte body)
* <p/>
* <-- HTTP/1.1 200 OK (22ms, 6-byte body)
* }</pre>
*/
BASIC,
/**
* Logs request and response lines and their respective headers.
* <p/>
* Example:
* <pre>{@code
* --> POST /greeting HTTP/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
* <p/>
* <-- HTTP/1.1 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }</pre>
*/
HEADERS,
/**
* Logs request and response lines and their respective headers and bodies (if present).
* <p/>
* Example:
* <pre>{@code
* --> POST /greeting HTTP/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* <p/>
* Hi?
* --> END GET
* <p/>
* <-- HTTP/1.1 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <p/>
* Hello!
* <-- END HTTP
* }</pre>
*/
BODY
}
public interface Logger {
void log(String message);
/**
* A {@link Logger} defaults output appropriate for the current platform.
*/
Logger DEFAULT = new Logger() {
@Override
public void log(String message) {
Platform.get().log(message);
}
};
}
public HttpLoggingInterceptor() {
this(Logger.DEFAULT);
}
public HttpLoggingInterceptor(Logger logger) {
this.logger = logger;
}
private final Logger logger;
private volatile Level level = Level.BODY;
/**
* Change the level at which this interceptor logs.
*/
public HttpLoggingInterceptor setLevel(Level level) {
if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
this.level = level;
return this;
}
@Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Level level = this.level;
Request request = chain.request();
if (level == Level.NONE) {
return chain.proceed(request);
}
boolean logBody = level == Level.BODY;
boolean logHeaders = logBody || level == Level.HEADERS;
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
String requestStartMessage = request.method() + ' ' + request.url();
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
}
logger.log(requestStartMessage);
if (logHeaders) {
if (!logBody || !hasRequestBody) {
logger.log("--> END " + request.method());
} else if (bodyEncoded(request.headers())) {
logger.log("--> END " + request.method() + " (encoded body omitted)");
} else if (request.body() instanceof MultipartBody) {
//如果是MultipartBody,會(huì)log出一大推亂碼的東東
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
contentType.charset(UTF8);
}
logger.log(buffer.readString(charset));
// logger.log(request.method() + " (" + requestBody.contentLength() + "-byte body)");
}
}
long startNs = System.nanoTime();
Response response = chain.proceed(request);
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
logger.log(response.code() + ' ' + response.message() + " (" + tookMs + "ms" + ')');
return response;
}
private boolean bodyEncoded(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity");
}
private static String protocol(Protocol protocol) {
return protocol == Protocol.HTTP_1_0 ? "HTTP/1.0" : "HTTP/1.1";
}
}
這樣在初始化Retrofit時(shí),我們可以通過以下代碼來log請求地址+參數(shù)
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Log.i("RxJava", message);
}
});
OkHttpClient client = new OkHttpClient.Builder()
//log請求參數(shù)
.addInterceptor(interceptor)
.build();
結(jié)果如下:
ApiWrapper封裝類
對于一個(gè)APP來說,我們需要建立一個(gè)或者多個(gè)接口(我們先分析一個(gè)接口的情況,下文用APIService來替代),里面是相應(yīng)的網(wǎng)絡(luò)請求,然而不可能每次請求都初始化一個(gè)Retrofit對象,進(jìn)而獲得APIService對象,傳入對應(yīng)參數(shù),進(jìn)行網(wǎng)絡(luò)請求,處理返回結(jié)果。
所以,首先可以新建RetrofitUtil類,用于初始化操作,網(wǎng)絡(luò)結(jié)果分割操作等等;然后新建ApiWrapper封裝類(繼承自RetrofitUtil)。新建ApiWrapper封裝類有什么好處呢?用代碼來說明吧!
比說在APIService中有這樣一個(gè)網(wǎng)絡(luò)請求方法:
@FormUrlEncoded
@POST("api/common/msg.json")
Observable<Response<String>> getSmsCode(@Field("mobile") String mobile, @Field("appType") String appType);
該方法是用來獲取短信驗(yàn)證碼的,需要傳入兩個(gè)參數(shù):手機(jī)號(hào)、app類型(醫(yī)生端or孕婦端)
由于返回結(jié)果為驗(yàn)證碼,即object字段為String類型,所以返回結(jié)果是Response<String>
通過ApiWrapper封裝后,代碼如下:
public Observable<String> getSmsCode(String mobile) {
return getService().getSmsCode(mobile, "GRAVIDA")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<Response<String>, Observable<String>>() {
@Override
public Observable<String> call(Response<String> stringResponse) {
return flatResponse(stringResponse);
}
});
}
其中getService()為父類RetrofitUtil中獲取APIService對象的方法。
這樣的話,在對應(yīng)Activity中調(diào)用起來就很方便了
ApiWrapper wrapper = new ApiWrapper();
wrapper.getSmsCode(mobile)
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String s) {
Log.i(TAG, "call " + s);
}
});
通過以上代碼,我們可以發(fā)現(xiàn)封裝類有以下好處:
- 傳遞某些固定參數(shù),如上述代碼中的
String appType,或者userId等 - 對網(wǎng)絡(luò)請求返回結(jié)果進(jìn)行分割操作
- 可以進(jìn)行線程控制
進(jìn)一步封裝
就只能這樣了么?
public Observable<String> getSmsCode(String mobile) {
return getService().getSmsCode(mobile, "GRAVIDA")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<Response<String>, Observable<String>>() {
@Override
public Observable<String> call(Response<String> stringResponse) {
return flatResponse(stringResponse);
}
});
}
從這個(gè)方法中,我們可以清楚地看到數(shù)據(jù)是如何在一系列操作符之間進(jìn)行轉(zhuǎn)換的。但是以后每個(gè)網(wǎng)絡(luò)請求都將進(jìn)行這樣的重復(fù)操作。
如何將一組操作符重用于多個(gè)數(shù)據(jù)流中呢?例如,因?yàn)橄M诠ぷ骶€程中處理數(shù)據(jù),在主線程中處理結(jié)果,然后分割網(wǎng)絡(luò)請求結(jié)果。所以我會(huì)頻繁使用subscribeOn()、observeOn()、flatMap()。如果我能夠通過重用的方式,將這種邏輯運(yùn)用到我所有的數(shù)據(jù)流中,將是一件多么棒的事。
RxJava提供了一種解決方案:Transformer(有轉(zhuǎn)換器意思),一般情況下可以通過使用操作符Observable.compose()來實(shí)現(xiàn)。
Transformer實(shí)際上就是一個(gè)Func1<Observable<T>, Observable<R>>,換言之就是:可以通過它將一種類型的Observable轉(zhuǎn)換成另一種類型的Observable,和調(diào)用一系列的內(nèi)聯(lián)操作符是一模一樣的。
/**
* http://www.itdecent.cn/p/e9e03194199e
* <p/>
*
* @param <T>
* @return
*/
protected <T> Observable.Transformer<Response<T>, T> applySchedulers() {
return new Observable.Transformer<Response<T>, T>() {
@Override
public Observable<T> call(Observable<Response<T>> responseObservable) {
return responseObservable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1<Response<T>, Observable<T>>() {
@Override
public Observable<T> call(Response<T> tResponse) {
return flatResponse(tResponse);
}
})
;
}
};
}
恩,沒錯(cuò),這一部分內(nèi)容參考了注釋內(nèi)的鏈接,大家可以去看下這篇帖子。
通過上面的方法,我們將Observable<Response<T>>轉(zhuǎn)化成了Observable<T>,并把處理了線程調(diào)度、分割返回結(jié)果等操作組合了起來,達(dá)到了復(fù)用的目的。
現(xiàn)在APIService中getSmsCode()可簡化為:
public Observable<String> getSmsCode(String mobile) {
return getService().getSmsCode(mobile, "GRAVIDA")
.compose(this.<String>applySchedulers());
}
由于要經(jīng)常調(diào)用applySchedulers()方法,可以考慮創(chuàng)造一個(gè)實(shí)例化Transformer,節(jié)省不必要的實(shí)例化對象。代碼如下:
final Observable.Transformer transformer = new Observable.Transformer() {
@Override
public Object call(Object observable) {
return ((Observable) observable).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Func1() {
@Override
public Object call(Object response) {
return flatResponse((Response<Object>)response);
}
})
;
}
};
protected <T> Observable.Transformer<Response<T>, T> applySchedulers() {
return (Observable.Transformer<Response<T>, T>) transformer;
}
Note
.flatMap(new Func1() {
@Override
public Object call(Object response) {
return flatResponse((Response<Object>)response);
}
})
flatResponse()進(jìn)行類型強(qiáng)轉(zhuǎn)的話,應(yīng)該沒問題吧?筆者暫時(shí)不確定,但目前也沒發(fā)現(xiàn)什么問題,,,
封裝Subscriber
在Activity中我們調(diào)用getSmsCode()代碼如下:
ApiWrapper wrapper = new ApiWrapper();
showLoadingDialog();
wrapper.getSmsCode(mobile)
.subscribe(new Subscriber<String>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(String s) {
Log.i(TAG, "call " + s);
}
});
其實(shí),對于大部分請求來說,我們只需處理onNext() 方法,默認(rèn)在onCompleted()方法中hideLoadingDialog();在onError()方法中Toast對應(yīng)的錯(cuò)誤信息。
所以我們可以進(jìn)一步封裝Subscriber,代碼如下:
/**
* 創(chuàng)建觀察者
*
* @param onNext
* @param <T>
* @return
*/
protected <T> Subscriber newSubscriber(final Action1<? super T> onNext) {
return new Subscriber<T>() {
@Override
public void onCompleted() {
hideLoadingDialog();
}
@Override
public void onError(Throwable e) {
if (e instanceof RetrofitUtil.APIException) {
RetrofitUtil.APIException exception = (RetrofitUtil.APIException) e;
showToast(exception.message);
} else if (e instanceof SocketTimeoutException) {
showToast(e.getMessage());
} else if (e instanceof ConnectException) {
showToast(e.getMessage());
}
Log.e(TAG, String.valueOf(e.getMessage()));
//e.printStackTrace();
hideLoadingDialog();
}
@Override
public void onNext(T t) {
if (!mCompositeSubscription.isUnsubscribed()) {
onNext.call(t);
}
}
};
}
在onError()方法中,可以根據(jù)Throwable e的類型進(jìn)行對應(yīng)處理,其中APIException是我們自定義的異常,SocketTimeoutException、ConnectException則是OKHttp返回的異常。
在onCompleted()和onError()中,我們都需要hideLoadingDialog()。
在
subscribe()之后,Observable會(huì)持有Subscriber的引用,這個(gè)引用如果不能及時(shí)被釋放,將有內(nèi)存泄露的風(fēng)險(xiǎn)。所以最好保持一個(gè)原則:要在不再使用的時(shí)候盡快在合適的地方(例如onPause()、onStop()等方法中)調(diào)用unsubscribe()來解除引用關(guān)系,以避免內(nèi)存泄露的發(fā)生。
我們可以在BaseActivity中聲明一個(gè)對象
/**
* 使用CompositeSubscription來持有所有的Subscriptions
*/
protected CompositeSubscription mCompositeSubscription;
在onCreate()方法中初始化:
mCompositeSubscription = new CompositeSubscription();
在onDestroy()中unsubscribe()接觸引用關(guān)系:
@Override
protected void onDestroy() {
super.onDestroy();
//一旦調(diào)用了 CompositeSubscription.unsubscribe(),這個(gè)CompositeSubscription對象就不可用了,
// 如果還想使用CompositeSubscription,就必須在創(chuàng)建一個(gè)新的對象了。
mCompositeSubscription.unsubscribe();
}
在Activity中調(diào)用網(wǎng)絡(luò)請求時(shí):
Subscription subscription = wrapper.getSmsCode2("15813351726")
.subscribe(newSubscriber(new Action1<String>() {
@Override
public void call(String s) {
Log.i(TAG, "call " + s);
}
}));
mCompositeSubscription.add(subscription);
所以在newSubscriber()中的onNext()方法中,我們需要事先判斷mCompositeSubscription是否已經(jīng)解除了引用。
---20160229更新---
代碼地址在RxAndroidDemo
請看下篇RxJava+Retrofit框架Demo(二)