手撸丐中丐版OkHttp
通过简单实现的方式来加深框架的理解,在实现过程中思考源码是怎么实现的,为什么这么做。
Request 和 Response
根据网络的基础,首先创建Request请求和Response响应两个基础类。
Request包含请求地址,请求头,请求方式(GET/POST等)。支持设置请求地址,其他参数直接给填充上。
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包含发起请求的Request信息,响应头,响应状态码和结果。
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";
}
}
创建一个Call接口,当前只包含一个execute方法,用来执行Request并返回结果。
public interface Call {
Response execute(Request request);
}
创建一个Activity用来测试流程。
- 实现Call接口
- 创建请求对象
- 使用call.execute执行请求,返回对象
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());
}
});
}
}
查看控制台打印的结果
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
}
我们首先打通了这条路,实现了单机版的同步请求。接下来对流程上的每一项进行填充,实现异步执行,拦截器等功能。
异步任务
早在Android4.X时代,谷歌官方就禁止了在主线程中执行网络请求。所以一个网络请求框架必须要具备异步请求的功能。
首先改造接口类call,增加enqueue方法,让请求放入任务队列中等待执行。此外由于是异步操作,需要定义回调接口。
//回调接口
public interface Callback {
void onResponse(Response response);
}
public interface Call {
Response execute(Request request);
void enqueue(Request request,Callback callback);
}
接下来定义线程池,进行入队任务的处理。在源码中线程池定义在Dispatch类中进行所有任务的管理,由于是丐中丐,直接创建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 {
//模拟网络请求耗时
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
callback.onResponse(new Response(request));
}
};
executorService().execute(runnable);
}
}
我们的网络请求往往是短暂的,次数较多的任务。所以采用的是与Executors.newCachedThreadPool()一致的设置,源码中设置了线程工厂用来设置线程的名字。在Activity中点击按钮触发测试。会在不同的线程执行任务。
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
拦截器的实现
在源码中分别有interceptors 和 networkInterceptors 两块拦截器,分别代表网络请求前对Request进行处理和在请求后对响应结果Response进行处理。
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
final List<Interceptor> interceptors;
final List<Interceptor> networkInterceptors;
}
在实际场景中,我们会对OkHttpClient设置统一的token信息,在请求完成后对结果进行打印。接下来就通过责任链模式来实现拦截器。
仿造源码实现拦截器接口类
public interface Interceptor {
Response intercept(Chain chain);
interface Chain {
Request request();
Response proceed(Request request);
}
}
定义处理Request,执行网络请求(用Request获取Response),处理Response三种类型的拦截器。
public static class NetWorkInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) {
// 执行网络请求,先执行之前对request的处理,调用AddParamsInterceptor
chain.proceed(chain.request());
try {
//模拟网络请求耗时
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进行处理
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进行操作,先之前网络请求前的request
Log.e(TAG, "intercept: " + response.toString());
return response;
}
}
改写enqueue为责任链调用。
@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());
// 请求发起者
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);
}
责任链这块的实现写了很久,这种设计模式平时是真没用过。关联了下事件分发机制大概好理解些。再者思考下树的遍历。
//先序遍历
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);
}
与上面三种状态的拦截器处理类似。
其他
优秀的框架肯定有优秀的封装,上面丐中丐只是梳理了异步请求与拦截器的实现与源码差距甚大,甚至根本没有走通网络。在OkHttpClient源码中使用了门面模式,来管理拦截器列表,分发实例,设置超时时间,管理连接池等,同时使用Builder模式实现默认参数设置与链式创建。
除以上设计模式以外需要掌握线程池的默认设置,为什么?如何管理连接池?