在實際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(二)