在实际Android应用的开发中,网络请求每每是必不可少的。如今有不少优秀的开源网络框架如Volley、Okhttp和Retrofit等,说到框架,不少童鞋信手拈来,反手一个Okhttp+etrofit+RxJava全家桶。不就是网络请求么,so easy~git
不过实际开发过程当中,确实会出现各类各样的问题,好比你上传一张图片,服务器那边接收不到,怎么办呢?你看了下本身这边,彻底按照标准api来写的,讲道理应该没错吧?这时候打开debug,但是框架内的代码怎么跟进?没看过也不懂啊,因此可能有些童鞋会去阅读源码,但是源码这种东西,不熟悉的话读起来晦涩难懂,固然边读边作源码分析写下几篇博客也是不错的选择。github
不过其实最本质的,就是对你框架的业务熟悉,好比网络请求框架,你就必须熟悉Http协议,才能够了解你的表单是怎么封装成数据,以什么结果表示,怎么发出去,接收到的内容又是什么?了解了这些,咱们彻底能够参考优秀的源码,本身动手去实现一个简易版的。编程
谈到网络框架,就不得不说到http协议了,网络框架必须严格按照http协议才能保证客户端和服务器双方数据的正常传输。api
一个http请求主要包含如下几个部分:请求行(request line)、请求头(header)、空行和请求正文四个部分。bash
以一个http请求为例:服务器
GET /form.html HTTP/1.1
Accept:image/gif,image/x-xbitmap,image/jpeg,application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword,*/* Accept-Language:zh-cn
Accept-Encoding:gzip,deflate
If-Modified-Since:Wed,05 Jan 2007 11:21:25 GMT
If-None-Match:W/"80b1a4c018f3c41:8317"
User-Agent:Mozilla/4.0(compatible;MSIE6.0;Windows NT 5.0)
Host:www.guet.edu.cn
Connection:Keep-Alive
复制代码
HTTP响应也由四个部分组成,分别是:状态行、响应头、空行和响应正文。 这里也以一段http响应为例:网络
HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8
<html>
<head></head>
<body>
<!--body goes here-->
</body>
</html>
复制代码
大概了解了Http,咱们就得选择一种具体的方式或者说一个比较底层的api来做为实现网络访问的实际参与者。大概有三种选择——Socket、HttpClient和HttpUrlConnection。若是是基于Socket那么咱们须要实现的内容比较多,固然目前的OkHttp是采用这种方式来的,毕竟socket进行操做自由度比较高,如内部socket链接池的分配,长链接短链接等均可以控制,自由度较高。而HttpClient在Android6.0之后官方已经移除了这个api,而HttpUrlConnection则是一个比较好的选择,足够轻量级,又实现了一些基本需求,所以以HttpUrlConnection做为实际网络请求的参与者。app
由于以为Okhttp的构建方式很优雅,这里咱们的构建方式就以OkHttp的方式进行构建,根据上面的Http协议的分析,结合OkHttp的构建方式,对对象的抽象其实也就一目了然了。必然是支持同步和异步的方式发起请求,因此咱们的构建方式基本以下:框架
FormBody body = new FormBody.Builder()
.add("username", "浩哥")
.add("pwd", "abc")
.build();
Request request = new Request.Builder()
.url("http://192.168.31.34:8080/API/upkeep")
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Response response) {
if (response.code() == 200) {
String msg = response.body().string();
Logger.e("response msg = " + msg);
}
}
@Override
public void onFail(Request request, IOException e) {
e.printStackTrace();
}
});
复制代码
主要抽象出的对象包括:Request、RequestBody、Response、ResponseBody、 Call、CallBack等。
请求怎么构建呢?结合上面对http协议的分析,请求包括起始行、请求头、空行和请求正文。由于基于HttpUrlConnection,因此起始行和空行能够不用考虑,请求头须要一个临时的暂存空间,请求正文因为不一样类型格式也不一样,所以请求正文给一个抽象的基类。
requestBody主要负责对流的写出和ContentType类型的构建,由于不一样类型如表单和文件的Content-Type内容是不一致的,服务器那边解析方式天然也是不同的。
public abstract class RequestBody {
/**
* body的类型
*
* @return
*/
abstract String contentType();
/**
* 将内容写出去
*
* @param ous
*/
abstract void writeTo(OutputStream ous) throws IOException;
}
复制代码
请求这块存储了url,和请求的方法类型,用ArrayMap来存储请求头,同时持有一个RequestBody的引用,都可以经过建造者模式构建进来。
public class Request {
final HttpMethod method;
final String url;
final Map<String, String> heads;
final RequestBody body;
public Request(Builder builder) {
this.method = builder.method;
this.url = builder.url;
this.heads = builder.heads;
this.body = builder.body;
}
public static final class Builder {
HttpMethod method;
String url;
Map<String, String> heads;
RequestBody body;
public Builder() {
this.method = HttpMethod.GET;
this.heads = new ArrayMap<>();
}
Builder(Request request) {
this.method = request.method;
this.url = request.url;
}
public Builder url(String url) {
this.url = url;
return this;
}
public Builder header(String name, String value) {
Util.checkMap(name, value);
heads.put(name, value);
return this;
}
public Builder get() {
method(HttpMethod.GET, null);
return this;
}
public Builder post(RequestBody body) {
method(HttpMethod.POST, body);
return this;
}
public Builder put(RequestBody body) {
method(HttpMethod.PUT, body);
return this;
}
public Builder delete(RequestBody body) {
method(HttpMethod.DELETE, body);
return this;
}
public Builder method(HttpMethod method, RequestBody body) {
Util.checkMethod(method, body);
this.method = method;
this.body = body;
return this;
}
public Request build() {
if (url == null) {
throw new IllegalStateException("访问url不能为空");
}
if (body != null) {
if (!TextUtils.isEmpty(body.contentType())) {
heads.put("Content-Type", body.contentType());
}
}
heads.put("Connection", "Keep-Alive");
heads.put("Charset", "UTF-8");
return new Request(this);
}
}
public enum HttpMethod {
GET("GET"),
POST("POST"),
PUT("PUT"),
DELETE("DELETE");
public String methodValue = "";
HttpMethod(String methodValue) {
this.methodValue = methodValue;
}
public static boolean checkNeedBody(HttpMethod method) {
return POST.equals(method) || PUT.equals(method);
}
public static boolean checkNoBody(HttpMethod method) {
return GET.equals(method) || DELETE.equals(method);
}
}
}
复制代码
这样整个请求块也就构建完毕了。剩下的无非是对具体请求体的抽象的具体实现,咱们再看看响应那边怎么实现的。
响应体这块主要存储为字节,能够转换成String类型进行返回,不作更具体的解析,没有直接提供流的缘由是设计上回调是在主线程中的,若是把流传入有须要本身作异步处理。
public class ResponseBody {
byte[] bytes;
public ResponseBody(byte[] bytes) {
this.bytes = bytes;
}
public byte[] bytes() {
return this.bytes;
}
public String string() {
try {
return new String(bytes(), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "";
}
}
复制代码
response相对就比较简单了,最关键的是服务端的返回码和响应正文。
public class Response {
final ResponseBody body;
final String message;
final int code;
public Response(Builder builder) {
this.body = builder.body;
this.message = builder.message;
this.code = builder.code;
}
public ResponseBody body() {
return this.body;
}
public int code() {
return this.code;
}
public String message() {
return this.message;
}
static class Builder {
private ResponseBody body;
private String message;
private int code;
public Builder body(ResponseBody body) {
this.body = body;
return this;
}
public Builder message(String message) {
this.message = message;
return this;
}
public Builder code(int code) {
this.code = code;
return this;
}
public Response build() {
if (message == null) throw new NullPointerException("response message == null");
if (body == null) throw new NullPointerException("response body == null");
return new Response(this);
}
}
}
复制代码
这样基本的请求和响应对象构建好了,中间须要向上面构建的方式进行调用,还须要引入Call和Callback做为请求的发起和回调接口。
call支持同步和异步方式的调用,同步直接返回Response,方法内部阻塞,异步提供一个回调接口回调结果。
public interface Call {
/**
* 同步执行
*
* @return response
*/
Response execute();
/**
* 异步执行
*
* @param callback 回调接口
*/
void enqueue(Callback callback);
}
复制代码
Callback 做为回调接口,提供成功和失败的回调,当访问网络成功并成功拿到数据则进入成功的回调,不然进入失败的回调。
public interface Callback {
/**
* 当成功拿到结果时返回
*
* @param response  返回结果
*/
void onResponse(Response response);
/**
* 当获取结果失败时
*
* @param request  请求
* @param e  Http请求过程当中可能产生的异常
*/
void onFail(Request request, IOException e);
}
复制代码
还有一个关键的就是咱们客户端——CatHttp了。
CatHttpClient 主要配置了一些超时信息之类的,主要是做为客户端的抽象,做为Call(这一呼叫服务端链接动做的外部发起者)。
public class CatHttpClient {
private Config config;
public CatHttpClient(Builder builder) {
this.config = new Config(builder);
}
public Call newCall(Request request) {
return new HttpCall(config, request);
}
static class Config {
final int connTimeout;
final int readTimeout;
final int writeTimeout;
public Config(Builder builder) {
this.connTimeout = builder.connTimeout;
this.readTimeout = builder.connTimeout;
this.writeTimeout = builder.writeTimeout;
}
}
public static final class Builder {
private int connTimeout;
private int readTimeout;
private int writeTimeout;
public Builder() {
this.connTimeout = 10 * 1000;
this.readTimeout = 10 * 1000;
this.writeTimeout = 10 * 1000;
}
public Builder readTimeOut(int readTimeout) {
this.readTimeout = readTimeout;
return this;
}
public Builder connTimeOut(int connTimeout) {
this.connTimeout = connTimeout;
return this;
}
public Builder writeTimeOut(int writeTimeout) {
this.writeTimeout = writeTimeout;
return this;
}
public CatHttpClient build() {
return new CatHttpClient(this);
}
}
}
复制代码
这样,请求和响应还有请求和回调的接口都约定好了,关键的就在于任务的执行过程和任务的调度了,由于网络请求都是耗时的,因此必然须要异步去处理网络请求才能最大的发挥框架的性能。咱们须要构建一个具体的任务——Task。
能够看到,HttpTask实现了Runnable接口,内部实际访问网路请求的操做交给了IRequestHandler来作,回调交给了IResponseHandler来作,最终拿到了Response结果
public class HttpTask implements Runnable {
private HttpCall call;
private Callback callback;
private IRequestHandler requestHandler;
private IResponseHandler handler = IResponseHandler.RESPONSE_HANDLER;
public HttpTask(HttpCall call, Callback callback, IRequestHandler requestHandler) {
this.call = call;
this.callback = callback;
this.requestHandler = requestHandler;
}
@Override
public void run() {
try {
Response response = requestHandler.handlerRequest(call);
handler.handlerSuccess(callback, response);
} catch (IOException e) {
handler.handFail(callback, call.request, e);
e.printStackTrace();
}
}
}
复制代码
IRequestHandler是实际网络请求的发起者,由于是面向接口编程,外部不用管内部的实现细节,只要调用方法拿到结果就好了。
public interface IRequestHandler {
/**
* 处理请求
*
* @param call  一次请求发起
* @return 应答
* @throws IOException  网络链接或者其它异常
*/
Response handlerRequest(HttpCall call) throws IOException;
}
复制代码
看到这里应该明白,这里无非就是包装了一层,实际内部是调用了handler的post(Runnable r)方法将结果回调到主线程中,也就是Callback接口的回调方法被咱们切换到了主线程中执行。
public interface IResponseHandler {
/**
* 线程切换,http请求成功时的回调
*
* @param callback  回调接口
* @param response  返回结果
*/
void handlerSuccess(Callback callback, Response response);
/**
* 线程切换,http请求失败时候的回调
*
* @param callback  回调接口
* @param request  请求
* @param e  可能产生的异常
*/
void handFail(Callback callback, Request request, IOException e);
IResponseHandler RESPONSE_HANDLER = new IResponseHandler() {
Handler HANDLER = new Handler(Looper.getMainLooper());
@Override
public void handlerSuccess(final Callback callback, final Response response) {
Runnable runnable = new Runnable() {
@Override
public void run() {
callback.onResponse(response);
}
};
execute(runnable);
}
@Override
public void handFail(final Callback callback, final Request request, final IOException e) {
Runnable runnable = new Runnable() {
@Override
public void run() {
callback.onFail(request, e);
}
};
execute(runnable);
}
/**
* 移除全部消息
*/
public void removeAllMessage() {
HANDLER.removeCallbacksAndMessages(null);
}
/**
* 线程切换
* @param runnable
*/
private void execute(Runnable runnable) {
HANDLER.post(runnable);
}
};
}
复制代码
能够看到,上面全部的内容,就差一点可以所有连通,就在于任务的调度,也就是调用线程的执行,必然在Call实体类的enqueue和execute方法中经过任务调度来执行Runnable内部的逻辑的。
能够看到,做为一个单例类,内部对外提供了同步执行和异步执行task的接口,内部经过线程池来实现,采用生产者-消费者模式,全部客户端提交的任务都会先进入到无界队列BlockingQueue中,线程池满的拒绝策略也是将当前没法被执行的任务放入BlockingQueue中,而在一开始就开了一个Runnable死循环从BlockingQueue中不断取任务执行。
public class HttpThreadPool {
/**
* 线程核心数
*/
public static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors();
/**
* 最大存活时间
*/
public static final int LIVE_TIME = 10;
/**
* 单例对象
*/
private static volatile HttpThreadPool threadPool;
/**
* 无界队列
*/
private BlockingQueue<Future<?>> queue = new LinkedBlockingQueue<>();
/**
* 线程池
*/
private ThreadPoolExecutor executor;
public static HttpThreadPool getInstance() {
if (threadPool == null) {
synchronized (HttpThreadPool.class) {
if (threadPool == null) {
threadPool = new HttpThreadPool();
}
}
}
return threadPool;
}
private HttpThreadPool() {
executor = new ThreadPoolExecutor(CORE_POOL_SIZE, CORE_POOL_SIZE+1, LIVE_TIME , TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(4), rejectHandler);
executor.execute(runnable);
}
/**
* 消费者
*/
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
FutureTask<?> task = null;
try {
task = (FutureTask<?>) queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (task != null) {
executor.execute(task);
}
}
}
};
/**
* 同步提交任务
*
* @param task  任务
* @return response对象
* @throws ExecutionException
* @throws InterruptedException
*/
public synchronized Response submit(Callable<Response> task) throws ExecutionException, InterruptedException {
if (task == null) throw new NullPointerException("task == null , 没法执行");
Future<Response> future = executor.submit(task);
return future.get();
}
/**
* 添加异步任务
*
* @param task
*/
public void execute(FutureTask<?> task) {
if (task == null) throw new NullPointerException("task == null , 没法执行");
try {
queue.put(task);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 拒绝策略,若是当线程池中的阻塞队列满,则添加到link队列中
*/
RejectedExecutionHandler rejectHandler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
try {
queue.put(new FutureTask<>(runnable, null));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}
复制代码
能够看到,上面除了调度的HttpThreadPool类,其他类基本都是抽象类或者接口,可是上面的这些接口和抽象类,相信看懂的童鞋应该明白,网络框架已经能够"运行"了。这里的运行固然不是说能在编译器或者具体的手机上运行,可是框架内部已经打通了任督二脉,能够完美的调度了。代码在github上——传送门
剩下的具体的类和内容在这篇文章
手写Android网络框架——CatHttp(二)