一步一步走向泥潭,无法解脱
- 前期项目刚启动的时候,可能项目比较赶,为了尽快做出功能,很多需要细分、封装、解耦合的代码和逻辑没有做到位,越到后期随着业务功能的增加,开发维护就会越困难
- 比如刚开始的时候我们的项目就对接一个登录支付服务器,请求服务器传递的参数以及返回的数据都比较单一,一般都是post方法,媒体类型为json字符串,返回的json数据格式一般就code,msg,data三个字段,data字段根据业务返回不同数据类型
- 后面为了数据安全,需要对数据进行加密解密,比如对post的字符串数据进行RSA,AES、DES或者自定义的加密算法进行加密,还需要对数据进行验签,防止篡改等
- 后面还需要在网络请求中加入请求头header;加入拦截器Interceptor,增加数据压缩gzip等
- 后面我们又新增了三个服务器,而且加密方式也不一样,有的加密有的不加密、有的需要验签有的不需要,返回的数据格式也不统一,这就太搞事情了,如果还在之前简单封装的网络请求代码中加功能,那后面的代码就太难维护
- 之前封装的网络请求代码将数据解析、加解密、请求参数都耦合在一起的,封装的接口也有很多重载的,比如同步异步、请求方式、加密方式、header等,封装了好几个重载方法,一个大类几乎囊括了全部的对外接口和内部功能实现,if语句也是满天飞
下定决心,重新做人
- 前期刚把项目架构进行了优化,终于有时间来优化下网络请求这一块的内容了
- 网络重构这块其实还是比较麻烦的,由于网络这块的api是公共模块,提供给其他模块使用,调用之处就多达几十处,而且参数、方法也不一样,重构起来得特别的小心仔细,重构完成后都得去测试,只有测试才能发现你写的隐藏的漏洞,自己review代码可能会找不出问题,最好能有其他同事review你的代码
- 既然是重构,那么我们就必须考虑多一点,把各种业务需求都考虑进去,尽量把功能业务细分、代码不冗余,可重用性高
开始实践吧
- 本文涉及到的代码都只是基本的框架封装,具体业务逻辑并不涉及
- 为了让网络框架能够尽可能的通用一些,我专门进行泛型话,后续好拓展
- 网络请求的动作就连个一个是请求,一个是取消请求,所以我们定义一个ICallAdapter接口,里面就call()、cancel()连个方法,如下
public interface ICallAdapter { void call(); void cancel(); }
- 然后定义一个abstract 泛型实现类,实现ICallAdapter定义的接口,定义createOkHttpCall()、handleSuccessBody(ResponseBody responseBody)方法,如下
package com.lcq.httplib.client.adapter; import com.lcq.httplib.ErrorEnum; import org.jetbrains.annotations.NotNull; import java.io.IOException; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Response; import okhttp3.ResponseBody; public abstract class AbsCallAdapter<T> implements ICallAdapter { protected final RequestParams<T> requestParams; protected final Call call; public AbsCallAdapter(RequestParams<T> requestParams) { this.requestParams = requestParams; this.call = createOkHttpCall(); } public void call() { if (requestParams.isAsync()) { call.enqueue(new Callback() { @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { callbackFail(ErrorEnum.NET_FAIL); } @Override public void onResponse(@NotNull Call call, @NotNull Response response) { handleResponse(response); } }); } else { try { Response response = call.execute(); handleResponse(response); } catch (IOException e) { e.printStackTrace(); callbackFail(ErrorEnum.NET_FAIL); } } } private void handleResponse(Response response) { if (!response.isSuccessful()) { requestParams.getCallback().onFail(response.code(), response.message()); return; } ResponseBody responseBody = response.body(); if (responseBody == null) { callbackFail(ErrorEnum.BODY_NULL); return; } handleSuccessBody(responseBody); } protected void callbackFail(ErrorEnum errorEnum) { requestParams.getCallback().onFail(errorEnum.getCode(), errorEnum.getMsg()); } public void cancel() { call.cancel(); } abstract protected Call createOkHttpCall(); abstract protected void handleSuccessBody(ResponseBody responseBody); }
- StringCallAdapter 继承AbsCallAdapter<String>,实现createOkHttpCall()和handleSuccessBody(ResponseBody responseBody)方法,如下
package com.lcq.httplib.client.adapter; import android.text.TextUtils; import com.lcq.httplib.ErrorEnum; import java.io.IOException; import java.util.Map; import java.util.concurrent.TimeUnit; import okhttp3.Call; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.ResponseBody; public class StringCallAdapter extends AbsCallAdapter<String> { private static final OkHttpClient okHttpClient; static { okHttpClient = new OkHttpClient.Builder() .readTimeout(9000, TimeUnit.MILLISECONDS) .writeTimeout(9000, TimeUnit.MILLISECONDS) .connectTimeout(9000, TimeUnit.MILLISECONDS) .build(); } public StringCallAdapter(RequestParams<String> requestParams) { super(requestParams); } @Override protected Call createOkHttpCall() { Request.Builder builder = new Request.Builder() .url(requestParams.getUrl()); RequestBody requestBody = requestParams.getRequestBody(); if (requestBody != null) { builder.post(requestBody); } Map<String, String> headers = requestParams.getHeaders(); if (headers != null && headers.size() > 0) { for (Map.Entry<String, String> entry : headers.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) { continue; } builder.addHeader(key, value); } } return okHttpClient.newCall(builder.build()); } @Override public void handleSuccessBody(ResponseBody responseBody) { try { String text = responseBody.string(); requestParams.getCallback().onSuccess(text); } catch (IOException e) { e.printStackTrace(); callbackFail(ErrorEnum.IO_EXCEPTION); } } }
- RequestParams<T> 是封装的网络请求参数,包括url、headers、requestBody、async、callback,这是第一层参数的封装,使用了Builder模式,如下
package com.lcq.httplib.client.adapter; import android.util.Log; import com.lcq.httplib.callback.Callback; import java.util.Map; import okhttp3.RequestBody; public class RequestParams<T> { private final String url; private final Map<String, String> headers; private final RequestBody requestBody; private final boolean async; private Callback<T> callback; private RequestParams(Builder<T> builder) { url = builder.url; headers = builder.headers; requestBody = builder.requestBody; async = builder.async; callback = builder.callBack; } public String getUrl() { return url; } public Map<String, String> getHeaders() { return headers; } public RequestBody getRequestBody() { return requestBody; } public boolean isAsync() { return async; } public Callback<T> getCallback() { if (callback == null) { callback = new Callback<T>() { @Override public void onSuccess(T data) { Log.d("http", "onSuccess,no callback"); } @Override public void onFail(int code, String msg) { Log.d("http", "onFail,no callback"); } }; } return callback; } public static final class Builder<T> { private String url; private Map<String, String> headers; private RequestBody requestBody; private boolean async; private Callback<T> callBack; public Builder() { } public Builder<T> url(String val) { url = val; return this; } public Builder<T> headers(Map<String, String> val) { headers = val; return this; } public Builder<T> requestBody(RequestBody val) { requestBody = val; return this; } public Builder<T> async(boolean val) { async = val; return this; } public Builder<T> callBack(Callback<T> val) { callBack = val; return this; } public RequestParams<T> build() { return new RequestParams<>(this); } } }
- 定义一个通用的携带泛型返回值类型Callback<T> 的回调类,如下
package com.lcq.httplib.callback; public interface Callback<T> { void onSuccess(T data); void onFail(int code, String msg); }
- 定义一个abstract 的泛型AbsClient<T>类,实现ICallAdapter,并定义连个成员变量,一个是ICallAdapter,一个是ClientParams<T>,代码如下
package com.lcq.httplib.client; import com.lcq.httplib.client.adapter.ICallAdapter; public abstract class AbsClient<T> implements ICallAdapter { protected final ClientParams<T> clientParams; protected ICallAdapter callService; public AbsClient(ClientParams<T> clientParams) { this.clientParams = clientParams; } public void call() { if (callService == null) { callService = createCallService(); } callService.call(); } public void cancel() { if (callService == null) { return; } callService.cancel(); } public ClientParams<T> getCallParams() { return clientParams; } protected abstract ICallAdapter createCallService(); }
- ClientParams<T> 封装了url、headers、postObject、post、async、callback,postObject是外部需要传递的数据对象,请求网络之前会根据解析器和加密器进行转化,callback的类型是DataCallback<T>,是结果回调,会根据解析器和加密器继续数据解析,返回泛型定义的类型,代码如下
package com.lcq.httplib.client; import android.util.Log; import com.google.gson.Gson; import com.lcq.httplib.callback.DataCallback; import java.lang.reflect.Type; import java.util.Map; public class ClientParams<T> { private final String url; private final Map<String, String> headers; private final Object postObject; private final boolean post; private final boolean async; private DataCallback<T> callback; private ClientParams(Builder<T> builder) { url = builder.url; headers = builder.headers; postObject = builder.postObject; post = builder.post; async = builder.async; callback = builder.callback; } public String getUrl() { return url; } public Map<String, String> getHeaders() { return headers; } public Object getPostObject() { return postObject; } public boolean isPost() { return post; } public boolean isAsync() { return async; } public DataCallback<T> getCallback() { if (callback == null) { callback = new DataCallback<T>() { @Override public Type getDataType() { return String.class; } @Override public void onSuccess(T data) { Log.d("http", "onSuccess,no DataCallback:"); } @Override public void onFail(int code, String msg) { Log.d("http", "onFail,no DataCallback,code:" + code + ",msg:" + msg); } }; } return callback; } public static final class Builder<T> { private String url; private Map<String, String> headers; private Object postObject; private boolean post; private boolean async = true;//默认异步 private DataCallback<T> callback; public Builder() { } public Builder<T> url(String val) { Log.d("http", "client.url:" + val); url = val; return this; } public Builder<T> headers(Map<String, String> val) { headers = val; return this; } public Builder<T> postObject(Object val) { Log.d("http", "client.postObject:" + (val instanceof String ? val : new Gson().toJson(val))); postObject = val; post = true; return this; } public Builder<T> async(boolean val) { async = val; return this; } public Builder<T> callback(DataCallback<T> val) { callback = val; return this; } public ClientParams<T> build() { return new ClientParams<>(this); } } }
- DataCallback<T> 继承Callback<T> 接口,新定义了一个getDataType() 接口,用于获取期望的数据类型,如下
package com.lcq.httplib.callback; import java.lang.reflect.Type; public interface DataCallback<T> extends Callback<T> { Type getDataType(); }
- 定义一个abstract StringClient<T>类继承AbsClient<T>,实现createCallService()接口,并定义三个虚方法postParamToText(Object param)、encryptor()、dataParser()。代码如下
package com.lcq.httplib.client; import android.util.Log; import com.lcq.httplib.ErrorEnum; import com.lcq.httplib.client.adapter.ICallAdapter; import com.lcq.httplib.client.adapter.RequestParams; import com.lcq.httplib.client.adapter.StringCallAdapter; import com.lcq.httplib.callback.Callback; import com.lcq.httplib.callback.DataCallback; import com.lcq.httplib.encryptor.Encryptor; import com.lcq.httplib.parser.DataParser; import okhttp3.MediaType; import okhttp3.RequestBody; public abstract class StringClient<T> extends AbsClient<T> { protected final Encryptor encryptor; protected final DataParser<T> dataParser; public StringClient(ClientParams<T> clientParams) { super(clientParams); this.encryptor = encryptor(); this.dataParser = dataParser(); } @Override protected ICallAdapter createCallService() { RequestParams.Builder<String> builder = new RequestParams.Builder<String>() .url(clientParams.getUrl()) .headers(clientParams.getHeaders()) .async(clientParams.isAsync()) .callBack(new Callback<String>() { @Override public void onSuccess(String data) { DataCallback<T> callback = clientParams.getCallback(); if (dataParser == null) { callback.onFail(ErrorEnum.DATA_PARSER_NULL.getCode(), ErrorEnum.DATA_PARSER_NULL.getMsg()); return; } if (encryptor != null) { try { data = encryptor.decrypt(data); } catch (Exception e) { e.printStackTrace(); callback.onFail(ErrorEnum.DECRYPT_FAIL.getCode(), ErrorEnum.DECRYPT_FAIL.getMsg()); } } dataParser.parse(data, callback); } @Override public void onFail(int code, String msg) { Log.d("http", "code:" + code + ",msg:" + msg); clientParams.getCallback().onFail(code, msg); } }); if (clientParams.isPost()) { builder.requestBody(createRequestBody(clientParams.getPostObject())); } return new StringCallAdapter(builder.build()); } private RequestBody createRequestBody(Object postParam) { String content = null; if (postParam == null) { Log.d("http", "postParam null"); } else { content = postParamToText(postParam); if (encryptor != null) { content = encryptor.encrypt(content); } } if (content == null) { Log.d("http", "post content is null,set empty String"); content = ""; } return RequestBody.create(content, MediaType.parse("application/json;charset=utf-8")); } protected abstract String postParamToText(Object param); protected abstract Encryptor encryptor(); protected abstract DataParser<T> dataParser(); }
- 定义一个Encryptor接口,用于数请求数据和返回数据的加解密,如下
package com.lcq.httplib.encryptor; public interface Encryptor { String encrypt(String content); String decrypt(String content); }
- 定义一个DataParser<T>类,用于解析返回的数据,如下
package com.lcq.httplib.parser; import com.lcq.httplib.callback.DataCallback; public interface DataParser<T> { void parse(String content, DataCallback<T> callback); }
- 定义一个DefaultStringClient<T> 继承 StringClient<T> 类,这个类就可以对外使用了,其默认实现了postParam 转字符串数据,空加密器,CommonDataParser 通用data数据解析器,如下
package com.lcq.httplib.client; import com.google.gson.Gson; import com.lcq.httplib.encryptor.Encryptor; import com.lcq.httplib.parser.CommonDataParser; import com.lcq.httplib.parser.DataParser; public class DefaultStringClient<T> extends StringClient<T> { public DefaultStringClient(ClientParams<T> clientParams) { super(clientParams); } @Override public String postParamToText(Object param) { if (param == null) { return null; } if (param instanceof String) { return param.toString(); } return new Gson().toJson(param); } @Override protected Encryptor encryptor() { return null; } @Override public DataParser<T> dataParser() { return new CommonDataParser<>(); } }
- 后续我们有不同的服务器请求数据,加解密不一样,数据格式不一样,就继承StringClient<T>这个类就可以了,比如我们要请求天气api接口,就可以这样封装,定义一个WeatherClient类,继承StringClient<Map<String, Object>> ,代码如下
package com.lcq.httplib.client; import com.google.gson.Gson; import com.lcq.httplib.encryptor.Encryptor; import com.lcq.httplib.parser.DataParser; import com.lcq.httplib.parser.WeatherParser; import java.util.Map; public class WeatherClient extends StringClient<Map<String, Object>> { public WeatherClient(ClientParams<Map<String, Object>> clientParams) { super(clientParams); } @Override protected String postParamToText(Object param) { if (param == null) return null;//自己根据服务端协议,将param转换为需要的post文本 return new Gson().toJson(param); } @Override protected Encryptor encryptor() { return null; } @Override protected DataParser<Map<String, Object>> dataParser() { return new WeatherParser(); } }
- 在定义一个天气解析器WeatherParser,实现DataParser<Map<String, Object>> 接口,如下
package com.lcq.httplib.parser; import com.google.gson.Gson; import com.lcq.httplib.callback.DataCallback; import java.util.Map; public class WeatherParser implements DataParser<Map<String, Object>> { @Override public void parse(String content, DataCallback<Map<String, Object>> callback) { WeatherInfo weatherInfo = new Gson().fromJson(content, WeatherInfo.class); callback.onSuccess(weatherInfo.getWeatherinfo()); } }
- 然后我们在项目里面进行调用,调用采用链式结构,看起来很简洁,如下
package com.lcq.lcqhttp; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import com.lcq.httplib.callback.MapCallback; import com.lcq.httplib.client.ClientParams; import com.lcq.httplib.client.WeatherClient; import java.util.Map; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void testClient(View view) { new WeatherClient(new ClientParams.Builder<Map<String, Object>>() .url("http://www.weather.com.cn/data/cityinfo/101010100.html") .callback(new MapCallback() { @Override public void onSuccess(Map<String, Object> data) { StringBuilder sb = new StringBuilder("天气信息:"); for (Map.Entry<String, Object> entry : data.entrySet()) { sb.append(entry.getKey()).append(" = ").append(entry.getValue()).append("\n"); Log.d("http", "entry:" + entry.getKey() + "=" + entry.getValue()); } runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, sb.toString(), Toast.LENGTH_LONG).show(); } }); } @Override public void onFail(int code, String msg) { Log.d("http", "onFail.code:" + code + "msg:" + msg); } }) .build()) .call(); } }
请求的返回数据也正常:
- 如果是相同的服务器,请求的数据转化,加密,以及返回的数据的解密,解析基本是不会变化的,如果对一个新的服务器,就定义一个新的类,继承StringClient<T>,根据需要实现Encryptor和DataParser<T>接口就可以了
- 如果需要下载一张图片,我们定义一个BitmapClient类,继承AbsClient<Bitmap> ,实现createCallService()接口,返回一个InputStreamCallAdapter对象,代码如下,BitmapClient:
package com.lcq.httplib.client; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import com.lcq.httplib.client.adapter.ICallAdapter; import com.lcq.httplib.client.adapter.InputStreamCallAdapter; import com.lcq.httplib.client.adapter.RequestParams; import com.lcq.httplib.callback.Callback; import java.io.InputStream; public class BitmapClient extends AbsClient<Bitmap> { public BitmapClient(ClientParams<Bitmap> clientParams) { super(clientParams); } @Override protected ICallAdapter createCallService() { RequestParams.Builder<InputStream> builder = new RequestParams.Builder<InputStream>() .url(clientParams.getUrl()) .async(clientParams.isAsync()) .headers(clientParams.getHeaders()) .callBack(new Callback<InputStream>() { @Override public void onSuccess(InputStream data) { Bitmap bitmap = BitmapFactory.decodeStream(data); clientParams.getCallback().onSuccess(bitmap); } @Override public void onFail(int code, String msg) { clientParams.getCallback().onFail(code, msg); } }); return new InputStreamCallAdapter(builder.build()); } }
InputStreamCallAdapter:
package com.lcq.httplib.client.adapter; import com.lcq.httplib.ErrorEnum; import java.io.InputStream; import okhttp3.Call; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.ResponseBody; public class InputStreamCallAdapter extends AbsCallAdapter<InputStream> { public InputStreamCallAdapter(RequestParams<InputStream> requestParams) { super(requestParams); } @Override public Call createOkHttpCall() { return new OkHttpClient().newCall(new Request.Builder() .url(requestParams.getUrl()) .build()); } @Override protected void handleSuccessBody(ResponseBody responseBody) { try { InputStream inputStream = responseBody.byteStream(); requestParams.getCallback().onSuccess(inputStream); responseBody.close(); } catch (Exception e) { requestParams.getCallback().onFail(ErrorEnum.PARSE_FAIL.getCode(), ErrorEnum.PARSE_FAIL.getMsg()); } } }
- 如果我们还有服务器有AES加密,返回数据格式和DefaultStringClient的一样,可以定义一个 AesClient<T> 继承 DefaultStringClient<T>,重写encryptor()就可以了,重用了DefaultStringClient的解析器CommonDataParser,代码如下:AesClient<T>
package com.lcq.httplib.client; import com.lcq.httplib.encryptor.AesEncryptor; import com.lcq.httplib.encryptor.Encryptor; public class AesClient<T> extends DefaultStringClient<T> { public AesClient(ClientParams<T> clientParams) { super(clientParams); } @Override protected Encryptor encryptor() { return new AesEncryptor(); } }
- 如果后面有post上传文件的需求,也可以很容易的实现,读者可以自己考虑下怎么去实现
- 题外话:如果是做SDK开发的,项目中不能有太多的第三方框架依赖,不然容易引起版本兼容问题,同一个依赖库的不同版本接口都会有不同的变化,好一点的三方库的api应该是兼容低版本的,这次开发过程中我就遇到一个问题,开发的时候用的是高版本的OKHTTP库,将自己的库发布到项目进行使用的时候,报了一个方法找不到的问题,原来是低版本的api没有RequestBody.create(String, MediaType) 这个方法,所以为了兼容,我只能采用RequestBody.create(MediaType,String )这个已被高版本废弃的方法
- 如果你觉得这种封装方式还可以,不妨点个赞哦,哈哈哈哈.....
- 需要参考我这种封装方式的可以访问这个地址去看源码:LcqHttp: 封装了网络请求OKHTTP的框架