Android secondary encapsulation network loading framework

Write it up front

During the development, when requesting the network, people more or less use some third-party frameworks, such as Android-Async-HTTP, Volley, XUtils, Okhttp, Retrofit, etc. These frameworks cut down on a lot of our work, while also intruding on our projects.

We review the project code, whether there are more or less such historical legacy problems, third-party framework call chaos, no package, or package unexpected end. If the framework is to be replaced, it is likely to involve major changes to the project.


The need for encapsulation

  • As requirements change or time migrates, some frameworks may no longer meet our requirements, and we need to use a new framework to replace them.
  • The encapsulation of the third-party framework is to achieve the control of the module project. The framework has been replaced at the minimum cost to achieve the control of the project.

You might think that another encapsulation of a third-party framework is unnecessary. That is, you haven’t tried to copy the post code line by line and replace it.

Some people may think, AS is not a batch replacement function, why packaging?

  • First of all, can you make sure that the parameters are exactly the same everywhere you call?
  • Second, why replace it with the roughest way when it can be done in a more elegant way?

Let’s first look at the use of okHttp


The use of Okhttp

OkHttpClient.Builder mBuilder= new OkHttpClient.Builder(). connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(new LoggingInterceptor()) .cache(new Cache(mContext.getExternalFilesDir("okhttp"),cacheSize)); client=mBuilder.build(); Request.Builder builder = new Request.Builder().url(url).tag(option.mTag); builder=configHeaders(builder,option); Request build = builder.build(); client.newCall(build).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e, iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response, iResponseListener); }});Copy the code

Without encapsulation, the okHttp request network would look something like this. Imagine how much work it would take to replace the framework if we all used it this way in our projects.

This approach, however, is something that most people don’t use in projects, or at least encapsulate into a utility class. The encapsulation is completed as follows.

public static void doGet(Context context,String url, Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener){ OkHttpClient.Builder mBuilder= new OkHttpClient.Builder(). connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(new LoggingInterceptor()) .cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize)); OkHttpClient cilent = mBuilder.build(); Request.Builder builder = new Request.Builder().url(url); Request build = builder.build(); cilent.newCall(build).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e, iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response, iResponseListener); }}); } public static void doPost(Context context,String url, Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener) { OkHttpClient.Builder mBuilder= new OkHttpClient.Builder(). connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(new LoggingInterceptor()) .cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize)); OkHttpClient cilent = mBuilder.build(); url= NetUtils.checkUrl(url); final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url); FormBody.Builder builder = new FormBody.Builder(); FormBody formBody = builder.build(); Request.Builder requestBuilder = new Request.Builder().url(url).post(formBody).tag(option.mTag); Request request = requestBuilder.build(); cilent.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e,iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response,iResponseListener); }}); }Copy the code

This package as a utility class is much better than no package at all, but there are still some problems. Packaged as a tool class, others have full access to your tool class, he can modify the implementation of your tool class at any time, which brings a certain cost to maintenance. Is there a better way?

Most people think of encapsulating a unified network interface, and yes, it is. So, after some thought, we might write the following code.

Void doGet(Context Context, String URL, final Map<String, String> paramsMap, final IResponseListener IResponseListener);Copy the code

What if we need to dynamically configure headers, request tags, what do you do, add parameters?

The interface may look like this:

Public interface NetRequest{void doGet(Context Context, String URL,final Map<String, String> paramsMap,final Map<String, String> headMap,  String tag,final IResponseListener iResponseListener); -}Copy the code

What about later on if you want to configure the cache path, set the request timeout, read the timeout, just add the corresponding parameter to the method?

This approach is not wise and leads to a bloated interface.

In that case, is there a solution?

First, let’s recall which parameters are necessary and which are not, that is, optional.

Necessary options

  • Url, request url
  • ParamsMap, request parameters
  • IResponseListener requests a callback for the result

Optional option

  • Context is usually used to configure some cache or something
  • HeadMap request header
  • Tag Request tag, used to distinguish or cancel network requests
  • ConnectTimeout Indicates the connection timeout time
  • ReadTimeout indicates the readTimeout time
  • WriteTimeout writeTimeout period

Now that we know the required and non-required parameters, how do we extract our interface?

I don’t know if you have noticed that okHttpClient is built, but it extracts all the network configuration and encapsulates it in okHttpClient, Request, and reduces the corresponding parameters when requesting the network. It is concise and flexible.

OkHttpClient.Builder mBuilder= new OkHttpClient.Builder(). connectTimeout(10, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .addInterceptor(new LoggingInterceptor()) .cache(new Cache(context.getExternalFilesDir("okhttp"),cacheSize)); OkHttpClient cilent = mBuilder.build(); Request.Builder builder = new Request.Builder().url(url); client.newCall(builder.build()).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e, iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response, iResponseListener); }});Copy the code

Looking at the OKhttp code, we can do the same. We can encapsulate the non-essential parameters in an entity class NetworkOption, with the necessary parameters as method parameters, so that the interface looks like this.

 void doGet(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener);

void doGet(String url, final Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener);
Copy the code

Is it much cleaner to add parameters to a method?

Next, let’s look at the properties of NetworkOption. Basically, anything that okHTTP can configure, we can configure into it. BaseUrl, request tag, mHeaders. -connectTimeout indicates the connection timeout duration, readTimeout indicates the readTimeout duration, and writeTimeout indicates the writeTimeout duration.

Public class NetworkOption {/** * public String mBaseUrl; public String mTag; public Map<String,String> mHeaders; public NetworkOption(String tag) { this.mTag = tag; } public static final class Builder{ public String tag; public Map<String,String> mHeaders; public String mBaseUrl; public Builder setTag(String tag){ this.tag=tag; return this; } public Builder setHeaders(Map<String,String> headers){ mHeaders=headers; return this; } public Builder setBaseUrl(String baseUrl) { mBaseUrl = baseUrl; return this; } public NetworkOption build(){ NetworkOption networkOption = new NetworkOption(tag); networkOption.mHeaders=mHeaders; networkOption.mBaseUrl=mBaseUrl; return networkOption; }}}Copy the code

At the same time, considering that the configuration of NetworkOption object can be complicated, we use the Builder mode to build. If you are interested, please refer to this blog post. Builder pattern and its application

Advantages of the Builder model

  • Encapsulation is good, decoupling the product itself from the product creation process and shielding the object construction process externally
  • Extensible, if there is a new requirement, only need to add a new concrete builder, no need to modify the original class library code

The final encapsulation implementation

Encapsulation of the NetRequest interface

public interface NetRequest {

    void init(Context context);

     void doGet(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener);

     void doGet(String url, final Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener);


     void doPost(String url, final Map<String, String> paramsMap, final IResponseListener iResponseListener);

     void doPost(String url, final Map<String, String> paramsMap, NetworkOption networkOption,
                 final IResponseListener iResponseListener);
    

     void cancel(Object tag);



}
Copy the code

As you can see, we have several main methods

  • Init method, which is used to configure some initialization parameters
  • DoGet has two methods, one of which is an overload of the other, designed to reduce the passing of method parameters when calling a method
  • DoPost is just like doGet
  • Cancel is primarily used to cancel network requests. In a project, it is best to cancel network requests during Activity or Fragment destruction, otherwise memory leaks or exceptions such as null pointer exceptions may occur.

The realization of the OkHttpRequest

OkHttp configuration is very flexible, so let’s look at how to configure the request header, request parameters, and how to cancel the network request.

Public class implements NetRequest {// ----- @override public void doGet(String url, Map<String, String> paramsMap, final IResponseListener iResponseListener) { doGet(url,paramsMap,null,iResponseListener); } @Override public void doGet(String url, Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener) { url= NetUtils.checkUrl(url); url=NetUtils.appendUrl(url,paramsMap); final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url); Request.Builder builder = new Request.Builder().url(url).tag(option.mTag); builder=configHeaders(builder,option); Request build = builder.build(); getCilent().newCall(build).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e, iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response, iResponseListener); }}); } private Request.Builder configHeaders(Request.Builder builder, NetworkOption option) { Map<String, String> headers = option.mHeaders; if(headers==null || headers.size()==0){ return builder; } Set<Map.Entry<String, String>> entries = headers.entrySet(); for(Map.Entry<String, String> entry: entries){ String key = entry.getKey(); String value = entry.getValue(); // Add request header builder.addheader (key,value); } return builder; } @Override public void doPost(String url, Map<String, String> paramsMap, final IResponseListener iResponseListener) { doPost(url,paramsMap,null,iResponseListener); } private FormBody.Builder configPushParam(FormBody.Builder builder, Map<String, String> paramsMap) { if(paramsMap! =null){ Set<Map.Entry<String, String>> entries = paramsMap.entrySet(); for(Map.Entry<String,String> entry:entries ){ String key = entry.getKey(); String value = entry.getValue(); builder.add(key,value); } } return builder; } @Override public void doPost(String url, Map<String, String> paramsMap, NetworkOption networkOption, final IResponseListener iResponseListener) { url= NetUtils.checkUrl(url); final NetworkOption option=NetUtils.checkNetworkOption(networkOption,url); // Submit formBody.builder Builder = new formBody.builder (); builder=configPushParam(builder,paramsMap); FormBody formBody = builder.build(); Request.Builder requestBuilder = new Request.Builder().url(url).post(formBody).tag(option.mTag); requestBuilder=configHeaders(requestBuilder,option); Request request = requestBuilder.build(); getCilent().newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { handleError(e,iResponseListener); } @Override public void onResponse(Call call, Response response) throws IOException { handleResult(response,iResponseListener); }}); } @Override public void cancel(Object tag) { if(client! =null){ if(client ! = null) {// Wait in the queue to see if there is a request for(Call Call: client.dispatcher().queuedCalls()) { if(call.request().tag().equals(tag)) call.cancel(); } // Check whether there is a request in the request queue for(Call Call: client.dispatcher().runningCalls()) { if(call.request().tag().equals(tag)) call.cancel(); } } } } }Copy the code

The implementation of OKHttpRequest is actually very simple, mainly according to NetworkOption to do the corresponding configuration, not familiar with the use of OKHttpRequest can refer to the blog. OkHttp uses a complete tutorial

VolleyRequest

VolleyRequest implementation also does not say, also according to NetworkOption to do the corresponding configuration, if you are interested, you can click to view Networklibrary

NetworkManger

In consideration of the possibility of changing the framework in the project, we use the simple factory mode to achieve the convenience of changing the framework at any time.

The UMl class diagram is shown below

  • NetRequest unified network interface
  • VolleyRequest, Volley Specific implementation of the request network
  • OkhttpRequest, Okhttp request network implementation
  • NetManger, returns a different network implementation depending on the parameters

Finally, considering that network loading is often used in the project, in order to save resources and improve speed, we combine the singleton mode, and the final implementation is as follows:

public class NetManger { private static NetRequest instance; private static Context mContext; public static NetRequest getRequest(){ return instance; } static HashMap<String,NetRequest> mMap=new HashMap<>(); public static void init(Context context){ instance = OKHttpRequest.getInstance(); mContext = context.getApplicationContext(); instance.init(NetManger.mContext); } // Use reflection as the implementation. One advantage of this is that when we add new implementation classes, we only need to pass the corresponding Class, Public static <T extends NetRequest> NetRequest (Class<T> CLZ){String simpleName = clz.getSimpleName(); NetRequest request = mMap.get(simpleName); if(request ==null){ try { Constructor<T> constructor = clz.getDeclaredConstructor(); constructor.setAccessible(true); request = constructor.newInstance(); request.init(mContext); mMap.put(simpleName,request); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } instance=request; return request; }}Copy the code

You ask me to answer

1) How to use NetManger

First you need to call NetManger init in your Application

NetManger.init(application);
Copy the code

The default implementation is implemented using OKHTTP in singleton mode

NetManger.getRequest().doGet(url, mMap, new IResponseListener() { @Override public void onResponse(String response) { LogUtil.i(TAG,"onResponse: response ="+response); } @Override public void onFail(HttpException httpException) { Log.i(TAG, "onFail: httpException=" +httpException.toString()); }});Copy the code

2) How to switch the implementation of NetManger

Volleyrequest.class is my favorite parameter. If I want to switch to Volley, I just need to pass the volleyRequest.class parameter

NetManger.getRequest(VolleyRequest.class).doPost(url, mMap, New IResponseListener() {@override public void onResponse(String response) {mTv. SetText ("post request \n"+response); LogUtil.i(TAG,"onResponse: response ="+response); } @Override public void onFail(HttpException httpException) { Log.i(TAG, "onFail: httpException=" +httpException.toString()); }});Copy the code
  1. If we want to use XUtils or Retrofit instead of OkHTTP or Volley, is there a way to do that?

The answer is arbitrary, we just need to add our own implementation class Implement NetRequest interface. Then pass the corresponding Class when using the pass parameter.

NetManger.getRequest(XUtilsRequest.class).doPost(url, mMap, New IResponseListener() {@override public void onResponse(String response) {mTv. SetText ("post request \n"+response); LogUtil.i(TAG,"onResponse: response ="+response); } @Override public void onFail(HttpException httpException) { Log.i(TAG, "onFail: httpException=" +httpException.toString()); }})Copy the code

digression

See the above network frame secondary encapsulation, the picture frame, JSON parsing framework encapsulation, you are not also thought of what, know how to encapsulate it.


Networklibrary:github.com/gdutxiaoxu/…


Related to recommend

Observer Design Pattern vs. Event Delegate (Java)

Decorator pattern and its application

Builder pattern and its application

The third party framework of secondary packaging picture — the application of simple factory mode

Android secondary encapsulation network loading framework

Java proxy schema in detail


Finally, I would like to sell the advertisement and welcome you to pay attention to my wechat public number. You can follow it by scanning the qr code below or searching the wechat account Stormjun. Currently, I focus on Android development, mainly sharing Android development related knowledge and some relevant excellent articles, including personal summary, workplace experience and so on.