前言
本文假设读者对RxJava、Retrofit和OkHttp具有基本的了解,以项目为例,重点讲解如何优雅的封装一个网络请求框架,以适应实际项目中的使用需求。
要解决的问题
- 支持切换网络请求地址,例如项目有分区功能,不同的分区对应不同的服务器地址
- 服务器返回接口格式不一致,有的接口是返回json格式,有的返回String格式
- url地址中,包含固定的参数,例如版本号,渠道信息等,怎么统一添加
- url地址中,包含动态地参数,如何动态添加
- 是否添加网络请求的Log
- 是否添加header(sessionId)
- 网络请求可配置是否显示网络加载动画
- 封装Observalbe进行数据处理的步骤,可动态改变数据处理的过程
- 封装错误处理,区分网络错误和应用层响应错误。
优雅封装
相互联系
在三者的关系中,Retrofit主要负责将网络请求接口化,真正的网络请求和响应交给OkHttp,而RxJava在网络框架中扮演数据响应后进行数据处理的角色。做个简单比喻:假设用户需要从上海飞往北京中关村办事,需要办理登机、做飞机、到达北京后前往中关村。登机手续由Retrofit负责,真正的运输是由飞机(Okhttp)完成,客户到达(数据返回)选择交通工具(RtHttp)到达中关村。
设计图
从设计图中可看出,RtHttp为网络请求总入口:
问题1、2交给Retrofit Builder模式解决;
问题3、4、5、6交给OkHttpClient的Builer解决;
问题7将封装到RtHttp类中;
问题8由BaseApi中的Observable Builder解决;
问题9由ApiSubscriber的OnError方法解决。
设计代码
使用
首先,我们来看封装后的网络请求使用:
RtHttp.with(this) //设置Context
.setShowWaitingDialog(true) //设置显示网络加载动画
.setObservable(MobileApi.response(map,ProtocolUtils.PROTOCOL_MSG_ID_LOGIN))//MobileApi.response 返回一个Observalbe
.subscriber(new ApiSubscriber<JSONObject>() { //设置Subscriber,ApiSubscriber封装Subscriber;返回JSONObject仅是因为适配替换成Retrofit前的老代码
public void onNext(JSONObject result) { //只实现OnNext方法
//具体业务逻辑
}
});
RtHttp
封装后,RtHttp支持链式调用,我们来看RtHttp的代码:
/**
* 网络请求总入口
*/
public class RtHttp{
public static final String TAG = "RtHttp";
public static RtHttp instance = new RtHttp(); //单例模式
private Observable observable;
private static WeakReference<Context> wrContext;
private boolean isShowWaitingDialog;
/**设置Context,使用弱引用
* @param ct
* @return
*/
public static RtHttp with(Context ct){
wrContext = new WeakReference<Context> (ct);
return instance;
}
/**设置是否显示加载动画
* @param showWaitingDialog
* @return
*/
public RtHttp setShowWaitingDialog(boolean showWaitingDialog) {
isShowWaitingDialog = showWaitingDialog;
return instance;
}
/**设置observable
* @param observable
* @return
*/
public RtHttp setObservable(Observable observable) {
this.observable = observable;
return instance;
}
/**设置ApiSubscriber
* @param subscriber
* @return
*/
public RtHttp subscriber(ApiSubscriber subscriber){
subscriber.setmCtx(wrContext.get()); //给subscriber设置Context,用于显示网络加载动画
subscriber.setShowWaitDialog(isShowWaitingDialog); //控制是否显示动画
observable.subscribe(subscriber); //RxJava 方法
return instance;
}
/**
* 使用Retrofit.Builder和OkHttpClient.Builder构建NetworkApi
*/
public static class NetworkApiBuilder{
private String baseUrl; //根地址
private boolean isAddSession; //是否添加sessionid
private HashMap<String,String> addDynamicParameterMap; //url动态参数
private boolean isAddParameter; //url是否添加固定参数
private Retrofit.Builder rtBuilder;
private OkHttpClient.Builder okBuild;
private Converter.Factory convertFactory;
public NetworkApiBuilder setConvertFactory(Converter.Factory convertFactory) {
this.convertFactory = convertFactory;
return this;
}
public NetworkApiBuilder setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
public NetworkApiBuilder addParameter(){
isAddParameter = true;
return this;
}
public NetworkApiBuilder addSession() {
isAddSession = true;
return this;
}
public NetworkApiBuilder addDynamicParameter(HashMap map) {
addDynamicParameterMap = map;
return this;
}
public NetworkApi build(){
rtBuilder= new Retrofit.Builder();
okBuild = new OkHttpClient().newBuilder();
if(!TextUtils.isEmpty(baseUrl)){
rtBuilder.baseUrl(baseUrl);
}else{
rtBuilder.baseUrl(Mobile.getBaseUrl());
}
if(isAddSession){
okBuild.addInterceptor(new HeaderInterceptor(wrContext.get()));
}
if(isAddParameter){
okBuild.addInterceptor(new ParameterInterceptor());
}
if(addDynamicParameterMap!=null){
okBuild.addInterceptor(new DynamicParameterInterceptor(addDynamicParameterMap));
}
//warning:must in the last intercepter to log the network;
if(Log.isDebuggable()){ //改成自己的显示log判断逻辑
okBuild.addInterceptor(new LogInterceptor());
}
if(convertFactory!=null){
rtBuilder.addConverterFactory(convertFactory);
}else{
rtBuilder.addConverterFactory(GsonConverterFactory.create());
}
rtBuilder.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(okBuild.build());
return rtBuilder.build().create(NetworkApi.class);
}
}
}
RtHttp的代码很简洁,NetworkApiBuilder使用builder模式创建NetWorkApi,可以动态地配置Retrofit和OkHttpClient的参数。
Retrofit可配置参数:
- baseUrl:可通过设置baseUrl产生不同的retrofit
- addConverterFactory:通过设置addConverterFactory可适配后台接口返回不同的数据类型,例如json和String
OkHttp可添加任意Interceptor实现网络请求的处理:
- HeaderInterceptory用于添加header(sessionid)
- ParameterInterceptor用于添加url固定的参数
- DynamicParameterInterceptor用于url添加动态参数
- LogInterceptor 用户显示log
MobileApi
下面我们来看Observable的创建:
public class MobileApi extends BaseApi{
public static NetworkApi networkApi;
public static Observable obserable;
public static NetworkApi getNetworkApi() { //使用NetworkApiBuilder创建networkApi
if(networkApi==null ){
networkApi = new RtHttp.NetworkApiBuilder()
.addSession() //添加sessionId
.addParameter() //添加固定参数
.build();
}
return networkApi;
}
public static Observable getObserable(Observable observable) {
obserable = new ObserableBuilder(observable)
.addApiException() //添加apiExcetion过滤
.build();
return obserable;
}
public static Observable response(HashMap map, int protocolId) {
RequestBody body = toBody(map);
return getObserable(getNetworkApi().response(protocolId, body));
}
}
getNetworkApi()方法可以创建特定的NetworkApi,getObserable添加数据返回后特定的处理。
NetWorkApi接口定义
public interface NetworkApi {
("open/open.do")
Observable<Object> post(@Query("ACID") int acid, @Body RequestBody entery);
("open/open.do")
Observable<ResponseInfo<Object>> response( ("ACID") int acid, RequestBody entery);
}
acid是用于区分接口功能,RequestBody为请求的body参数。
BaseApi
public abstract class BaseApi {
public static RequestBody toBody(HashMap map) {
Gson gson = new Gson();
ImiRequestBean requestBean= new ImiRequestBean();
requestBean.setRequeststamp(ProtocolUtils.getTimestamp());
requestBean.setData(map);
return RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"), gson.toJson(requestBean));
}
public static RequestBody toBody(JSONObject jsonObject) {
return RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"), (jsonObject).toString());
}
public static class ObserableBuilder{
private Observable observable;
private boolean apiException;
private boolean toJSONJbject;
private boolean isWeb;
private Scheduler subscribeScheduler;
private Scheduler obscerveScheduler;
public void setObscerveScheduler(Scheduler obscerveScheduler) {
this.obscerveScheduler = obscerveScheduler;
}
public void setSubscribeScheduler(Scheduler subscribeScheduler) {
this.subscribeScheduler = subscribeScheduler;
}
public ObserableBuilder(Observable o) {
this.observable = o;
}
public ObserableBuilder addApiException(){
apiException = true;
return this;
}
public ObserableBuilder addToJSONObject(){
toJSONJbject = true;
return this;
}
public ObserableBuilder isWeb() {
isWeb = true;
return this;
}
public Observable build(){
if(isWeb){
observable = observable.map(new StringToJSONObjectFun1());
}
if(apiException){
observable = observable.flatMap(new ApiThrowExcepitionFun1());
}
if(toJSONJbject){
observable = observable.map(new ObjectToJSONObjectFun1());
}
if(subscribeScheduler!=null){
observable = observable.subscribeOn(subscribeScheduler);
}else {
observable = observable.subscribeOn(Schedulers.io());
}
if(obscerveScheduler!=null){
observable = observable.observeOn(obscerveScheduler);
}else{
observable = observable.observeOn(AndroidSchedulers.mainThread());
}
return observable;
}
}
}
BaseApi提供toBody的方法,支持将JSONObject和HashMap转换成RequestBody。ObserableBuilder用于处理NetworkApi返回的Observalbe对象。使用ObserableBuilder可返回不同的observalbe。默认设置数据请求在子线程,处理完返回OnNext方法使用主线程。
WebApi
webApi跟MobileApi请求地址以及返回数据的数据都不一样,WebApi返回的数据类型是String,我们来看WebApi的代码:
public class WebApi extends BaseApi {
public static final int ROLLER = 1;
public static final int FRUIT = 2;
public static final int WX = 3;
public static NetworkApi networkApi;
public static Observable observable;
public static NetworkApi getNetworkApi(String baseUrl, HashMap map) {
networkApi = new RtHttp.NetworkApiBuilder()
.setBaseUrl(baseUrl)
.addDynamicParameter(map)
.setConvertFactory(StringConverFactory.create())
.build();
return networkApi;
}
public static NetworkApi getRollerApi(HashMap map) {
return getNetworkApi(Web.getRollerUrl(), map);
}
public static NetworkApi getFruitApi(HashMap map) {
return getNetworkApi(Web.getFruitUrl(), map);
}
public static NetworkApi getWxApi(HashMap map) {
return getNetworkApi(Web.getWXUrl(), map);
}
public static Observable getObserable(Observable observable) {
observable = new ObserableBuilder(observable)
.isWeb()
.build();
return observable;
}
public static Observable webPost(HashMap map, String action, int type) {
NetworkApi networkApi = null;
if (type == ROLLER) {
networkApi = getRollerApi(map);
} else if (type == FRUIT) {
networkApi = getFruitApi(map);
} else if (type == WX) {
networkApi = getWxApi(map);
}
String[] str = action.split("/");
if (str.length == 1) {
observable = networkApi.webPost(str[0]);
} else if (str.length == 2) {
observable = networkApi.webPost(str[0], str[1]);
} else {
return null;
}
return getObserable(observable);
}
}
getNetworkApi的参数时baseUrl和设置动态url参数 的map。getObserable的方法不使用addApiException的方法,而是使用isWeb()的方法。可以看出,变化的代码都封装在BaseApi的子类中。通过创建不同的子类,实现不同的网络请求及数据处理逻辑。
ApiSubscriber
ApiSubscriber封装了是否显示加载动画和对onError()的默认处理。
public abstract class ApiSubscriber<T> extends Subscriber<T> {
private Context mCtx;
private WaitingDialog waitingDialog; //加载dialog
private boolean isShowWaitDialog;
public void setShowWaitDialog(boolean showWaitDialog) {
isShowWaitDialog = showWaitDialog;
}
public void onStart() {
super.onStart();
if(isShowWaitDialog){
showWaitDialog();
}
}
public void setmCtx(Context mCtx) {
this.mCtx = mCtx;
}
public void onCompleted() {
if(isShowWaitDialog){
dismissDialog();
}
}
/**
* 对 onError进行处理
* @param e
*/
public void onError(Throwable e) {
if(isShowWaitDialog){
dismissDialog();
}
Throwable throwable = e;
if(Log.isDebuggable()){
Log.i(RtHttp.TAG,throwable.getMessage().toString());
}
/**
* 获取根源 异常
*/
while (throwable.getCause() != null){
e = throwable;
throwable = throwable.getCause();
}
if(e instanceof HttpException){//对网络异常 弹出相应的toast
HttpException httpException = (HttpException) e;
if(TextUtils.isEmpty(httpException.getMessage())){
ToastUtil.showToast(mCtx, R.string.imi_toast_common_net_error);
}else {
String errorMsg = httpException.getMessage();
if(TextUtils.isEmpty(errorMsg)){
ToastUtil.showToast(mCtx, R.string.imi_toast_common_net_error);
}else {
ToastUtil.showToast(mCtx, errorMsg);
}
}
}else if(e instanceof ApiException){//服务器返回的错误
onResultError((ApiException) e);
}else if(e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException){//解析异常
ToastUtil.showToast(mCtx, R.string.imi_toast_common_parse_error);
}else if(e instanceof UnknownHostException){
ToastUtil.showToast(mCtx, R.string.imi_toast_common_server_error);
}else if(e instanceof SocketTimeoutException) {
ToastUtil.showToast(mCtx, R.string.imi_toast_common_net_timeout);
}else {
e.printStackTrace();
ToastUtil.showToast(mCtx, R.string.imi_toast_common_net_error);
}
}
/**
* 服务器返回的错误
* @param ex
*/
protected void onResultError(ApiException ex){
switch (ex.getCode()){ //服务器返回code默认处理
case 10021:
ToastUtil.showToast(mCtx, R.string.imi_login_input_mail_error);
break;
case 10431:
ToastUtil.showToast(mCtx, R.string.imi_const_tip_charge);
break;
default:
String msg = ex.getMessage();
if(TextUtils.isEmpty(msg)){
ToastUtil.showToast(mCtx, R.string.imi_toast_common_net_error);
}else {
ToastUtil.showToast(mCtx, msg);
}
}
}
private void dismissDialog(){
if(waitingDialog!=null) {
if(waitingDialog.isShowing()) {
waitingDialog.dismiss();
}
}
}
private void showWaitDialog(){
if (waitingDialog == null) {
waitingDialog = new WaitingDialog(mCtx);
waitingDialog.setDialogWindowStyle();
waitingDialog.setCanceledOnTouchOutside(false);
}
waitingDialog.show();
}
}
ApiThrowExcepitionFun1
使用ObservalbeBuilder中通过addApiException()的方可以法添加对服务器返回code的处理,下面来看抛出异常的代码:
/**
* 用来统一处理Http的resultCode,并将HttpResult的Data部分剥离出来返回给subscriber
*
* @param <T> Subscriber真正需要的数据类型,也就是Data部分的数据类型
*/
public class ApiThrowExcepitionFun1<T> implements Func1<ResponseInfo<T>, Observable<T>>{
public Observable<T> call(ResponseInfo<T> responseInfo) {
if (responseInfo.getCode()!= 200) { //如果code返回的不是200,则抛出ApiException异常,否则返回data数据
return Observable.error(new ApiException(responseInfo.getCode(),responseInfo.getMessage()));
}
return Observable.just(responseInfo.getData());
}
}
ResponseInfo
public class ResponseInfo<T> {
private int code;
private String message;
private T data;
private String responsestamp;
public String getResponsestamp() {
return responsestamp;
}
public void setResponsestamp(String responsestamp) {
this.responsestamp = responsestamp;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
ApiException
public class ApiException extends Exception {
int code;
public ApiException(int code,String s) {
super(s);
this.code = code;
}
public int getCode() {
return code;
}
}
ParameterInterceptor
public class ParameterInterceptor implements Interceptor{
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//get请求后面追加共同的参数
HttpUrl httpUrl = request.url().newBuilder() //使用addQueryParameter()在url后面添加参数
.addQueryParameter("userId", CommonData.getUid()+"")
.build();
request = request.newBuilder().url(httpUrl).build();
return chain.proceed(request);
}
}
DynamicParameterInterceptor
public class DynamicParameterInterceptor implements Interceptor{
private HashMap<String, String> map;
public DynamicParameterInterceptor(HashMap<String, String> map) {
this.map = map;
}
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//get请求后面追加共同的参数
HttpUrl.Builder bulider = request.url().newBuilder();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
bulider.addQueryParameter((String) entry.getKey(), (String) entry.getValue());
}
request = request.newBuilder().url(bulider.build()).build();
return chain.proceed(request);
}
}
HeadInterceptor
public class HeaderInterceptor implements Interceptor{
private Context context;
public HeaderInterceptor(Context context) {
this.context = context;
}
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.header("sessionId", CommonData.getUserInfo(context).sessionId); //添加sessionId
Request request = requestBuilder.build();
return chain.proceed(request);
}
}
LogInterceptor
public class LogInterceptor implements Interceptor {
private static final String TAG = "LogInterceptor";
private static final Charset UTF8 = Charset.forName("UTF-8");
public Response intercept(Chain chain) throws IOException {
Log.d(TAG,"before chain,request()");
Request request = chain.request();
Response response;
try {
long t1 = System.nanoTime();
response = chain.proceed(request);
long t2 = System.nanoTime();
double time = (t2 - t1) / 1e6d;
String acid = request.url().queryParameter("ACID"); //本项目log特定参数项目接口acid
String userId = request.url().queryParameter("userId"); //本项目log特定参数用户id
String type = "";
if (request.method().equals("GET")) {
type = "GET";
} else if (request.method().equals("POST")) {
type = "POST";
} else if (request.method().equals("PUT")) {
type = "PUT";
} else if (request.method().equals("DELETE")) {
type = "DELETE";
}
BufferedSource source = response.body().source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
String logStr = "\n--------------------".concat(TextUtils.isEmpty(acid) ? "" : acid).concat(" begin--------------------\n")
.concat(type)
.concat("\nacid->").concat(TextUtils.isEmpty(acid) ? "" : acid)
.concat("\nuserId->").concat(TextUtils.isEmpty(userId) ? "" : userId)
.concat("\nnetwork code->").concat(response.code() + "")
.concat("\nurl->").concat(request.url() + "")
.concat("\ntime->").concat(time + "")
.concat("\nrequest headers->").concat(request.headers() + "")
.concat("request->").concat(bodyToString(request.body()))
.concat("\nbody->").concat(buffer.clone().readString(UTF8));
Log.i(RtHttp.TAG, logStr);
} catch (Exception e) {
throw e;
}
return response;
}
private static String bodyToString(final RequestBody request) {
try {
final Buffer buffer = new Buffer();
request.writeTo(buffer);
return buffer.readUtf8();
} catch (final IOException e) {
return "did not work";
}
}
}
小结
我们来看,网络请求返回的log如下:
RtHttp: -------------------- begin--------------------
POST
acid->
userId->306448537
network code->200
url->http://*******/user/loadInitData?userId=306448537&userSecretKey=ckj5k3vrxao***ekblcru5v3r
time->160.708437
request headers->request->
body->({"data":{"getTime":1,"prize":[{"id":4,"name":"跑车, 价值8000金豆","num":"x1"},{"id":9,"name":"生日蛋糕, 价值80000金豆","num":"x1"},{"id":11,"name":"爱的火山, 价值80000金豆","num":"x1"},{"id":15,"name":"68888金豆","num":"68888"},{"id":63,"name":"炫酷边框,发言与众不同!","num":"x1"},{"id":25,"name":"中幸运石, 5分钟内中奖率中幅提升","num":"x1"},{"id":28,"name":"1888阳光, 价值944金豆","num":"1888"},{"id":30,"name":"自行车, 价值80000金豆","num":"x1"},{"id":59,"name":"红玫瑰, 价值1000金豆","num":"10"},{"id":34,"name":"超级靓号宝箱,价值700000金豆","num":"x1"},{"id":36,"name":"花精灵520个, 女神的最爱","num":"x520"},{"id":38,"name":"幸运星, 30分钟内中奖率极大提升","num":"x1"},{"id":40,"name":"钻石项链, 价值12000金豆","num":"x3"},{"id":42,"name":"水晶鞋, 价值2400金豆","num":"x3"}],"resetTime":6,"userId":306448537},"msg":"OK","result":1})
以上是封装的代码,通过使用Builder模式可以创建不同的Networkapi实例,从而满足项目中的需求及能够更好的应对变化的需求。
引用
Handling API exceptions in RxJava
RxJava 与 Retrofit 结合的最佳实践
给 Android 开发者的 RxJava 详解
Retrofit+RxJava+OkHttp链式封装
HTTP 协议入门
OkHttp官网