1. Reasons for writing this blog

Retrofit has become a very popular networking framework because of its ease of use and support for RxJava. So learning how to encapsulate and practice with the Retrofit Web request framework is a great example of how to improve your architecture. And when you successfully encapsulate the first component, you’ll be more comfortable with the task of encapsulating the component again.

2. Main logic of encapsulation


Main logic

Follow this logic diagram step by step to encapsulate the network framework.

2.1 Importing Dependencies
The compile "IO. Reactivex. Rxjava2: rxjava: 2.1.0" / / necessary rxjava2 depend on the compile "IO. Reactivex. Rxjava2: rxandroid: 2.0.1" / / Required rxandRroID dependencies, Cut threads need to use the compile 'com. Squareup. Retrofit2: retrofit: 2.3.0' / / need to depend on the compile retrofit 'com. Squareup. Retrofit2: adapter - rxjava2:2.3.0' / / need to rely on, and Rxjava combination must be used, Mentioned below will compile 'com. Squareup. Retrofit2: converter - gson: 2.3.0' / / need to rely on, Parsing json characters used in the compile 'com. Squareup. Okhttp3: logging - interceptor: 3.8.1' / / not necessary to rely on, the log dependent, if need to print OkHttpLog need to addCopy the code
2.2 Creating a Manger class to manage required apis

Here is a cookie-cutter approach, if you encapsulate a framework for global use, and it needs to have the same lifetime as the entire software. That has two characteristics:

  • Class in Application.
  • Use a simple profit model.
  • Initialize all required parameters in a “management class”.
/** * Created by Zaifeng on 2018/2/28. */ public class NetWorkManager {private static NetWorkManager mInstance; private static Retrofit retrofit; private static volatile ApiService apiService = null; public static NetWorkManager getInstance() { if (mInstance == null) { synchronized (NetWorkManager.class) { if (mInstance == null) { mInstance = new NetWorkManager(); } } } return mInstance; Public void init() {// initialize okhttp OkHttpClient client = new okHttpClient.builder ().build(); Retrofit = new retrofit.builder ().client(client).baseurl (ApiService.HOST) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .build();  } public static Request getRequest() { if (request == null) { synchronized (Request.class) { request = retrofit.create(Request.class); } } return request; }}Copy the code

There’s a new NetWorkManager that initializes OkHttp and Retrofit in the init method. Both initializations are constructor patterns.

Initializing OKHttp extensions The above code just adds the necessary attributes, and more extensions can be added to OKHttp and Retrofit. For example, if you want to add a Log to OkHttp, you can write as follows:

HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
OkHttpClient client = new OkHttpClient.Builder()
                    .addInterceptor(logging)
                    .build();
Copy the code

AddInterceptor () also has many uses, such as encapsulating common parameters. Refer to the following blog: blog.csdn.net/jdsjlzx/art… I won’t say much here.

There are two necessary configurations when initializing Retrofit:

  • The first configuration:.addCallAdapterFactory(RxJava2CallAdapterFactory.create())

    This is used to determine whether your return value is Observable or Call.
Call<String> login(); Observable<String> login();Copy the code

If Call is returned, this configuration may not be added. You must add this configuration if you use Observable. Otherwise will request when will report an error! For details on this configuration, see this blog post. Blog.csdn.net/new_abc/art…

  • Second configuration:.addConverterFactory(GsonConverterFactory.create())

    This configuration converts the JSON string returned by the server into an object. This can be customized to handle the different data returned by the server. Specific how to customize, here also do not explain (because the content is more), you can see the following blog.

    www.jianshu.com/p/5b8b10628…

Initializing Request this is also initializing Request. If you have a Retrofit background and have seen retrofit.create, you should know that Request is an API wrapper class for the server that defines the Request. It uses annotations to declare the requested interface, as described below.

2.3 agreement Response

When parsing the data returned by the server, you need to return the SAME Json format as the server, which is often encountered in development. Usually the server will not return Json format to the front-end, otherwise the front-end parsing will be very troublesome. (So the data structure here requires the front-end and server to agree on a fixed format) The fixed JSON format here is:

{ret:0,data:"",msg:""}
Copy the code

So the fixed Response defined here is:

public class Response<T> { private int ret; // Return code private T data; Private String MSG; Public int getCode() {return ret; } public void setCode(int code) { this.ret = code; } public T getData() { return data; } public void setData(T data) { this.data = data; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; }}Copy the code
2.4 define the Request

As mentioned earlier in NetWorkManager, this is the interface that defines the request server API.

Public interface Request {// Enter the address of the server to be accessed public static String HOST = "https://www.xxx.com/app_v5/"; @POST("? service=sser.getList") Observable<Response<List<javaBean>>> getList(@Query("id") String id); }Copy the code

Here we annotate a Post request with @POST.

2.4 Define ResponseTransformer to handle data and exceptions

This is also a self-defined class, and it is ok to not use this custom class. But for the convenience of packaging after use, or recommended packaging. ResponseTransformer is really a wrapper around “transformations” in Rxjava. When not using ResponseTransformer.

model.getCarList("xxxxx") .compose(schedulerProvider.applySchedulers()) .subscribe(new Consumer<Response<List<JavaBean>>>() { @Override public void accept(Response<List<JavaBean>> listResponse) throws Exception { if(listResponse.getCode() == 200){ List<JavaBean> javaBeans = listResponse.getData(); }else{// exception handling}}}, New Consumer<Throwable>() {@override public void accept(Throwable) throws Exception {// Exception handling}});Copy the code

When using ResponseTransformer:

model.getCarList("xxxxx") .compose(ResponseTransformer.handleResult()) .compose(schedulerProvider.applySchedulers()) .subscribe(new Consumer<List<JavaBean>>() { @Override public void accept(List<JavaBean> carBeans) throws Exception { // List<JavaBean> carBeans}} New Consumer<Throwable>() {@override public void accept(Throwable) throws Exception {// Handle exceptions}});Copy the code

Without ResponseTransformer, you need to determine the code of the Response and then retrieve the data from the Response. More tedious is to determine code==200 code, each request interface needs to be written again. Use ResponseTransformer to further process the parsed Response data (encapsulate the code that determines code==200), directly extract the data processing that needs to be used, and uniformly process the Exception! This is the great use of “transformations” in RxJava, and is used in conjunction with “compose” to reduce code duplication. Specific ResponseTransformer code.

public class ResponseTransformer { public static <T> ObservableTransformer<Response<T>, T> handleResult() { return upstream -> upstream .onErrorResumeNext(new ErrorResumeFunction<>()) .flatMap(new ResponseFunction<>()); } /** * Non-server-generated exceptions, such as no local network requests, Json data parsing errors, etc. * * @param <T> */ private static class ErrorResumeFunction<T> implements Function<Throwable, ObservableSource<? extends Response<T>>> { @Override public ObservableSource<? extends Response<T>> apply(Throwable throwable) throws Exception { return Observable.error(CustomException.handleException(throwable)); ** @param <T> */ private static class ResponseFunction<T> implements */ private static class ResponseFunction<T> implements  Function<Response<T>, ObservableSource<T>> { @Override public ObservableSource<T> apply(Response<T> tResponse) throws Exception { int code = tResponse.getCode(); String message = tResponse.getMsg(); if (code == 200) { return Observable.just(tResponse.getData()); } else { return Observable.error(new ApiException(code, message)); }}}}Copy the code
2.4 handle the Exception

Exception here is divided into two parts:

  • Native exceptions, such as parsing errors, network link errors, and so on.
  • Server generated Excption, such as 404,503, etc. Server returned Excption.

Define ApiException unified handling:

public class ApiException extends Exception { private int code; private String displayMessage; public ApiException(int code, String displayMessage) { this.code = code; this.displayMessage = displayMessage; } public ApiException(int code, String message, String displayMessage) { super(message); this.code = code; this.displayMessage = displayMessage; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getDisplayMessage() { return displayMessage; } public void setDisplayMessage(String displayMessage) { this.displayMessage = displayMessage; }}Copy the code

Handle locally generated exceptions as follows:

Public class CustomException {/** * UNKNOWN error */ public static final int UNKNOWN = 1000; Public static final int PARSE_ERROR = 1001; Public static final int NETWORK_ERROR = 1002; Public static final int HTTP_ERROR = 1003; public static ApiException handleException(Throwable e) { ApiException ex; If (e instanceof JsonParseException | | e instanceof JSONException | | e instanceof ParseException) {/ / parsing errors ex = new ApiException(PARSE_ERROR, e.getMessage()); return ex; } else if (e instanceof ConnectException) {ex = new ApiException(NETWORK_ERROR, LLDB etMessage()); return ex; } else if (e instanceof UnknownHostException | | e instanceof SocketTimeoutException) {/ / connection error ex = new ApiException(NETWORK_ERROR, e.getMessage()); return ex; } else {ex = new ApiException(UNKNOWN, LLDB etMessage()); return ex; }}}Copy the code

How do these exceptions get thrown? Take a look at the ResponseTransformer code that handles exceptions mentioned above. Both exceptions are emitted by Observable.error.

/ / local exception handling observables. Error (CustomException. HandleException (throwable)); Int code = tresponse.getCode (); String message = tResponse.getMsg(); if (code == 200) { return Observable.just(tResponse.getData()); } else { return Observable.error(new ApiException(code, message)); }Copy the code
2.5. Thread switching

Having worked with Rxjava, you should be familiar with Rxjava switching, which wraps the thread switch into a separate class and uses it in conjunction with compose.

public class SchedulerProvider implements BaseSchedulerProvider { @Nullable private static SchedulerProvider INSTANCE; // Prevent direct instantiation. private SchedulerProvider() { } public static synchronized SchedulerProvider getInstance() { if (INSTANCE == null) { INSTANCE = new SchedulerProvider(); } return INSTANCE; } @Override @NonNull public Scheduler computation() { return Schedulers.computation(); } @Override @NonNull public Scheduler io() { return Schedulers.io(); } @Override @NonNull public Scheduler ui() { return AndroidSchedulers.mainThread(); } @NonNull @Override public <T> ObservableTransformer<T, T> applySchedulers() { return observable -> observable.subscribeOn(io()) .observeOn(ui()); }}Copy the code

3, in actual combat

Once packaged, it’s time to actually use it. However, in actual development, the logic may be implemented first and then encapsulated, and many things need to be packaged after the code is written.

3.1. Initialization

BaseApplication.

public class BaseApplication extends Application { @Override public void onCreate() { super.onCreate(); NetWorkManager.getInstance().init(); }}Copy the code
3.2. Build the MVP structure and use it in Presenter.
Disposable disposable = model.getCarList("xxxxxx") .compose(ResponseTransformer.handleResult()) Compose (schedulerProvider. ApplySchedulers ()). The subscribe (carBeans - > {/ / processing data direct access to a List < JavaBean > carBeans view.getDataSuccess(); }, throwable -> {// handle exception view.getDatafail (); }); mDisposable.add(disposable);Copy the code

Two callbacks, after which the View layer makes corresponding UI changes. The use of compose is not explained here. In this case, Compose combines the switch and transform encapsulation of the thread to reduce redundant code. About compose link: www.jianshu.com/p/3d0bd5483… Final directory structure:


The directory structure

4, need to pay attention to the place

1, a lot of times need to achieve the effect to carry out encapsulation. 2, when encapsulating the framework, encapsulate the logical layer, do not write the business layer code in the logical layer.

Source code address: github.com/AxeChen/ret…