市場上的絕大多數(shù)APP,都離不開網(wǎng)絡數(shù)據(jù)請求,從注冊到登錄、提交/拉取數(shù)據(jù),都是我們必須要面對的問題,可見網(wǎng)絡數(shù)據(jù)請求在APP產(chǎn)品中的重要性。那么網(wǎng)絡請求在企業(yè)級架構(gòu)中,應該怎樣處理是比較合理的呢?這篇文章我們來聊一聊網(wǎng)絡數(shù)據(jù)請求的架構(gòu)處理。
可能有人會問:網(wǎng)上有太多合適的框架供我們選擇和使用,比如原生的HttpClient、HttpURLConnection,Google在前兩者上擴展的Volley,Square組織的OkHttp(+Retrofit組合)等等,都是很方便的網(wǎng)絡請求框架,那我們?yōu)槭裁催€要自己去探究,重復造輪呢?
PS:HttpClient因維護成本大,在2.3版本就不建議使用,6.0版本已經(jīng)廢棄,還在用的小伙伴要注意
原因很簡單,在企業(yè)級開發(fā)過程中,最重要的指標是穩(wěn)定,同時要控制更新、替換和后期維護的成本,這一指標對所有第三方框架都相同。另外還需要控制Apk文件的大小,不可以無限制的添加第三方庫,或僅為一個小功能添加了一個龐大且沒用的庫。
說了這么多廢話,下面扯正題
我們要做什么?是要自己寫一套網(wǎng)絡請求庫?
No,網(wǎng)上有那么多寫的牛X的網(wǎng)絡庫,我們怎么寫也寫不過他們。我們做的事,只為我們自己和我們的產(chǎn)品就夠了。那應該做什么事呢?
封裝
我們需要封裝自己的網(wǎng)絡層外殼,有了這個外殼,其它開發(fā)者不需要關(guān)注網(wǎng)絡請求的具體實現(xiàn),直接調(diào)用我們的API即可。以前Volley熱門,我們可以使用Volley的實現(xiàn),現(xiàn)在OkHttp比較火,我們可以使用OkHttp的實現(xiàn),但是不管怎么換,都不會影響上層的使用。大大降低了更新、維護的成本。
時下最火的Retrofit是一款很時尚的封裝框架,它的框架設計能力非一般人所能及,接口的設計與邏輯完全解耦,非常適合學習研究,只是今天火的是Retrofit,明天可能就是其它的XXX。所以筆者對于這種框架,僅停留在學習階段,不會輕易使用,寫一套最適合自己的,最普通的框架,對自己和團隊的學習本成來說,都只有好處沒有壞處
建議愛學習的朋友,仔細讀一下Retrofit,網(wǎng)上有很多文章,我就不寫了
原始底層封裝
這節(jié)寫封裝的框架,是一套原始框架,其中不包涵任何的與需求相關(guān)的邏輯,所以今天這一節(jié)的代碼,不管拿到哪個項目上,都可以適用。我們的網(wǎng)絡連接層使用OkHttp3,優(yōu)點大家都知道,不了解的去看一下。
網(wǎng)絡數(shù)據(jù)請求是一套工作流,簡單畫了一副圖,大家看一下
發(fā)起一個網(wǎng)絡請求簡單來說有5個節(jié)點:1. 發(fā)起請求,2. 連接請求,3. 請求響應,4. 數(shù)據(jù)解析,5. 返回結(jié)果。其中節(jié)點2我們使用OkHttp來幫我們做,那余下的4個節(jié)點,就是我們要做的事情了。
- 配置OkHttp3:打開OkHttp的GitHub地址我們看到OkHttp現(xiàn)在最新的版本是3.7.0,打開工程目錄下的build.gradle文件,在ext中添加如下變量:
配置OkHttp的版本號
然后打開network目錄下的build.gradle文件,在dependencies下添加如下引用
compile "com.squareup.okhttp3:okhttp:$rootProject.ext.okhttpVersion"
然后同步一下工程,將OkHttp的庫下載到本地。 -
打開network目錄下的src->main->java->com.monch.network,開始我們真正的Code之旅。
整個文件目錄并不算復雜,如下圖所示:
Java文件結(jié)構(gòu)
整個文件目錄,是圍繞圖1的流程所示,從上到下分別是:executor(執(zhí)行器)目錄、AccountException(帳號異常)、ApiCallback(網(wǎng)絡請求回調(diào))、ApiRequest(網(wǎng)絡請求)、ApiResponse(網(wǎng)絡請求響應)、ApiResult(網(wǎng)絡請求結(jié)果)、ArrayFactory(Array創(chuàng)建器)、Failed(錯誤類型枚舉)
在executor(執(zhí)行器)目錄下,包括IExecutor(執(zhí)行器接口)、OkHttpCallback(OkHttp響應回調(diào))、OkHttpClientFactory(OkHttpClient創(chuàng)建器)、OkHttpExecutor(OkHttp的執(zhí)行器實現(xiàn))、RequestUtils(請求工具類) - 下面我們按照一個請求的流程來分解代碼。
請求的最開始是ApiRequest,代碼如下
// 請求實例池
private static final Pools.Pool<ApiRequest> POOL = new Pools.SynchronizedPool<>(30);
// 生成ApiRequest實例
public static ApiRequest obtain(Builder builder) {
ApiRequest instance = POOL.acquire();
if (instance == null) {
instance = new ApiRequest(builder);
}
return instance;
}
// 釋放ApiRequest實例
public static void release(ApiRequest request) {
if (request == null) return;
IExecutor executor = request.executor;
if (executor != null) {
// 釋放請求緩存
executor.releaseCache(request);
}
request.url = null;
// 在這里,我們將所有的ArrayMap都置為null,
// 是因為之前的請求有可能已經(jīng)將ArrayMap的空間增加的足夠大
// 為了避免浪費多余分配的空間,之后的每次使用我們都重新創(chuàng)建
if (request.parameters != null) {
request.parameters.clear();
request.parameters = null;
}
if (request.headers != null) {
request.headers.clear();
request.headers = null;
}
if (request.files != null) {
request.files.clear();
request.files = null;
}
request.callback = null;
request.charset = null;
request.tag = null;
POOL.release(request);
}
// 默認執(zhí)行器
private static IExecutor mDefaultExecutor = new OkHttpExecutor();
// 默認編碼方式
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
/* 請求類型 */
public static final int GET = 0;
public static final int POST = GET + 1;
public static final int UPLOAD = POST + 1;
public static final int DOWNLOAD = UPLOAD + 1;
private String url; // 請求URL
private int type; // 請求類型
private ArrayMap<String, String> parameters; // 參數(shù)集合
private ArrayMap<String, String> headers; // 請求頭集合
private ArrayMap<String, File> files; // 上傳文件集合
private ApiCallback callback; // 請求回調(diào)
private Charset charset; // 編碼
private Object tag; // 標記
private IExecutor executor; // 執(zhí)行器
private ApiRequest(Builder builder) {
this.url = builder.url;
this.type = builder.type;
this.parameters = builder.parameters;
this.headers = builder.headers;
this.files = builder.files;
this.callback = builder.callback;
this.charset = builder.charset != null ? builder.charset : DEFAULT_CHARSET;
this.tag = builder.tag;
this.executor = builder.executor != null ? builder.executor : mDefaultExecutor;
}
// 開始請求
private void request() {
switch (type) {
case POST:
executor.doPost(this);
break;
case UPLOAD:
executor.doUpload(this);
break;
case DOWNLOAD:
executor.doDownload(this);
break;
default:
executor.doGet(this);
break;
}
}
public String getUrl() {
return url;
}
public int getType() {
return type;
}
public ArrayMap<String, String> getParameters() {
return parameters;
}
public ArrayMap<String, String> getHeaders() {
return headers;
}
public ArrayMap<String, File> getFiles() {
return files;
}
public ApiCallback getCallback() {
return callback;
}
public Charset getCharset() {
return charset;
}
public Object getTag() {
return tag;
}
public IExecutor getExecutor() {
return executor;
}
public void cancel() {
if (executor != null) {
executor.cancel(this);
}
}
public static Builder newBuilder() {
return new Builder();
}
public static class Builder {
private String url;
private int type = GET;
private ArrayMap<String, String> parameters;
private ArrayMap<String, String> headers;
private ArrayMap<String, File> files;
private ApiCallback callback;
private Charset charset;
private Object tag;
private IExecutor executor;
private Builder(){}
public Builder url(String url) {
this.url = url;
return this;
}
public Builder addParameter(String key, String value) {
if (parameters == null) {
parameters = ArrayFactory.createArrayMap();
}
parameters.put(key, value);
return this;
}
public Builder addHeader(String key, String value) {
if (headers == null) {
headers = ArrayFactory.createArrayMap(1);
}
headers.put(key, value);
return this;
}
public Builder addFile(String key, File file) {
if (files == null) {
files = ArrayFactory.createArrayMap(1);
}
files.put(key, file);
return this;
}
public Builder callback(ApiCallback callback) {
this.callback = callback;
return this;
}
public Builder charset(Charset charset) {
this.charset = charset;
return this;
}
public Builder tag(Object tag) {
this.tag = tag;
return this;
}
public Builder executor(IExecutor executor) {
this.executor = executor;
return this;
}
public ApiRequest get() {
this.type = GET;
ApiRequest request = obtain(this);
request.request();
return request;
}
public ApiRequest post() {
this.type = POST;
ApiRequest request = obtain(this);
request.request();
return request;
}
public ApiRequest upload() {
this.type = UPLOAD;
ApiRequest request = obtain(this);
request.request();
return request;
}
public ApiRequest download() {
this.type = DOWNLOAD;
ApiRequest request = obtain(this);
request.request();
return request;
}
}
在企業(yè)級工程中,網(wǎng)絡請求的需求量是巨大的,所以我們對每個ApiRequest都使用Pools.Pool,這是一個實例緩存池,避免在運行時重復創(chuàng)建大量的實例,了解過JVM的同學都知道,創(chuàng)建實例是一個比較耗時的過程,而且大量實例廢棄后會引發(fā)GC頻繁,造成卡頓。這里我要強調(diào)一點,優(yōu)化都是從一點一滴做起的,在優(yōu)化的過程中,我們學習的內(nèi)容也能更深入。
obtain和release兩個靜態(tài)方法,是生成和釋放ApiRequest實例的方法,用法大家可以看一下代碼。
接下來就是一些屬性的定義:
mDefaultExecutor:這個是靜態(tài)變量,存放默認的網(wǎng)絡請求執(zhí)行器,在我們的框架中,默認為OkHttpExecutor的實現(xiàn),如果某一天大家需要更改執(zhí)行器,那么直接替換這里就可以了。
默認的編碼方式是UTF-8,這個沒什么好說,大家都這樣。
接下來是請求類型的定義,共使用4種:GET、POST、UPLOAD、DOWNLOAD,網(wǎng)絡請求還有一些其它的類型,比如PUT、DELETE等,但現(xiàn)在基本很少使用,所以這里不定義。
以上的這些屬性都是靜態(tài)的,在內(nèi)存中單獨存在,不屬于任何一個實例。下面是一個請求真正需要的變量:請求地址、請求類型、參數(shù)、請求頭、文件、回調(diào)、編碼、標記、執(zhí)行器。ApiRequest類的構(gòu)造函數(shù)是私有的,強制使用obtain方法創(chuàng)建實例。
tag(標記)屬性需要說明一下,這是一個起到上下文的屬性,舉個粟子:我們在Service中發(fā)起了一個獲取好友列表的請求,假設這個接口比較費時,當數(shù)據(jù)還沒有返回時,用戶已經(jīng)退出登錄又換了一個帳號登錄上,然后獲取好友列表的請求才返回,如果這些數(shù)據(jù)按照當前已經(jīng)換帳號的環(huán)境去處理,必然是錯的,所以這個時候,大家就有必要做一下處理了。這個屬性我們可以在發(fā)起請求時保存一個userId,在處理之前,對比一下當前的userId是否一至,當一至時再處理。
代碼很簡單,大家都應該能看懂。對外暴露的接口就是ApiRequest.Builder創(chuàng)建請求。
接下來看一下IExecutor接口
/**
* 拉取請求
* @param request
*/
void doGet(ApiRequest request);
/**
* 提交請求
* @param request
*/
void doPost(ApiRequest request);
/**
* 上傳請求
* @param request
*/
void doUpload(ApiRequest request);
/**
* 下載請求
* @param request
*/
void doDownload(ApiRequest request);
/**
* 釋放緩存
* @param request
*/
void releaseCache(ApiRequest request);
/**
* 取消請求
* @param request
*/
void cancel(ApiRequest request);
/**
* 取消所有請求
*/
void cancelAll();
這個接口定義了這些方法,此版本我們使用OkHttp來實現(xiàn)這些接口,如果有一天我們需要換成Volley連接,那么直接創(chuàng)建一個VolleyExecutor類,實現(xiàn)IExecutor接口即可。
OkHttpExecutor的具體實現(xiàn)如下
private OkHttpClient mOkHttpClient;
private Map<ApiRequest, Call> mRequestCache = ArrayFactory.createConcurrentHashMap();
public OkHttpExecutor() {
mOkHttpClient = OkHttpClientFactory.getClient();
}
@Override
public void doGet(ApiRequest request) {
OkHttpClient client = mOkHttpClient;
Charset charset = request.getCharset();
String url = RequestUtils.makeUrl(request.getUrl(), request.getParameters(), charset);
ApiCallback apiCallback = request.getCallback();
if (apiCallback != null) {
apiCallback.onStart(request);
}
Request.Builder builder = new Request.Builder();
builder.url(url);
builder.tag(request.getTag());
builder.get();
RequestUtils.makeHeader(builder, request.getHeaders(), charset);
Call call = client.newCall(builder.build());
call.enqueue(new OkHttpCallback(request));
mRequestCache.put(request, call);
}
@Override
public void doPost(ApiRequest request) {
OkHttpClient client = mOkHttpClient;
Charset charset = request.getCharset();
ApiCallback callback = request.getCallback();
if (callback != null) {
callback.onStart(request);
}
Request.Builder builder = new Request.Builder();
builder.url(request.getUrl());
builder.tag(request.getTag());
builder.post(formBody(request.getParameters(), charset));
RequestUtils.makeHeader(builder, request.getHeaders(), charset);
Call call = client.newCall(builder.build());
call.enqueue(new OkHttpCallback(request));
mRequestCache.put(request, call);
}
private RequestBody formBody(Map<String, String> params, Charset charset) {
FormBody.Builder builder = new FormBody.Builder();
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, String> entry : params.entrySet()) {
String key = RequestUtils.encode(entry.getKey(), charset);
String value = RequestUtils.encode(entry.getValue(), charset);
if (!TextUtils.isEmpty(key) && value != null) {
builder.addEncoded(key, value);
}
}
}
return builder.build();
}
@Override
public void doUpload(ApiRequest request) {
OkHttpClient client = mOkHttpClient;
Charset charset = request.getCharset();
ApiCallback callback = request.getCallback();
if (callback != null) {
callback.onStart(request);
}
Request.Builder builder = new Request.Builder();
builder.url(request.getUrl());
builder.tag(request.getTag());
builder.post(formBody(request.getParameters(), request.getFiles(), charset));
RequestUtils.makeHeader(builder, request.getHeaders(), charset);
Call call = client.newCall(builder.build());
call.enqueue(new OkHttpCallback(request));
mRequestCache.put(request, call);
}
private static final String DEFAULT_CONTENT = "Content-Disposition";
private RequestBody formBody(Map<String, String> params, Map<String, File> files, Charset charset) {
MultipartBody.Builder builder = new MultipartBody.Builder();
builder.setType(MultipartBody.FORM);
if (params != null && !params.isEmpty()) {
for (Map.Entry<String, String> entry : params.entrySet()) {
String key = RequestUtils.encode(entry.getKey(), charset);
byte[] content = entry.getValue().getBytes(charset);
builder.addPart(Headers.of(DEFAULT_CONTENT, getParamValue(key)),
RequestBody.create(MediaType.parse(charset.name()), content));
}
}
if (files != null && !files.isEmpty()) {
for (Map.Entry<String, File> entry : files.entrySet()) {
String name = RequestUtils.encode(entry.getKey(), charset);
File file = entry.getValue();
String fileName = file.getName();
builder.addPart(Headers.of(DEFAULT_CONTENT, getFileValue(name, fileName)),
RequestBody.create(MediaType.parse(guessMimeType(fileName)), file));
}
}
return builder.build();
}
private static String guessMimeType(String path) {
FileNameMap fileNameMap = URLConnection.getFileNameMap();
String contentTypeFor = fileNameMap.getContentTypeFor(path);
if (contentTypeFor == null) {
contentTypeFor = "application/octet-stream";
}
return contentTypeFor;
}
private static String getParamValue(String name) {
return "form-data; name=\"" + name + "\"";
}
private static String getFileValue(String name, String fileName) {
return "form-data; name=\"" + name + "\"; filename=\"" + fileName + "\"";
}
@Override
public void doDownload(ApiRequest request) {
doGet(request);
}
@Override
public void releaseCache(ApiRequest request) {
if (request == null) return;
if (mRequestCache.containsKey(request)) {
mRequestCache.remove(request);
}
}
@Override
public void cancel(ApiRequest request) {
if (request == null) return;
if (mRequestCache.containsKey(request)) {
Call call = mRequestCache.get(request);
if (call != null && !call.isCanceled()) {
call.cancel();
}
mRequestCache.remove(request);
}
}
@Override
public void cancelAll() {
if (!mRequestCache.isEmpty()) {
for (Call call : mRequestCache.values()) {
if (call != null && !call.isCanceled()) {
call.cancel();
}
}
mRequestCache.clear();
}
}
OkHttp的具體用法在這就不講了,如果有不清楚的,去看一下OkHttp的使用即可。
這里有一點需要注意,就是我們的mRequestCache(請求緩存)變量,因為存在并發(fā)的問題,所以我這里定義為線程安全的ConcurrentHashMap。
請求的參數(shù),在這里都已經(jīng)做過Encoder處理,GET請求也會自動的將參數(shù)拼接到URL上,所以大家不需要擔心。
OkHttpClient的賦值,是使用OkHttpClientFactory類的getClient方法,這個方法是獲取了一個支持所有證書的SSL連接的實例,可用于https的請求。代碼如下
···
private static final int TIMEOUT = 60;
private volatile static OkHttpClient mOkHttpClient;
static OkHttpClient getClient() {
if (mOkHttpClient == null) {
synchronized (OkHttpClientFactory.class) {
if (mOkHttpClient == null) {
try {
X509TrustManager trustManager = createInsecureTrustManager();
SSLSocketFactory sslSocketFactory = createInsecureSslSocketFactory(trustManager);
mOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(TIMEOUT, TimeUnit.SECONDS)
.readTimeout(TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(TIMEOUT, TimeUnit.SECONDS)
.sslSocketFactory(sslSocketFactory, trustManager)
.hostnameVerifier(createInsecureHostnameVerifier())
.build();
} catch (Exception e) {
Log.e(TAG, "Get client error", e);
mOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(TIMEOUT, TimeUnit.SECONDS)
.readTimeout(TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(TIMEOUT, TimeUnit.SECONDS)
.build();
}
}
}
}
return mOkHttpClient;
}
private static HostnameVerifier createInsecureHostnameVerifier() {
return new HostnameVerifier() {
@Override public boolean verify(String s, SSLSession sslSession) {
return true;
}
};
}
private static SSLSocketFactory createInsecureSslSocketFactory(TrustManager trustManager) {
try {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] {trustManager}, new SecureRandom());
return context.getSocketFactory();
} catch (Exception e) {
throw new AssertionError(e);
}
}
/**
* 信任所有證書
*/
private static X509TrustManager createInsecureTrustManager() {
return new X509TrustManager() {
@Override public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
}
···
接下來再看如下代碼:
Call call = client.newCall(builder.build());
call.enqueue(new OkHttpCallback(request));
這是將每個具體請求加入到請求隊列中,我們都創(chuàng)建了一個OkHttpCallback實例,用于請求響應的回調(diào)。代碼如下
public class OkHttpCallback implements Callback {
/** 主線程執(zhí)行器 **/
private static Handler mResponseHandler = new Handler(Looper.getMainLooper());
private static Executor mResponsePoster = new Executor() {
@Override
public void execute(@NonNull Runnable command) {
mResponseHandler.post(command);
}
};
private ApiRequest apiRequest;
public OkHttpCallback(ApiRequest apiRequest) {
this.apiRequest = apiRequest;
}
@Override
public void onFailure(Call call, IOException e) {
// 請求失敗,將任務拋回主線程執(zhí)行
mResponsePoster.execute(new FailedRunnable(apiRequest, call, e));
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (call.isCanceled()) {
// 請求已終止
mResponsePoster.execute(new FailedRunnable(apiRequest, call, null));
return;
}
if (response == null) {
// 請求未響應
mResponsePoster.execute(new FailedRunnable(apiRequest, call,
new IOException("Response is null.")));
return;
}
if (!response.isSuccessful()) {
// 請求失敗
mResponsePoster.execute(new FailedRunnable(apiRequest, call,
new IOException("Unexpected code " + response)));
return;
}
ApiCallback apiCallback = apiRequest.getCallback();
if (apiCallback == null) return;
ResponseBody body = response.body();
try {
// 填充請求響應數(shù)據(jù)
ApiResponse apiResponse = ApiResponse.obtain();
apiResponse.setCode(response.code());
apiResponse.setCharset(apiRequest.getCharset());
apiResponse.setBody(body.bytes());
Headers headers = response.headers();
for (String key : headers.names()) {
apiResponse.addHeader(key, headers.get(key));
}
apiResponse.setTag(apiRequest.getTag());
// 回調(diào)響應,運行在子線程,用于數(shù)據(jù)解析
ApiResult apiResult = apiCallback.onResponse(apiResponse);
// 釋放響應數(shù)據(jù)
ApiResponse.release(apiResponse);
// 拋回主線程執(zhí)行完成
mResponsePoster.execute(new SuccessRunnable(apiRequest, call, apiResult));
} catch (Exception e) {
// 拋回主線程執(zhí)行失敗
mResponsePoster.execute(new FailedRunnable(apiRequest, call, e));
} finally {
if (body != null) body.close();
}
}
// 請求錯誤處理
private static class FailedRunnable implements Runnable {
private ApiRequest apiRequest;
private Call call;
private Throwable throwable;
FailedRunnable(ApiRequest apiRequest, Call call, Throwable throwable) {
this.apiRequest = apiRequest;
this.call = call;
this.throwable = throwable;
}
@Override
public void run() {
ApiCallback apiCallback = apiRequest.getCallback();
if (apiCallback != null) {
if (call.isCanceled()) {
apiCallback.onCancel();
} else {
Failed f = Failed.OTHER;
if (throwable != null) {
if (throwable instanceof IOException) {
f = Failed.NETWORK_ERROR; // 網(wǎng)絡異常
} else if (throwable instanceof TimeoutException) {
f = Failed.TIMEOUT_ERROR; // 請求超時
} else if (throwable instanceof JSONException) {
f = Failed.PARSE_ERROR; // 數(shù)據(jù)解析異常
} else if (throwable instanceof AccountException) {
apiCallback.onAccountError(); // 帳戶信息異常
return;
}
}
apiCallback.onFailure(f, throwable);
}
}
ApiRequest.release(apiRequest);
}
}
// 請求成功處理
private static class SuccessRunnable implements Runnable {
private ApiRequest apiRequest;
private Call call;
private ApiResult apiResult;
SuccessRunnable(ApiRequest apiRequest, Call call, ApiResult apiResult) {
this.apiRequest = apiRequest;
this.call = call;
this.apiResult = apiResult;
}
@Override
public void run() {
ApiCallback callback = apiRequest.getCallback();
if (callback != null) {
if (call.isCanceled()) {
callback.onCancel();
} else {
callback.onComplete(apiResult);
}
}
ApiResult.release(apiResult);
ApiRequest.release(apiRequest);
}
}
}
這個類實現(xiàn)了okhttp3.Callback接口,需要實現(xiàn)兩個方法onFailure和onResponse,大家應該都知道這兩個方法的作用吧?
在onResponse回調(diào)方法中,我們創(chuàng)建了ApiResponse實例,將主要的響應數(shù)據(jù)填充進去,包括響應碼、響應數(shù)據(jù)、響應頭等信息,依靠這些數(shù)據(jù),我們使用ApiCallback的回調(diào),將這些數(shù)據(jù)拋給上層使用者去解析
ApiResult apiResult = apiCallback.onResponse(apiResponse);
然后將結(jié)果使用mResponsePoster拋回到主線程處理,至此,一個完整的請求流程完成了。
下面我們看一下ApiCallback都有哪些回調(diào)
public interface ApiCallback {
/**
* 請求開始回調(diào),運行在當前線程
* @param request 請求數(shù)據(jù)
*/
void onStart(ApiRequest request);
/**
* 請求響應回調(diào),運行在子線程,主要用于數(shù)據(jù)解析。
* 在解析過程中,如果發(fā)現(xiàn)后臺返回的錯誤為帳戶異常,
* 可直接拋出AccountException,在onAccountError回調(diào)中統(tǒng)一處理
* @param response 響應數(shù)據(jù)
* @return
* @throws JSONException JSON解析異常
* @throws AccountException 帳戶登錄異常
*/
ApiResult onResponse(ApiResponse response) throws JSONException, AccountException;
/**
* 請求完成,運行在主線程,將解析后的結(jié)果返回
* @param result 解析結(jié)果
*/
void onComplete(ApiResult result);
/**
* 請求失敗回調(diào),運行在主線程
* @param failed 錯誤類型
* @param throwable 異常信息
*/
void onFailure(Failed failed, Throwable throwable);
/**
* 帳戶異?;卣{(diào),運行在主線程,在onResponse回調(diào)中,如果接收到AccountException異常,會直接回調(diào)到這里
*/
void onAccountError();
/**
* 請求取消回調(diào),運行在主線程
*/
void onCancel();
}
注釋寫的比較清楚,不再贅述。
整個封裝的代碼框架差不多就這些,此框架當前狀態(tài)下,有著非常好的擴展性。當我需要替換成Volley,我只需要做到以下幾點:
- 創(chuàng)建一個類叫VolleyExecutor,實現(xiàn)IExecutor接口,然后按照Volley的方式填充各個方法。
- 再創(chuàng)建一個叫VolleyCallback的類,將Volley的回調(diào)轉(zhuǎn)換成我們的ApiCallback。
- 將ApiRequest類的mDefaultExecutor屬性,默認實現(xiàn)替換成new VolleyExecutor();即可,完全不影響上層的任何使用。
這只是一個簡單的封裝,我們也可以更徹底一點,做一個請求分發(fā)流,這樣我們只需要使用OkHttp的連接功能,不過這樣做太費事,暫時不考慮。
下面的代碼,是現(xiàn)在狀態(tài)的使用
ApiRequest req = ApiRequest.newBuilder()
.url("https://www.baidu.com")
.addParameter("key1", "value1")
.addParameter("key2", "value2")
.addHeader("header1", "headerValue1")
.callback(new ApiCallback() {
@Override
public void onStart(ApiRequest request) {
// 請求開始
}
@Override
public ApiResult onResponse(ApiResponse response) throws JSONException, AccountException {
JSONObject jsonObject = new JSONObject(response.getString());
ApiResult result = ApiResult.obtain();
int code = jsonObject.optInt("code");
// 假如我們與服務端的同學設定,code等于10的時候,說明帳號異常,那么處理代碼如下:
if (code == 10) {
throw new AccountException();
}
result.setCode(code);
result.setErrorMessage(jsonObject.optString("errorMsg"));
if (result.isSuccess()) { // 當code為0時,表示成功
Object object = jsonObject.optString("test");
result.put("object", object);
}
return result;
}
@Override
public void onComplete(ApiResult result) {
if (result.isSuccess()) {
Object object = result.get("object");
if (object != null) {
Log.e("Test", object.toString());
}
}
}
@Override
public void onFailure(Failed failed, Throwable throwable) {
Toast.makeText(this, failed.error(), Toast.LENGTH_SHORT).show();
}
@Override
public void onAccountError() {
// 這里表示登錄異常,我們可以將用戶踢到登錄頁面
}
@Override
public void onCancel() {
// 請求取消的回調(diào)
}
})
.get();
if (如果需要取消請求) {
req.cancel();
}
各位看官,是不是一看嚇了一跳,我擦,這么費事還搞個屁呀。。。
說明,說明,說明。。。這是半成品,現(xiàn)在的狀態(tài),是擴展性非常好的狀態(tài),但是使用還不方便,在真正的工程使用時,我們還需要結(jié)合具體業(yè)務,做二次封裝。比如我們所有的接口都需要上傳用戶的位置、版本號等信息,現(xiàn)在的狀態(tài)是不方便的。
今天說到這,具體代碼請移步GitHub查看。感謝!