市场上的绝大多数APP,都离不开网络数据请求,从注册到登录、提交/拉取数据,都是我们必须要面对的问题,可见网络数据请求在APP产品中的重要性。那么网络请求在企业级架构中,应该怎样处理是比较合理的呢?这篇文章我们来聊一聊网络数据请求的架构处理。
可能有人会问:网上有太多合适的框架供我们选择和使用,比如原生的HttpClient、HttpURLConnection,Google在前两者上扩展的Volley,Square组织的OkHttp(+Retrofit组合)等等,都是很方便的网络请求框架,那我们为什么还要自己去探究,重复造轮呢?
PS:HttpClient因维护成本大,在2.3版本就不建议使用,6.0版本已经废弃,还在用的小伙伴要注意
原因很简单,在企业级开发过程中,最重要的指标是稳定,同时要控制更新、替换和后期维护的成本,这一指标对所有第三方框架都相同。另外还需要控制Apk文件的大小,不可以无限制的添加第三方库,或仅为一个小功能添加了一个庞大且没用的库。
说了这么多废话,下面扯正题
我们要做什么?是要自己写一套网络请求库?
No,网上有那么多写的牛X的网络库,我们怎么写也写不过他们。我们做的事,只为我们自己和我们的产品就够了。那应该做什么事呢?
封装
我们需要封装自己的网络层外壳,有了这个外壳,其它开发者不需要关注网络请求的具体实现,直接调用我们的API即可。以前Volley热门,我们可以使用Volley的实现,现在OkHttp比较火,我们可以使用OkHttp的实现,但是不管怎么换,都不会影响上层的使用。大大降低了更新、维护的成本。
时下最火的Retrofit是一款很时尚的封装框架,它的框架设计能力非一般人所能及,接口的设计与逻辑完全解耦,非常适合学习研究,只是今天火的是Retrofit,明天可能就是其它的XXX。所以笔者对于这种框架,仅停留在学习阶段,不会轻易使用,写一套最适合自己的,最普通的框架,对自己和团队的学习本成来说,都只有好处没有坏处
建议爱学习的朋友,仔细读一下Retrofit,网上有很多文章,我就不写了
原始底层封装
这节写封装的框架,是一套原始框架,其中不包涵任何的与需求相关的逻辑,所以今天这一节的代码,不管拿到哪个项目上,都可以适用。我们的网络连接层使用OkHttp3,优点大家都知道,不了解的去看一下。
网络数据请求是一套工作流,简单画了一副图,大家看一下
发起一个网络请求简单来说有5个节点:1. 发起请求,2. 连接请求,3. 请求响应,4. 数据解析,5. 返回结果。其中节点2我们使用OkHttp来帮我们做,那余下的4个节点,就是我们要做的事情了。
- 配置OkHttp3:打开OkHttp的GitHub地址我们看到OkHttp现在最新的版本是3.7.0,打开工程目录下的build.gradle文件,在ext中添加如下变量:
然后打开network目录下的build.gradle文件,在dependencies下添加如下引用
compile "com.squareup.okhttp3:okhttp:$rootProject.ext.okhttpVersion"
然后同步一下工程,将OkHttp的库下载到本地。 -
打开network目录下的src->main->java->com.monch.network,开始我们真正的Code之旅。
整个文件目录并不算复杂,如下图所示:
整个文件目录,是围绕图1的流程所示,从上到下分别是:executor(执行器)目录、AccountException(帐号异常)、ApiCallback(网络请求回调)、ApiRequest(网络请求)、ApiResponse(网络请求响应)、ApiResult(网络请求结果)、ArrayFactory(Array创建器)、Failed(错误类型枚举)
在executor(执行器)目录下,包括IExecutor(执行器接口)、OkHttpCallback(OkHttp响应回调)、OkHttpClientFactory(OkHttpClient创建器)、OkHttpExecutor(OkHttp的执行器实现)、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,
// 是因为之前的请求有可能已经将ArrayMap的空间增加的足够大
// 为了避免浪费多余分配的空间,之后的每次使用我们都重新创建
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);
}
// 默认执行器
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; // 参数集合
private ArrayMap<String, String> headers; // 请求头集合
private ArrayMap<String, File> files; // 上传文件集合
private ApiCallback callback; // 请求回调
private Charset charset; // 编码
private Object tag; // 标记
private IExecutor executor; // 执行器
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;
}
}
在企业级工程中,网络请求的需求量是巨大的,所以我们对每个ApiRequest都使用Pools.Pool,这是一个实例缓存池,避免在运行时重复创建大量的实例,了解过JVM的同学都知道,创建实例是一个比较耗时的过程,而且大量实例废弃后会引发GC频繁,造成卡顿。这里我要强调一点,优化都是从一点一滴做起的,在优化的过程中,我们学习的内容也能更深入。
obtain和release两个静态方法,是生成和释放ApiRequest实例的方法,用法大家可以看一下代码。
接下来就是一些属性的定义:
mDefaultExecutor:这个是静态变量,存放默认的网络请求执行器,在我们的框架中,默认为OkHttpExecutor的实现,如果某一天大家需要更改执行器,那么直接替换这里就可以了。
默认的编码方式是UTF-8,这个没什么好说,大家都这样。
接下来是请求类型的定义,共使用4种:GET、POST、UPLOAD、DOWNLOAD,网络请求还有一些其它的类型,比如PUT、DELETE等,但现在基本很少使用,所以这里不定义。
以上的这些属性都是静态的,在内存中单独存在,不属于任何一个实例。下面是一个请求真正需要的变量:请求地址、请求类型、参数、请求头、文件、回调、编码、标记、执行器。ApiRequest类的构造函数是私有的,强制使用obtain方法创建实例。
tag(标记)属性需要说明一下,这是一个起到上下文的属性,举个粟子:我们在Service中发起了一个获取好友列表的请求,假设这个接口比较费时,当数据还没有返回时,用户已经退出登录又换了一个帐号登录上,然后获取好友列表的请求才返回,如果这些数据按照当前已经换帐号的环境去处理,必然是错的,所以这个时候,大家就有必要做一下处理了。这个属性我们可以在发起请求时保存一个userId,在处理之前,对比一下当前的userId是否一至,当一至时再处理。
代码很简单,大家都应该能看懂。对外暴露的接口就是ApiRequest.Builder创建请求。
接下来看一下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来实现这些接口,如果有一天我们需要换成Volley连接,那么直接创建一个VolleyExecutor类,实现IExecutor接口即可。
OkHttpExecutor的具体实现如下
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(请求缓存)变量,因为存在并发的问题,所以我这里定义为线程安全的ConcurrentHashMap。
请求的参数,在这里都已经做过Encoder处理,GET请求也会自动的将参数拼接到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));
这是将每个具体请求加入到请求队列中,我们都创建了一个OkHttpCallback实例,用于请求响应的回调。代码如下
public class OkHttpCallback implements Callback {
/** 主线程执行器 **/
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) {
// 请求失败,将任务抛回主线程执行
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 {
// 填充请求响应数据
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());
// 回调响应,运行在子线程,用于数据解析
ApiResult apiResult = apiCallback.onResponse(apiResponse);
// 释放响应数据
ApiResponse.release(apiResponse);
// 抛回主线程执行完成
mResponsePoster.execute(new SuccessRunnable(apiRequest, call, apiResult));
} catch (Exception e) {
// 抛回主线程执行失败
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; // 网络异常
} else if (throwable instanceof TimeoutException) {
f = Failed.TIMEOUT_ERROR; // 请求超时
} else if (throwable instanceof JSONException) {
f = Failed.PARSE_ERROR; // 数据解析异常
} 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);
}
}
}
这个类实现了okhttp3.Callback接口,需要实现两个方法onFailure和onResponse,大家应该都知道这两个方法的作用吧?
在onResponse回调方法中,我们创建了ApiResponse实例,将主要的响应数据填充进去,包括响应码、响应数据、响应头等信息,依靠这些数据,我们使用ApiCallback的回调,将这些数据抛给上层使用者去解析
ApiResult apiResult = apiCallback.onResponse(apiResponse);
然后将结果使用mResponsePoster抛回到主线程处理,至此,一个完整的请求流程完成了。
下面我们看一下ApiCallback都有哪些回调
public interface ApiCallback {
/**
* 请求开始回调,运行在当前线程
* @param request 请求数据
*/
void onStart(ApiRequest request);
/**
* 请求响应回调,运行在子线程,主要用于数据解析。
* 在解析过程中,如果发现后台返回的错误为帐户异常,
* 可直接抛出AccountException,在onAccountError回调中统一处理
* @param response 响应数据
* @return
* @throws JSONException JSON解析异常
* @throws AccountException 帐户登录异常
*/
ApiResult onResponse(ApiResponse response) throws JSONException, AccountException;
/**
* 请求完成,运行在主线程,将解析后的结果返回
* @param result 解析结果
*/
void onComplete(ApiResult result);
/**
* 请求失败回调,运行在主线程
* @param failed 错误类型
* @param throwable 异常信息
*/
void onFailure(Failed failed, Throwable throwable);
/**
* 帐户异常回调,运行在主线程,在onResponse回调中,如果接收到AccountException异常,会直接回调到这里
*/
void onAccountError();
/**
* 请求取消回调,运行在主线程
*/
void onCancel();
}
注释写的比较清楚,不再赘述。
整个封装的代码框架差不多就这些,此框架当前状态下,有着非常好的扩展性。当我需要替换成Volley,我只需要做到以下几点:
- 创建一个类叫VolleyExecutor,实现IExecutor接口,然后按照Volley的方式填充各个方法。
- 再创建一个叫VolleyCallback的类,将Volley的回调转换成我们的ApiCallback。
- 将ApiRequest类的mDefaultExecutor属性,默认实现替换成new VolleyExecutor();即可,完全不影响上层的任何使用。
这只是一个简单的封装,我们也可以更彻底一点,做一个请求分发流,这样我们只需要使用OkHttp的连接功能,不过这样做太费事,暂时不考虑。
下面的代码,是现在状态的使用
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() {
// 请求取消的回调
}
})
.get();
if (如果需要取消请求) {
req.cancel();
}
各位看官,是不是一看吓了一跳,我擦,这么费事还搞个屁呀。。。
说明,说明,说明。。。这是半成品,现在的状态,是扩展性非常好的状态,但是使用还不方便,在真正的工程使用时,我们还需要结合具体业务,做二次封装。比如我们所有的接口都需要上传用户的位置、版本号等信息,现在的状态是不方便的。
今天说到这,具体代码请移步GitHub查看。感谢!