手?jǐn)]丐中丐版OkHttp
通過簡單實(shí)現(xiàn)的方式來加深框架的理解,在實(shí)現(xiàn)過程中思考源碼是怎么實(shí)現(xiàn)的,為什么這么做。
Request 和 Response
根據(jù)網(wǎng)絡(luò)的基礎(chǔ),首先創(chuàng)建Request請求和Response響應(yīng)兩個基礎(chǔ)類。
Request包含請求地址,請求頭,請求方式(GET/POST等)。支持設(shè)置請求地址,其他參數(shù)直接給填充上。
public class Request {
final Map<String, String> headers;
final String url;
final String method;
public Request(String url) {
this.url = url;
this.method = "GET";
this.headers = new HashMap<>();
this.headers.put("Content-Type", "application/json;charset=UTF-8");
}
}
Response包含發(fā)起請求的Request信息,響應(yīng)頭,響應(yīng)狀態(tài)碼和結(jié)果。
public class Response {
final Request request;
final Map<String, String> headers;
final int code;
final String message;
public Response(Request request) {
this.request = request;
this.headers = new HashMap<>();
this.headers.put("Content-Type", "application/json;charset=UTF-8");
this.code = 200;
this.message = "Success";
}
}
創(chuàng)建一個Call接口,當(dāng)前只包含一個execute方法,用來執(zhí)行Request并返回結(jié)果。
public interface Call {
Response execute(Request request);
}
創(chuàng)建一個Activity用來測試流程。
- 實(shí)現(xiàn)Call接口
- 創(chuàng)建請求對象
- 使用call.execute執(zhí)行請求,返回對象
public class HttpTestActivity extends AppCompatActivity {
public static final String TAG = "HttpTestActivity";
private TextView tvResponse;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_http);
final Call call = new Call() {
@Override
public Response execute(Request request) {
return new Response(request);
}
};
tvResponse = findViewById(R.id.tv_response);
findViewById(R.id.btn_request).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Response response = call.execute(new Request("http://www.baidu.com"));
Log.e(TAG, response.toString());
tvResponse.setText(response.toString());
}
});
}
}
查看控制臺打印的結(jié)果
Response{
request=Request{
headers={Content-Type=application/json;charset=UTF-8},
url='http://www.baidu.com,
method='GET
},
headers={Content-Type=application/json;charset=UTF-8},
code=200,
message='Success
}
我們首先打通了這條路,實(shí)現(xiàn)了單機(jī)版的同步請求。接下來對流程上的每一項(xiàng)進(jìn)行填充,實(shí)現(xiàn)異步執(zhí)行,攔截器等功能。
異步任務(wù)
早在Android4.X時代,谷歌官方就禁止了在主線程中執(zhí)行網(wǎng)絡(luò)請求。所以一個網(wǎng)絡(luò)請求框架必須要具備異步請求的功能。
首先改造接口類call,增加enqueue方法,讓請求放入任務(wù)隊列中等待執(zhí)行。此外由于是異步操作,需要定義回調(diào)接口。
//回調(diào)接口
public interface Callback {
void onResponse(Response response);
}
public interface Call {
Response execute(Request request);
void enqueue(Request request,Callback callback);
}
接下來定義線程池,進(jìn)行入隊任務(wù)的處理。在源碼中線程池定義在Dispatch類中進(jìn)行所有任務(wù)的管理,由于是丐中丐,直接創(chuàng)建RealCall類將線程池定義在此。
public class RealCall implements Call {
public static final String TAG = "RealCall";
private @Nullable
ExecutorService executorService;
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
return executorService;
}
@Override
public Response execute(Request request) {
return new Response(request);
}
@Override
public void enqueue(final Request request, final Callback callback) {
Runnable runnable = new Runnable() {
@Override
public void run() {
//打印線程信息
Log.e(TAG, "run: " + Thread.currentThread().getName());
try {
//模擬網(wǎng)絡(luò)請求耗時
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
callback.onResponse(new Response(request));
}
};
executorService().execute(runnable);
}
}
我們的網(wǎng)絡(luò)請求往往是短暫的,次數(shù)較多的任務(wù)。所以采用的是與Executors.newCachedThreadPool()一致的設(shè)置,源碼中設(shè)置了線程工廠用來設(shè)置線程的名字。在Activity中點(diǎn)擊按鈕觸發(fā)測試。會在不同的線程執(zhí)行任務(wù)。
call.enqueue(new Request("http://www.baidu.com"), new Callback() {
@Override
public void onResponse(Response response) {
Log.e(TAG, response.toString());
}
});
--------------------------------
E/RealCall: run: pool-1-thread-1
E/RealCall: run: pool-1-thread-2
E/RealCall: run: pool-1-thread-3
E/RealCall: run: pool-1-thread-4
E/RealCall: run: pool-1-thread-4
E/RealCall: run: pool-1-thread-3
E/RealCall: run: pool-1-thread-2
E/RealCall: run: pool-1-thread-3
E/RealCall: run: pool-1-thread-2
攔截器的實(shí)現(xiàn)
在源碼中分別有interceptors 和 networkInterceptors 兩塊攔截器,分別代表網(wǎng)絡(luò)請求前對Request進(jìn)行處理和在請求后對響應(yīng)結(jié)果Response進(jìn)行處理。
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
final List<Interceptor> interceptors;
final List<Interceptor> networkInterceptors;
}
在實(shí)際場景中,我們會對OkHttpClient設(shè)置統(tǒng)一的token信息,在請求完成后對結(jié)果進(jìn)行打印。接下來就通過責(zé)任鏈模式來實(shí)現(xiàn)攔截器。
仿造源碼實(shí)現(xiàn)攔截器接口類
public interface Interceptor {
Response intercept(Chain chain);
interface Chain {
Request request();
Response proceed(Request request);
}
}
定義處理Request,執(zhí)行網(wǎng)絡(luò)請求(用Request獲取Response),處理Response三種類型的攔截器。
public static class NetWorkInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) {
// 執(zhí)行網(wǎng)絡(luò)請求,先執(zhí)行之前對request的處理,調(diào)用AddParamsInterceptor
chain.proceed(chain.request());
try {
//模擬網(wǎng)絡(luò)請求耗時
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new Response(chain.request());
}
}
public static class AddParamsInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) {
// 對Request進(jìn)行處理
Request request = chain.request();
request.headers.put("token", "SElLIFlpTWpGbEcwSVQ1dit1Wm86ejdCbUhFVnRrcTFTV0FPWGNMVWJuZDF0ekY4dHpRbUkwRys1V21DMXYvWT06MTU4NTY1MjYyMjQ0NA==");
return chain.proceed(request);
}
}
public static class LoggerInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) {
Response response = chain.proceed(chain.request());
//對response進(jìn)行操作,先之前網(wǎng)絡(luò)請求前的request
Log.e(TAG, "intercept: " + response.toString());
return response;
}
}
改寫enqueue為責(zé)任鏈調(diào)用。
@Override
public void enqueue(final Request request, final Callback callback) {
Runnable runnable = new Runnable() {
@Override
public void run() {
//打印線程信息
Log.e(TAG, "run: " + Thread.currentThread().getName());
// 請求發(fā)起者
Interceptor.Chain next = new Interceptor.Chain() {
@Override
public Request request() {
return request;
}
@Override
public Response proceed(Request request) {
return null;
}
};
for (final Interceptor interceptor : interceptors) {
final Interceptor.Chain finalNext = next;
Interceptor.Chain chain = new Interceptor.Chain() {
@Override
public Request request() {
return finalNext.request();
}
@Override
public Response proceed(Request request) {
return interceptor.intercept(finalNext);
}
};
next = chain;
}
callback.onResponse(next.proceed(request));
}
};
executorService().execute(runnable);
}
責(zé)任鏈這塊的實(shí)現(xiàn)寫了很久,這種設(shè)計模式平時是真沒用過。關(guān)聯(lián)了下事件分發(fā)機(jī)制大概好理解些。再者思考下樹的遍歷。
//先序遍歷
find(Node root){
System.out.println(root.value);
find(root.left);
find(root.right);
}
//中序遍歷
find(Node root){
find(root.left);
System.out.println(root.value);
find(root.right);
}
//后序遍歷
find(Node root){
find(root.left);
find(root.right);
System.out.println(root.value);
}
與上面三種狀態(tài)的攔截器處理類似。
其他
優(yōu)秀的框架肯定有優(yōu)秀的封裝,上面丐中丐只是梳理了異步請求與攔截器的實(shí)現(xiàn)與源碼差距甚大,甚至根本沒有走通網(wǎng)絡(luò)。在OkHttpClient源碼中使用了門面模式,來管理攔截器列表,分發(fā)實(shí)例,設(shè)置超時時間,管理連接池等,同時使用Builder模式實(shí)現(xiàn)默認(rèn)參數(shù)設(shè)置與鏈?zhǔn)絼?chuàng)建。
除以上設(shè)計模式以外需要掌握線程池的默認(rèn)設(shè)置,為什么?如何管理連接池?