网上对这三个开源库的组合框架代码已经一搜一大把了,但是都很零碎,需要搜索很多东拼西凑才能写一套完整的复合自己需求的框架。这篇文章就是我自己在封装过程遇到的各种问题的记录和总结,免得很多人重复踩坑。。
一、封装后的效果
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(单例)
在这里初始化各种网络设置
/// RxRetrofitClient.java
private void initClient() {
// 创建OkHttpClient
OkHttpClient.Builder builder = new OkHttpClient.Builder()
// 超时设置
.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);
// 创建Retrofit实例
Retrofit doubanRetrofit = new Retrofit.Builder()
.client(builder.build())
.addConverterFactory(GsonConverterFactory.create())
// .addConverterFactory(FastJsonConvertFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl(BASE_URL_DOUBAN)
.build();
// 创建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);
// 添加调试工具
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的列表数据
*
* @param subscriber 由调用者传过来的观察者对象
* @param page 页码
* @param count 每页个数
*/
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. 对请求结果统一封装 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. 对请求结果进行转换和预处理(map)
/// RxUtil.java
/**
* 对RESTful返回结果做预处理,对逻辑错误抛出异常
*
* @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做转换时,需要显式写明返回类型,不然编译器会报错,这点在很多网上查找的资料中都没有提及.map(RxUtil.<String>handleRESTFulResult())
.compose(RxUtil.<String>normalSchedulers())
7. 对服务器返回的逻辑错误进行统一拦截和封装,抛出异常
/**
* 对服务器返回的逻辑错误值进行封装
*
* 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;
}
/**
* 映射服务器返回的自定义错误码,
* (此时的http状态码在[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异常再次封装
/**
* 暴露给最上层的网络请求回调处理类
*
* Created by XiaoFeng on 16/12/28.
*/
public abstract class RxCallback<T> extends Subscriber<T> {
/**
* 成功返回结果时被调用
*
* @param t
*/
public abstract void onSuccess(T t);
/**
* 成功或失败到最后都会调用
*/
public abstract void onFinished();
@Override
public void onCompleted() {
onFinished();
}
@Override
public void onError(Throwable e) {
String errorMsg;
if (e instanceof IOException) {
/** 没有网络 */
errorMsg = "Please check your network status";
} else if (e instanceof HttpException) {
/** 网络异常,http 请求失败,即 http 状态码不在 [200, 300) 之间, such as: "server internal error". */
errorMsg = ((HttpException) e).response().message();
} else if (e instanceof ApiException) {
/** 网络正常,http 请求成功,服务器返回逻辑错误 */
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.jianshu.com/p/f3f0eccbcd6f
http://wuxiaolong.me/2016/06/18/retrofits/
http://stackoverflow.com/questions/35243785/rxjava-static-generic-utility-method-with-transformer