前沿
每个Android开发必不可少的网络框架,毋庸置疑,它就是okhttp,作为处理网络请求的开源项目,一度成为了最火热的Android轻量级框架。下面同学我简单记录下Okhttp框架的源码分析路程
okhttp框架的使用介绍
无论学习什么开源框架,我们首先学习的是如何去使用它,只有熟练使用了该框架,才可以更深入系统的去剖析它的底层原理
okhttp同步方法请求
- 首先,我们来简单看下okhttp的同步请求是如何使用的,大致有三个步骤
- 创建OkHttpClient和Request对象,具体使用了builder设计模式
- 将Request封装成Call对象
- 调用Call的execute()发送同步去请求(这个Call其实是一个接口) 具体代码可以查看如下:
private fun synRequest() {
val client = OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build()
val request = Request.Builder().url("https://www.baidu.com")
.get().build()
val call = client.newCall(request)
kotlin.runCatching {
val response = call.execute()
println(response.body().toString())
}.onFailure {
println("the error is $it")
}
}
ps:okhttp同步需要注意的是,发送请求后, 就会进入到阻塞状态,直到收到响应
okhttp异步方法请求
- 创建OkHttpClient和Request对象
- 将Request封装成Call对象
- 调用Call的enqueue方法进行异步请求,然后在Callback中的onResponse和onFailure中进行相应的处理
private fun asyRequest() {
val client = OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build()
val request = Request.Builder().url("https://www.baidu.com")
.get().build()
val call = client.newCall(request)
call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
println(e.message)
}
override fun onResponse(call: Call, response: Response) {
println(response.body().toString())
}
})
}
ps:需要注意的是,onResponse和onFailure都是在子线程中进行执行的
okhttp同步异步不同
- 发起请求的方法调用不同,同步的调用是Call.execute()方法,异步调用的是enqueue方法
- 阻塞线程与否,同步是会阻塞线程,但是异步不会,相反会开启一个新的工作线程去完成网络请求操作
- 同步与异步的话最大的区别在于,异步请求会开启的一个线程AsyncCall,其实简单来说就是一个Runnable
okhttp同步异步方法总结
ps:这里插入图片
- 简单来说,无论是同步请求还是异步请求,都要创建请求Request请求报文,通过Request中的newCall方法,创建好我们实际的http请求Call
- Call本身又是一个接口,它的具体实现类有在RealCall中,这时候需要判断调用的Chain方法进行同步请求, 还是AsyncCall进行异步请求的
- 不管是同步还是异步请求的,都会使用getResponseWithInterceptorChain方法,这是okhttp核心之一,内部其实是构建拦截器的链,通过依次执行这个拦截器链中的每一个不同作用的拦截器来获取服务器数据返回
- Dispatcher总控类,其实是okhttp的一个分发器,由它来对这个,异步请求进行执行还是就绪的选择
请求流程和源码分析
上述我们已经基本清楚okhttp请求的基本用法,下面我们通过相应的源码来分析一波Okhttp在同步和异步请求流程
同步请求流程源码分析
- 首先对于构建的okhttpClient,设置参数需要用到它的内部类,首先可以看下okhttpclient的具体构造方法,使用Builder设计模式,因为这个对象使用很多参数(额外话题,如果需要使用很多参数,那么就可以使用建造者模式就比较合适)
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
- dispatcher对象,上文已经说过是okhttp的分发器对象,对于同步请求没有做太大的操作,只是放在同步请求队列中执行
- connectPool连接池,我的理解是客户端于服务器的链接conntection,而每一个connection都会放在ConnectionPool中由它进行统一管理,实现了哪一些网络连接保持打开,哪一些可以复用。
- request请求
- 首先我们也分析也它的内部build的构造方法是怎么实现的,可以看到只有两行代码
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
- 第一行指定了请求方式,这里是GET
- 第二行创建了Headers内部类Builder对象,来进行保存它的头部信息
- 接着我们看下它的build方法
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
- 直接创建了个Request对象,然后把当前的build对象传递过去,这样意图就很明确了,就是将之前配置的请求方式,url,头部都设置给这个Request对象。
- 看下request构造方法,不出所料,就是为其指定了所需要的请求方法,网络地址,头部信息等等
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this;
}
- 这样我们就完成了同步请求的前两步
- 创建一个okhttpclient对象
- 构建了携带请求信息的Request对象
- 完成了前面两步之后,通过okhttpclient和request请求构建出我们shijihttp请求的call对象
- 首先它是通过newcall这个实现类进行实现的,发现它是通过RealCall去实现,源码如下
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
- 我们可以看下RealCall中的实现方式,它是怎么做的呢?
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
- 创建了RealCall对象,然后赋值了一个listener就返回了,这里值得自己去思考一下,这个方法到底完成了什么工作呢,还是到RealCall方法中去看下把
- 我们看下RealCall的方法是怎么写的
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
- 这里可以看到这里持有了前两步已经初始化好的okhttpclient和request两个对象
- 同时还赋值了一个重定向拦截器,关于拦截器部分也值得小伙伴们可以深入学习下
- 接下来就需要执行call.execute()方法来完成同步请求,我们开看看该方法的具体实现
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
- 在同步代码块中,首先判断executed标志位是否为true,这样为什么呢?意思是同一个http请求只会执行一次,如果没有执行过就会把标志位设为true,如果执行过就会抛出异常
- captureCallStackTrace()捕捉一些http请求堆栈信息
- 开启了监听时间callStart(),源码注释写的很清晰了,每当我们的excute或者enqueue方法就会开启这个listener
- 当然接下来才是我们最关注的,dispatcher方法以及同步请求的excute方法,这里可以看下具体实现,其实就是通过synchronized方法把它添加到同步请求队列当中
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
- 对于dispatch方法呢,有什么作用呢?我的理解是维持call请求发给它的状态,同时它也维护了一个线程池用于执行网络请求;call请求在执行任务的时候,通过dispatch这个分发基类,推到执行队列当中进行操作,操作完成后执行等待队列的任务
- 上面都执行完成后,getResponseWithInterceptorChain()方法会依次调用拦截器来进行相应的操作
- 最后有一个很微妙,很重要的机制,finish会主动回收某些同步请求,可以看下具体实现,首先同步请求传入进来,首要操作就是从当前队列中移除同步请求,如果不能移除就抛出异常,这里可以看下由于传入的第三个参数是false,所以同步请求是不会调用promoteCalls()方法,但是异步请求会;然后调用runningCallsCount()来计算目前还在执行的请求的数量,最后有个判断,如果正在执行的请求数为0,同时满足idleCallback不为空的时候,然后就调用它的run方法
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
通过自己的理解,至此同步请求流程源码分析完成了
同步请求流程总结
- 创建一个OkhttpClient对象
- 构建一个Request对象,通过OkhttpClient和Request对象,构建出Call对象
- 执行call的execute方法,相对异步请求来说,dispatcher分发在同步请求中作用没有这么大