Rxjava + RetroFIT +OkHttp package

preface

This article, assuming a basic understanding of RxJava, Retrofit, and OkHttp, focuses on how to elegantly encapsulate a web request framework for use in a real-world project, using a project as an example.

Problems to be solved

  1. Switch network request addresses. For example, the project has the partition function. Different partitions correspond to different server addresses
  2. The format of the interface returned by the server is different. Some interfaces return THE JSON format, while others return String format
  3. How can I add fixed parameters, such as version number and channel information, to the URL address
  4. How do I add dynamic parameters to a URL address
  5. Whether to add logs for network requests
  6. Whether to add header(sessionId)
  7. Network requests can be configured to display network loading animations
  8. Encapsulating Observalbe’s data processing steps can dynamically change the process of data processing
  9. Encapsulate error handling to distinguish network errors from application-layer response errors.

Elegant packaging

Connect with each other

In the three-way relationship, Retrofit is primarily responsible for interfacing network requests, handing the actual network requests and responses to OkHttp, while RxJava plays the role of data processing after the data response in the network framework. Take a simple analogy: suppose the user needs to fly from Shanghai to Zhongguancun in Beijing for business. He needs to check in, take a plane, and go to Zhongguancun after arriving in Beijing. Check-in is handled by Retrofit, the actual transportation is done by aircraft (Okhttp), and the customer arrives (data return) to select the vehicle (RtHttp) to zhongguancun.

The design



As can be seen from the design drawing, RtHttp is the total network request entry:

Problems 1 and 2 were assigned to Retrofit Builder mode.

Questions 3, 4, 5, 6 go to OkHttpClient’s Builer;

Question 7 will be encapsulated in the RtHttp class;

Problem 8 is solved by Observable Builder in BaseApi.

Problem 9 is solved by ApiSubscriber’s OnError method.

Design code

use

First, let’s look at the encapsulated network request usage:

Rthttp.with (this) // Set context.setShowWaitingDialog (true) // Set to display the network loading animation SetObservable (MobileApi. The response (map, ProtocolUtils PROTOCOL_MSG_ID_LOGIN)) / / MobileApi. The response returns a Observalbe . Subscriber(new ApiSubscriber() {// Set subscriber, ApiSubscriber encapsulates subscriber; @override public void onNext(JSONObject result) {// only implement onNext method // specific business logic}});Copy the code

RtHttp

RtHttp supports chained calls.

Public class RtHttp{public static final String TAG = "RtHttp"; public static RtHttp instance = new RtHttp(); // Singleton mode private Observable Observable; private static Context context; private boolean isShowWaitingDialog; @param ct * @return */ public static RtHttp with(Context ct){WeakReference wr = new WeakReference (ct); context = wr.get(); return instance; } public RtHttp setShowWaitingDialog(Boolean showWaitingDialog) { isShowWaitingDialog = showWaitingDialog; return instance; } public RtHttp setObservable(Observable observable) {this.observable;} public RtHttp setObservable(Observable observable) {this.observable = observable; return instance; } /** Set ApiSubscriber * @param subscriber * @return */ public RtHttp subscriber(ApiSubscriber subscriber){ subscriber.setmCtx(context); / / to the subscriber set the Context, is used to display the network loading animation. The subscriber setShowWaitDialog (isShowWaitingDialog); // controls whether to display the animation observable.subscribe(subscriber); //RxJava method return instance; } /** * Build NetworkApi with retrofit.Builder and okHttpClient.builder */ public static class NetworkApiBuilder{private String baseUrl; Private Boolean isAddSession; // Whether to add sessionID private HashMap addDynamicParameterMap; Private Boolean isAddParameter; 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 ()){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); }}}Copy the code

RtHttp code is concise, NetworkApiBuilder uses the Builder pattern to create NetWorkApi, which can dynamically configure Retrofit and OkHttpClient parameters. Retrofit configurable parameters:

  1. BaseUrl: Different Retrofit can be generated by setting baseUrl
  2. AddConverterFactory: By setting addConverterFactory, the background interface can return different data types, such as JSON and String

OkHttp can add any Interceptor to handle network requests:

  1. HeaderInterceptory add header(sessionID)
  2. ParameterInterceptor is used to add url fixed parameters
  3. DynamicParameterInterceptor add dynamic parameters used in the url
  4. LogInterceptor Users display logs

MobileApi

Let’s look at creating an Observable:

public class MobileApi extends BaseApi{ public static NetworkApi networkApi; public static Observable obserable; Public static NetworkApi getNetworkApi() {// Create NetworkApi if(NetworkApi ==null){NetworkApi = new RtHttp.NetworkApiBuilder().addSession() // add sessionid.addParameter () // add fixed.build(); } return networkApi; } public static Observable getObserable(Observable observable) { obserable = new ObserableBuilder(observable) .addapiException () // Add apiExcetion filter.build (); return obserable; } public static Observable response(HashMap map, int protocolId) { RequestBody body = toBody(map); return getObserable(getNetworkApi().response(protocolId, body)); }}Copy the code

The getNetworkApi() method can create a specific NetworkApi, and getObserable can add specific processing after the data is returned.

NetWorkApi Defines the interface

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);
}
Copy the code

Acid is used to distinguish interface functions. RequestBody is the body parameter of the request.

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;
        }
    }
}
Copy the code

The BaseApi provides methods for toBody and supports converting JSONObject and HashMap into RequestBody. ObserableBuilder is used to process the Observalbe object returned by NetworkApi. Different ObservalBes can be returned using ObserableBuilder. By default, the data request is set in the child thread, and after processing, the OnNext method is returned using the main thread.

WebApi

WebApi: MobileApi: MobileApi: MobileApi: MobileApi: MobileApi: MobileApi: MobileApi: MobileApi: MobileApi: MobileApi: MobileApi: MobileApi

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); }}Copy the code

Parameter getNetworkApi when baseUrl and set dynamic URL parameter map. Instead of using the addApiException method, the getObserable method uses the isWeb() method. As you can see, the changing code is wrapped in a BaseApi subclass. By creating different subclasses, different network request and data processing logic can be realized.

ApiSubscriber

ApiSubscriber encapsulates whether to display loading animations and the default handling of onError ().

public abstract class ApiSubscriber extends Subscriber { private Context mCtx; private WaitingDialog waitingDialog; 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(); @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){// If (e instanceof HttpException){ 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) {/ / parsing exceptions 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); }} /** * Server returned error * @param ex */ protected void onResultError(ApiException ex){switch (ex. GetCode ()){// server returned code default processing 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(); }}Copy the code

ApiThrowExcepitionFun1

AddApiException () can be used in ObservalbeBuilder to add handling to the code returned by the server. Here is the code that throws the exception:

/** * The Http resultCode is used for unified processing, and the Data part of the HttpResult is stripped out and returned to subscriber ** @param subscriber which really needs the Data type. */ public class ApiThrowExcepitionFun1 implements Func1, Observable>{ @Override public Observable call(ResponseInfo responseInfo) { if (responseInfo.getCode()! If code returns something other than 200, throw an ApiException, Error (new ApiException(responseInfo.getCode(),responseInfo.getMessage()))); } return Observable.just(responseInfo.getData()); }}Copy the code

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; }}Copy the code

ApiException

public class ApiException extends Exception { int code; public ApiException(int code,String s) { super(s); this.code = code; } public int getCode() { return code; }}Copy the code

ParameterInterceptor

public class ParameterInterceptor implements Interceptor{ @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); // HttpUrl HttpUrl = request.url().newBuilder() // Use addQueryParameter() to add parameters to the URL .addQueryParameter("userId", CommonData.getUid()+"") .build(); request = request.newBuilder().url(httpUrl).build(); return chain.proceed(request); }}Copy the code

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(); // 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); }}Copy the code

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); Request = requestBuilder.build(); return chain.proceed(request); }}Copy the code

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"); Acid String userId = request.url().queryParameter("userId"); // log specific parameter user 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"; }}}Copy the code

summary

The network request returns the following 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 ":" sports car, worth 8000 gold beans ", "num" : "x1"}, {9, "id" : "name" : "birthday cake. ","num":"x1"},{"id":11,"name":" volcano of love ", Worth 80000 gold beans, "" num" : "x1"}, {" id ": 15," name ":" 68888 gold beans ", "num" : "68888"}, {" id ": 63," name ":" cool border, speak different! ", "num" : "x1"}, {" id ": 25," name ":" the chance of lucky stone, five minutes of ascension ", "num" : "x1"}, {" id ": 28," name ":" 1888 sunshine, Worth 944 gold beans, "" num" : "1888"}, {" id ": 30," name ":" bicycle, worth 80000 gold beans ", "num" : "x1"}, {" id ": 59," name ":" red rose, Worth 1000 gold beans, "" num" : "10"}, {" id ": 34," name ":" super beautiful treasure box, worth 700000 gold beans ", "num" : "x1"}, {" id ": 36," name ":" flower elves, 520, The goddess of love, "" num" : "x520"}, {" id ": 38," name ":" lucky star, can greatly improve within 30 minutes ", "num" : "x1"}, {" id ": 40," name ":" a diamond necklace, ","num":"x3"},{"id":42,"name":" Worth 2400 gold beans, "" num" : "x3"}], "resetTime" : 6, "userId" : 306448537}, "MSG" : "OK", "result" : 1})Copy the code

The above is the encapsulated code. By using the Builder pattern, you can create different Networkapi instances to meet the requirements of the project and better respond to the changing requirements.

reference

Handling API exceptions in RxJava Best practices combined with Retrofit for Android developers RxJava full details Retrofit+RxJava+OkHttp chain encapsulation HTTP Protocol introduction OkHttp website