RxJava+Retrofit+OkHttp 封装

9,063 阅读9分钟

原文链接:rxjava+retrofit+OkHttp封装

前言

本文假设读者对RxJava、Retrofit和OkHttp具有基本的了解,以项目为例,重点讲解如何优雅的封装一个网络请求框架,以适应实际项目中的使用需求。

要解决的问题

  1. 支持切换网络请求地址,例如项目有分区功能,不同的分区对应不同的服务器地址
  2. 服务器返回接口格式不一致,有的接口是返回json格式,有的返回String格式
  3. url地址中,包含固定的参数,例如版本号,渠道信息等,怎么统一添加
  4. url地址中,包含动态地参数,如何动态添加
  5. 是否添加网络请求的Log
  6. 是否添加header(sessionId)
  7. 网络请求可配置是否显示网络加载动画
  8. 封装Observalbe进行数据处理的步骤,可动态改变数据处理的过程
  9. 封装错误处理,区分网络错误和应用层响应错误。

优雅封装

相互联系

在三者的关系中,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() { //设置Subscriber,ApiSubscriber封装Subscriber;返回JSONObject仅是因为适配替换成Retrofit前的老代码
                    @Override
                    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 Context context;
    private boolean isShowWaitingDialog;

    /**设置Context,使用弱引用
     * @param ct
     * @return
     */
    public static RtHttp with(Context ct){
        WeakReference wr = new WeakReference (ct);
        context = wr.get();
        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(context);  //给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 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(context));
            }
            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可配置参数:

  1. baseUrl:可通过设置baseUrl产生不同的retrofit
  2. addConverterFactory:通过设置addConverterFactory可适配后台接口返回不同的数据类型,例如json和String

OkHttp可添加任意Interceptor实现网络请求的处理:

  1. HeaderInterceptory用于添加header(sessionid)
  2. ParameterInterceptor用于添加url固定的参数
  3. DynamicParameterInterceptor用于url添加动态参数
  4. 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 {

    @POST("open/open.do")
    Observable post(@Query("ACID") int acid, @Body RequestBody  entery);

    @POST("open/open.do")
    Observable> response(@Query("ACID") int acid, @Body 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 extends Subscriber {

    private  Context mCtx;
    private WaitingDialog waitingDialog;  //加载dialog
    private boolean isShowWaitDialog;

    public void setShowWaitDialog(boolean showWaitDialog) {
        isShowWaitDialog = showWaitDialog;
    }

    @Override
    public void onStart() {
        super.onStart();
        if(isShowWaitDialog){
            showWaitDialog();
        }
    }

    public void setmCtx(Context mCtx) {
        this.mCtx = mCtx;
    }

    @Override
    public void onCompleted() {
        if(isShowWaitDialog){
            dismissDialog();
        }
    }

    /**
     * 对 onError进行处理
     * @param e
     */
    @Override
    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  Subscriber真正需要的数据类型,也就是Data部分的数据类型
 */
public class ApiThrowExcepitionFun1 implements Func1, Observable>{

    @Override
    public Observable call(ResponseInfo 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 {
    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{
         @Override
         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 map;

    public DynamicParameterInterceptor(HashMap map) {
        this.map = map;
    }
    @Override
    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;
    }
    @Override
    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");

    @Override
    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官网