前提##
網(wǎng)絡(luò)訪問是一個App的基礎(chǔ)功能勃教,也是非常重要的一塊兄墅;一般我們會使用一些第三方的網(wǎng)絡(luò)組件捎迫,如:volley晃酒、okhttp,xutils窄绒,并通過一定的封裝來實現(xiàn)網(wǎng)絡(luò)請求贝次;
我們的項目已經(jīng)快3年了,從最初彰导,簡單的封裝了一下 xutils的工具類蛔翅,直到現(xiàn)在敲茄,一直在用,雖沒出現(xiàn)過問題山析,但隨著一些其他 網(wǎng)絡(luò)庫的 出現(xiàn)堰燎,想替換確因耦合度太高,造成無法替換笋轨;
通過上圖秆剪,可以看到,只要我們想訪問網(wǎng)絡(luò)爵政,我們直接去調(diào)用 封裝的網(wǎng)絡(luò)仅讽,而不管是在 界面UI,還是Service钾挟,或是MVP中的model洁灵,這樣就造成了。在代碼的任何處掺出,都有可能看到網(wǎng)絡(luò)請求的代碼徽千;
如下:
** 在activity中請求網(wǎng)絡(luò)**
private void initData() {
NetWorkManager.request(this, NetworkConstant.API_GET_MSG_TYPE, new SimpleRequestCallback<String>(null, false, true) {
@Override
public void onStart() {
super.onStart();
// UI 上的控件
mSwipeRefreshLayout.setRefreshing(true);
}
@Override
public void onSuccess(ResponseInfo<String> info) {
super.onSuccess(info);
mSwipeRefreshLayout.setRefreshing(false);
ResponseParser parser = new ResponseParser(info.result, getActivity(), false);
parser.parse(new ResponseParser.ParseCallback() {
在MVP模式(對應(yīng)的M中訪問)
class DetailSonRepo implements DetailSonContract.Repo {
@Override
public void loadData(String processInstanceId, String subCode, String subColumns, final LoadDataCallback<DetailSonModel> callback) {
Map<String, String> params = new HashMap<>();
params.put("processInstanceId", processInstanceId);
params.put("subCode", subCode);
params.put("subColumns", subColumns);
NetWorkManager.request(this, NetworkConstant.API___, new SimpleReqCallbackAdapter<>(new AbsReqCallback<DetailSonModel>(DetailSonModel.class) {
@Override
protected void onSuccess(DetailSonModel detailSonModel, List<DetailSonModel> tArray, String rawData) {
super.onSuccess(detailSonModel, tArray, rawData);
callback.onDataLoaded(detailSonModel);
}
@Override
public void onFailure(String errorMsg, int code) {
super.onFailure(errorMsg);
callback.onDataNotAvailable(errorMsg, code);
}
}), params);
}
通過MVP模式,可將網(wǎng)絡(luò)的請求汤锨,在M層搞定双抽;避免了在界面UI層,去做網(wǎng)絡(luò)請求的事情泥畅;
思考##
上面的層級結(jié)構(gòu)荠诬,是有缺陷的:
- 網(wǎng)絡(luò)模塊耦合度太高琅翻,單獨分離出來位仁,困難;
- 另開一個App方椎,代碼無法直接復(fù)用聂抢,無法移植代碼,造成重復(fù)開發(fā)棠众;
MVP模式
在MVP模式中琳疏,是通過M去操作數(shù)據(jù)的(網(wǎng)絡(luò)、數(shù)據(jù)庫闸拿、緩存)都是在這塊完成空盼,但某個時候,迫于業(yè)務(wù)上開發(fā)壓力新荤,經(jīng)常導(dǎo)致開發(fā)人員違反設(shè)計規(guī)范揽趾,直接在頁面中操作任何數(shù)據(jù);畢竟采用MVP模式開發(fā)苛骨,是會用大量的子類需要創(chuàng)建篱瞎;
basenet模塊
我們思考苟呐,通過一個 basenet 的組件去訪問網(wǎng)絡(luò),這個 組件的目標(biāo)就是純網(wǎng)絡(luò)訪問俐筋,不涉及到任何業(yè)務(wù)(如:解析牵素,加密等);一句話:來了請求澄者,我就請求網(wǎng)絡(luò)笆呆;此模塊依賴于第三方的網(wǎng)絡(luò)組件庫,如:volley粱挡;
basenet通過接口來解耦腰奋,通過統(tǒng)一的抽象類 or 接口,對外實現(xiàn)網(wǎng)絡(luò)請求抱怔,客戶端劣坊,無須關(guān)注具體的實現(xiàn)類;
實踐1(老套路屈留,參數(shù)通過方法傳)##
總體設(shè)計圖
具體實現(xiàn)代碼片段
// 請求接口
public interface IRequest {
public static final Short GET = 0;
public static final Short POST = 1;
/**
* @param reqType 請求方式
* @param url 地址
* @param headers 請求頭
* @param param 請求參數(shù)
*/
void request(final int reqType, final String url, final List<Pair<String, String>> headers, final List<Pair<String, String>> param, final IRequestCallBack callback);
void request(final int reqType, final String url, final List<Pair<String, String>> headers, final List<Pair<String, String>> param, final IRequestCallBack callback);
void request(final int reqType, final String url, final List<Pair<String, String>> headers, final List<Pair<String, String>> param, final IRequestCallBack callback, final long timeout);
}
// 回調(diào)接口
public interface IRequestCallBack<T> {
void onSuccess(T t);
void onFailure(Throwable e);
}
請求抽象類
/**
* 抽象類
* Created by zhaoyu1 on 2017/3/6.
*/
public abstract class AbsRequest implements IRequest {
public static final String CHAR_SET = "UTF-8";
/**
* 生成請求的url地址
*
* @param url
* @param params
* @return
*/
protected String generateUrl(String url, Map<String, String> params) {
StringBuilder sb = new StringBuilder(url);
if (params != null && params.size() > 0) { // GET 請求局冰,拼接url
if (sb.charAt(sb.length() - 1) != '?') { // get 請求 有 ?
sb.append("?");
}
for (Map.Entry<String, String> entry : params.entrySet()) {
try {
sb.append(URLEncoder.encode(entry.getKey(), CHAR_SET)).append("=").append(URLEncoder.encode(entry.getValue(), CHAR_SET)).append("&");
} catch (UnsupportedEncodingException e) {
// NOT_HAPPEND
}
}
sb = sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
Volley Request 類
public class VolleyRequest extends AbsRequest {
private RequestQueue requestQueue;
private static VolleyRequest sRequest;
public static VolleyRequest getInstance() {
if (sRequest == null) {
synchronized (VolleyRequest.class) {
if (sRequest == null) {
sRequest = new VolleyRequest();
sRequest.requestQueue = Volley.newRequestQueue();
}
}
}
return sRequest;
}
@Override
public void request(final int reqType, String url, final Map<String, String> headers, final Map<String, String> params, long timeout, final IRequestCallBack callback) {
StringRequest stringRequest = null;
int tReqType = Request.Method.GET;
String tUrl = url;
switch (reqType) {
case RequestType.GET:
tReqType = Request.Method.GET;
tUrl = generateUrl(url, params);
break;
case RequestType.POST:
tReqType = Request.Method.POST;
break;
}
// 創(chuàng)建請求
stringRequest = new StringRequest(tReqType, tUrl, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
callback.onSuccess(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
callback.onFailure(error.getCause());
}
}) {
// 設(shè)置Header
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> superHeader = super.getHeaders();
if (headers != null && headers.size() > 0) {
superHeader = headers;
}
return superHeader;
}
// 設(shè)置Body參數(shù)
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> tParams = super.getParams();
if (params != null && params.size() > 0) {
tParams = params;
}
return tParams;
}
};
// 設(shè)置此次請求超時時間
if (timeout > 1000) {
stringRequest.setRetryPolicy(new DefaultRetryPolicy((int) timeout, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
}
stringRequest.setTag(url);
requestQueue.add(stringRequest);
}
@Override
public void request(final int reqType, String url, Map<String, String> headers, Map<String, String> param, IRequestCallBack callback) {
this.request(reqType, url, headers, param, 0, callback);
}
@Override
public void request(int reqType, String url, Map<String, String> params, IRequestCallBack callback) {
this.request(reqType, url, null, params, 0, callback);
}
}
客戶端測試調(diào)用
@Test
public void testVolley() {
Context appContext = InstrumentationRegistry.getTargetContext();
getRequest(appContext).request(IRequest.RequestType.GET, "https://www.jd.com", null, new IRequestCallBack<String>() {
@Override
public void onSuccess(String o) {
Log.e("volley", o.toString());
}
@Override
public void onFailure(Throwable e) {
}
});
SystemClock.sleep(500);
}
實踐2(改由builder模式)##
經(jīng)過上面的一系列步驟,我們完成了一個簡單的基于Volley的請求封裝灌危,可以實現(xiàn)訪問接口了康二;和同事討論過后,覺得可用 Builder 建造者模式來重新構(gòu)建勇蝙,于是沫勿,經(jīng)常改造,有了第二版味混,各種網(wǎng)絡(luò)請求的參數(shù)产雹,不再有 方法去傳,而改由builder去構(gòu)造翁锡;
修改后的接口
// IRequest 接口
public interface IRequest {
interface RequestType {
int GET = 0;
int POST = 1;
}
/**
* 執(zhí)行請求蔓挖,默認是 get 方式
*/
void request();
/**
* 取消網(wǎng)絡(luò)請求
*/
void cancel();
}
// AbsRequest抽象類
public abstract class AbsRequest implements IRequest {
public static final String CHAR_SET = "UTF-8";
/**
* url 地址
*/
protected String mUrl;
/**
* 參數(shù)
*/
protected Map<String, String> mParams;
/**
* 請求頭信息
*/
protected Map<String, String> mHeader;
/**
* 本地請求超時時間
*/
protected long mTimeOut;
/**
* 請求標(biāo)記
*/
protected Object mTag;
/**
* 回調(diào)
*/
protected IRequestCallBack mCallBack;
/**
* 請求方式
*/
protected int mReqType;
// 通過builder來構(gòu)造
protected AbsRequest(Builder builder) {
this.mUrl = builder.mUrl;
this.mCallBack = builder.mCallBack;
this.mTag = builder.mTag;
this.mTimeOut = builder.mTimeOut;
this.mReqType = builder.mReqType;
this.mParams = builder.mParams;
this.mHeader = builder.mHeader;
}
@Override
public final void request() {
switch (mReqType) {
case RequestType.GET:
get();
break;
case RequestType.POST:
post();
break;
}
}
/**
* 執(zhí)行g(shù)et方式
*/
protected abstract void get();
/**
* 執(zhí)行post方式
*/
protected abstract void post();
/**
* 生成請求的url地址
*
* @param url
* @param params
* @return
*/
protected String generateUrl(String url, Map<String, String> params) {
StringBuilder sb = new StringBuilder(url);
if (params != null && params.size() > 0) { // GET 請求,拼接url
if (sb.charAt(sb.length() - 1) != '?') { // get 請求 有 ?
sb.append("?");
}
for (Map.Entry<String, String> entry : params.entrySet()) {
try {
sb.append(URLEncoder.encode(entry.getKey(), CHAR_SET)).append("=").append(URLEncoder.encode(entry.getValue(), CHAR_SET)).append("&");
} catch (UnsupportedEncodingException e) {
// NOT_HAPPEND
}
}
sb = sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
// 抽象的建造者 Builder
public static abstract class Builder {
/**
* url 地址
*/
private String mUrl;
/**
* 參數(shù)
*/
private Map<String, String> mParams;
/**
* 請求頭信息
*/
private Map<String, String> mHeader;
/**
* 本地請求超時時間
*/
private long mTimeOut;
/**
* 請求標(biāo)記
*/
private Object mTag;
/**
* 回調(diào)
*/
private IRequestCallBack mCallBack;
/**
* 請求方式
*/
private int mReqType;
public Builder() {
}
public Builder url(String url) {
this.mUrl = url;
return this;
}
public Builder body(Map<String, String> params) {
this.mParams = params;
return this;
}
public Builder headders(Map<String, String> headers) {
this.mHeader = headers;
return this;
}
public Builder timeout(long time) {
this.mTimeOut = time;
return this;
}
public Builder tag(Object tag) {
this.mTag = tag;
return this;
}
public Builder callback(IRequestCallBack callBack) {
this.mCallBack = callBack;
return this;
}
/**
* @param reqType {@link IRequest.RequestType}中常量
* @return
*/
public Builder type(int reqType) {
this.mReqType = reqType;
return this;
}
public abstract AbsRequest build();
}
}
接口我們看一下 修改后的Volley Request
public class VolleyRequest extends AbsRequest {
private static RequestQueue requestQueue;
private Request mRequest;
private VolleyRequest(Builder builder) {
super(builder);
}
@Override
protected void get() {
realRequest(Request.Method.GET);
}
@Override
protected void post() {
realRequest(Request.Method.POST);
}
private void realRequest(final int reqType) {
int tReqType = Request.Method.GET;
String tUrl = mUrl;
switch (tReqType) {
case Request.Method.GET:
tReqType = Request.Method.GET;
tUrl = generateUrl(mUrl, mParams);
break;
case Request.Method.POST:
tReqType = Request.Method.POST;
break;
}
mRequest = new StringRequest(tReqType, tUrl, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
mCallBack.onSuccess(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mCallBack.onFailure(error);
}
}) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> superHeader = super.getHeaders();
if (mHeader != null && mHeader.size() > 0) {
superHeader = mHeader;
}
return superHeader;
}
// 設(shè)置Body參數(shù)
@Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> tParams = super.getParams();
if (mParams != null && mParams.size() > 0 && reqType == Request.Method.POST) {
tParams = mParams;
}
return tParams;
}
};
// 設(shè)置此次請求超時時間
if (mTimeOut > 1000) {
mRequest.setRetryPolicy(new DefaultRetryPolicy((int) mTimeOut, 0, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
}
mRequest.setTag(mTag);
requestQueue.add(mRequest);
}
@Override
public void cancel() {
if (mRequest != null) {
mRequest.cancel();
} else if (mTag != null) {
requestQueue.cancelAll(mTag);
}
}
// 實現(xiàn)建造者
public static class Builder extends AbsRequest.Builder {
private Context mCtx;
// volley request 為單例
private VolleyRequest sRequest;
public Builder(Context ctx) {
this.mCtx = ctx;
}
@Override
public AbsRequest build() {
if (sRequest == null) {
synchronized (VolleyRequest.class) {
if (sRequest == null) {
sRequest = new VolleyRequest(this);
requestQueue = Volley.newRequestQueue(mCtx);
}
}
}
return sRequest;
}
}
}
客戶端的測試代碼(簡潔的鏈?zhǔn)骄幊?:
Context appContext = InstrumentationRegistry.getTargetContext();
AbsRequest req = new VolleyRequest.Builder(appContext).url("http://www.jd.com")
.timeout(2000).tag("hello")
.callback(new IRequestCallBack() {
@Override
public void onSuccess(Object o) {
Log.e("volley", o.toString());
}
@Override
public void onFailure(Throwable e) {
Log.e("volley", e.toString());
}
}).build();
req.request();
SystemClock.sleep(300);