網(wǎng)上對這三個開源庫的組合框架代碼已經(jīng)一搜一大把了盛撑,但是都很零碎,需要搜索很多東拼西湊才能寫一套完整的復(fù)合自己需求的框架藻雪。這篇文章就是我自己在封裝過程遇到的各種問題的記錄和總結(jié)仰坦,免得很多人重復(fù)踩坑铺韧。。
一蟹略、封裝后的效果
private void requestTopMovies(int page) {
showWaitingDialog();
Subscriber subscriber = new RxCallback<List<Movie>>() {
@Override
public void onSuccess(List<Movie> movieList) {
if (adapter != null) {
adapter.updateData(movieList);
}
}
@Override
public void onFinished() {
dismissWaitingDialog();
}
};
RxRetrofitClient.getInstance().requestTop250Movies(subscriber, page, 10);
}
二登失、封裝過程
1. 全局Client管理類封裝 RxRetrofitClient(單例)
在這里初始化各種網(wǎng)絡(luò)設(shè)置
/// RxRetrofitClient.java
private void initClient() {
// 創(chuàng)建OkHttpClient
OkHttpClient.Builder builder = new OkHttpClient.Builder()
// 超時設(shè)置
.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS)
// 錯誤重連
.retryOnConnectionFailure(true)
// 支持HTTPS
.connectionSpecs(Arrays.asList(ConnectionSpec.CLEARTEXT, ConnectionSpec.MODERN_TLS)) //明文Http與比較新的Https
// cookie管理
.cookieJar(new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(App.getInstance())));
// 添加各種插入器
addInterceptor(builder);
// 創(chuàng)建Retrofit實例
Retrofit doubanRetrofit = new Retrofit.Builder()
.client(builder.build())
.addConverterFactory(GsonConverterFactory.create())
// .addConverterFactory(FastJsonConvertFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL_DOUBAN)
.build();
// 創(chuàng)建API接口類
doubanApi = doubanRetrofit.create(IDoubanApi.class);
}
private void addInterceptor(OkHttpClient.Builder builder) {
// 添加Header
builder.addInterceptor(new HttpHeaderInterceptor());
// 添加緩存控制策略
File cacheDir = App.getInstance().getExternalCacheDir();
Cache cache = new Cache(cacheDir, DEFAULT_CACHE_SIZE);
builder.cache(cache).addInterceptor(new HttpCacheInterceptor());
// 添加http log
HttpLoggingInterceptor logger = new HttpLoggingInterceptor();
logger.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(logger);
// 添加調(diào)試工具
builder.networkInterceptors().add(new StethoInterceptor());
}
2. Get和Post請求封裝
/**
* 豆瓣 Retrofit API
*
* Created by XiaoFeng on 16/12/20.
*/
public interface IDoubanApi {
@GET("top250")
Observable<DoubanResult<List<Movie>>> getTopMovies(@Query("start") int start, @Query("count") int count);
/**
* Json格式的Post請求(application/json)
*/
@POST("account/update_user_info")
Observable<RESTResult<String>> updateUserInfo(@Body RequestBody body);
/**
* Form格式的Post請求(application/x-www-form-urlencoded)
*/
@FormUrlEncoded
@POST("account/update_user_info")
Observable<RESTResult<String>> updateUserInfo(@FieldMap Map<String, String> params);
}
/// RxRetrofitClient.java
/**
* 獲取豆瓣電影Top250的列表數(shù)據(jù)
*
* @param subscriber 由調(diào)用者傳過來的觀察者對象
* @param page 頁碼
* @param count 每頁個數(shù)
*/
public void requestTop250Movies(Subscriber<List<Movie>> subscriber, int page, int count) {
doubanApi.getTopMovies(page * count, count)
.map(RxUtil.<List<Movie>>handleDoubanResult())
.compose(RxUtil.<List<Movie>>normalSchedulers())
.subscribe(subscriber);
}
/**
* 修改用戶信息
* Json格式
*
* @param subscriber
* @param params
*/
public void updateUserInfo(Subscriber<String> subscriber, Map<String, Object> params) {
xzApi.updateUserInfo(toRequestBody(params))
.map(RxUtil.<String>handleRESTFulResult())
.compose(RxUtil.<String>normalSchedulers())
.subscribe(subscriber);
}
/**
* 修改用戶信息
* Form格式
*
* @param subscriber
* @param params
*/
public void updateUserInfo(Subscriber<String> subscriber, Map<String, Object> params) {
xzApi.updateUserInfo(params)
.map(RxUtil.<String>handleRESTFulResult())
.compose(RxUtil.<String>normalSchedulers())
.subscribe(subscriber);
}
這里有幾個 坑
- 寫Form格式的Post請求時,需要添加 @FormUrlEncoded 注解挖炬,否則編譯器會報錯
- 寫Json格式的Post請求時揽浙,不使用@FieldMap注解,而是使用 @Body 注解,并聲明 RequestBody 類型變量
3. RequestBody封裝
private RequestBody toRequestBody(Map params) {
return RequestBody.create(JSON, toJsonStr(params));
}
private String toJsonStr(Map params) {
return new JSONObject(params).toString();
}
4. 對請求結(jié)果統(tǒng)一封裝 RESTResult
/**
* RESTFul 返回值封裝類
*
* Created by XiaoFeng on 16/12/21.
*/
public class RESTResult<T> {
public static final int FAILURE = 0;
public static final int SUCCESS = 1;
@SerializedName("res")
private int res;
@SerializedName("msg")
private String msg;
@SerializedName("data")
T data;
public int getRes() {
return res;
}
public void setRes(int res) {
this.res = res;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
5. 對請求結(jié)果進行轉(zhuǎn)換和預(yù)處理(map)
/// RxUtil.java
/**
* 對RESTful返回結(jié)果做預(yù)處理馅巷,對邏輯錯誤拋出異常
*
* @param <T>
* @return
*/
public static <T> Func1<RESTResult<T>, T> handleRESTFulResult() {
return new Func1<RESTResult<T>, T>() {
@Override
public T call(RESTResult<T> restResult) {
if (restResult.getRes() != RESTResult.SUCCESS) {
throw new ApiException(restResult.getRes(), restResult.getMsg());
}
return restResult.getData();
}
};
}
6. rxjava線程切換封裝(compose)
/// RxUtil.java
/**
* 普通線程切換: IO -> Main
*
* @param <T>
* @return
*/
public static <T> Observable.Transformer<T, T> normalSchedulers() {
return new Observable.Transformer<T, T>() {
@Override
public Observable<T> call(Observable<T> source) {
return source.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
};
}
這里有一個 坑 :
我把handleRESTFulResult和normalSchedulers封裝到了單獨的幫助類RxUtil中膛虫,這樣在使用map或者compose做轉(zhuǎn)換時,需要顯式寫明返回類型钓猬,不然編譯器會報錯稍刀,這點在很多網(wǎng)上查找的資料中都沒有提及.map(RxUtil.<String>handleRESTFulResult())
.compose(RxUtil.<String>normalSchedulers())
7. 對服務(wù)器返回的邏輯錯誤進行統(tǒng)一攔截和封裝,拋出異常
/**
* 對服務(wù)器返回的邏輯錯誤值進行封裝
*
* Created by XiaoFeng on 16/12/21.
*/
public class ApiException extends RuntimeException {
private static final int USER_NOT_EXIST = 100;
private static final int WRONG_PASSWORD = 101;
private int errorCode;
public ApiException(String detailMessage) {
super(detailMessage);
}
public ApiException(int resultCode) {
this(resultCode, toApiExceptionMessage(resultCode));
}
public ApiException(int resultCode, String detailMessage) {
super(detailMessage);
this.errorCode = resultCode;
}
public int getErrorCode() {
return errorCode;
}
/**
* 映射服務(wù)器返回的自定義錯誤碼敞曹,
* (此時的http狀態(tài)碼在[200, 300) 之間)
*
* @param resultCode
* @return
*/
private static String toApiExceptionMessage(int resultCode) {
String message;
switch (resultCode) {
case USER_NOT_EXIST:
message = "該用戶不存在";
break;
case WRONG_PASSWORD:
message = "密碼錯誤";
break;
default:
message = "未知錯誤";
}
return message;
}
}
8. 對Subscriber的封裝账月,同時對onError異常再次封裝
/**
* 暴露給最上層的網(wǎng)絡(luò)請求回調(diào)處理類
*
* Created by XiaoFeng on 16/12/28.
*/
public abstract class RxCallback<T> extends Subscriber<T> {
/**
* 成功返回結(jié)果時被調(diào)用
*
* @param t
*/
public abstract void onSuccess(T t);
/**
* 成功或失敗到最后都會調(diào)用
*/
public abstract void onFinished();
@Override
public void onCompleted() {
onFinished();
}
@Override
public void onError(Throwable e) {
String errorMsg;
if (e instanceof IOException) {
/** 沒有網(wǎng)絡(luò) */
errorMsg = "Please check your network status";
} else if (e instanceof HttpException) {
/** 網(wǎng)絡(luò)異常,http 請求失敗澳迫,即 http 狀態(tài)碼不在 [200, 300) 之間, such as: "server internal error". */
errorMsg = ((HttpException) e).response().message();
} else if (e instanceof ApiException) {
/** 網(wǎng)絡(luò)正常局齿,http 請求成功,服務(wù)器返回邏輯錯誤 */
errorMsg = e.getMessage();
} else {
/** 其他未知錯誤 */
errorMsg = !TextUtils.isEmpty(e.getMessage()) ? e.getMessage() : "unknown error";
}
Toast.makeText(App.getInstance(), errorMsg, Toast.LENGTH_SHORT).show();
onFinished();
}
@Override
public void onNext(T t) {
onSuccess(t);
}
}
參考:
https://gank.io/post/56e80c2c677659311bed9841
http://tech.glowing.com/cn/glow-android-performance-optimization/
http://www.reibang.com/p/f3f0eccbcd6f
http://wuxiaolong.me/2016/06/18/retrofits/
http://stackoverflow.com/questions/35243785/rxjava-static-generic-utility-method-with-transformer