okHttp, square 公司开源的网络请求神器,截止到 2019-09-02,在 Github 上面已经超过 34K 的 star,足见他的受欢迎程度。
到目前为止,他的最新版本是 4.1.0, 使用 kotlin 语言写的,由于本人对 kotlin 语言不是很熟悉,这篇文章已 3.5.0 的版本为基础进行分析。
Rxjava+Okhttp+Refrofit 如今已经成为项目网络请求的首选,在讲解原理之前,我们先来看一下 Okhttp 的基本使用。
使用 OkHttp 基本是以下四步:
//创建OkHttpClient对象 OkHttpClient client = new OkHttpClient(); String run(String url) throws IOException { //创建Request请求对象 Request request = new Request.Builder() .url(url) .build(); //创建Call对象,并执行同步获取网络数据 Response response = client.newCall(request).execute(); return response.body().string(); } 复制代码
void runAsync(String url, Callback callback) { OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Request.Builder builder = request.newBuilder().addHeader("name", "test"); return chain.proceed(builder.build()); } }).build(); //创建Request请求对象 Request request = new Request.Builder() .url(url) .build(); client.newCall(request).enqueue(callback); } 复制代码
接下来我会从这四步,分析 Okhttp 的基本原理。
创建 OkHttpClient 一般有两种方法,一种是直接 new OkHttpClient()
,另外一种是通过 OkHttpClient.Builder()
OkhttpClient client = new OkHttpClient .Builder() .connectTimeout(5, TimeUnit.SECONDS) .writeTimeout(10,TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build(); 复制代码
第二种创建方式主要是通过建造者模式,来配置一些参数,比如连接超时时间,读写超时时间,超时重试次数等。这样有一个好处,可以对外屏蔽掉构建 client 的细节。关于建造者模式的,有兴趣的可以读我的这一篇文章 建造者模式(Builder)及其应用
public final class Request { final HttpUrl url; final String method; final Headers headers; final RequestBody body; final Object tag; private volatile CacheControl cacheControl; // Lazily initialized. } 复制代码
Request 对象主要封装的是一些网络请求的信息,比如请求 url,请求方法,请求头,请求 body 等,也比较简单,这里不再展开阐述。
@Override public Call newCall(Request request) { return new RealCall(this, request, false /* for web socket */); } 复制代码
可以看到 call 对象实际是 RealCall 的实例化对象
@Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); try { // 执行 client.dispatcher() 的 executed 方法 client.dispatcher().executed(this); Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally { // 最后再执行 dispatcher 的 finish 方法 client.dispatcher().finished(this); } } 复制代码
在 execute 方法中,首先会调用 client.dispatcher().executed(this)
加入到 runningAsyncCalls 队列当中,接着执行 getResponseWithInterceptorChain()
获取请求结果,最终再执行 client.dispatcher().finished(this)
将 realCall 从 runningAsyncCalls 队列中移除 。
我们先来看一下 getResponseWithInterceptorChain
方法
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest); return chain.proceed(originalRequest); } 复制代码
可以看到,首先,他会将客户端的 interceptors 添加到 list 当中,接着,再添加 okhhttp 里面的 interceptor,然后构建了一个 RealInterceptorChain
对象,并将我们的 List<Interceptor>
作为成员变量,最后调用 RealInterceptorChain 的 proced 方法。
Interceptor 里面是怎样实现的,这里我们暂不讨论,接下来,我们来看一下 proceed 方法
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, Connection connection) throws IOException { // 省略无关代码 // 生成 list 当中下一个 interceptot 的 chain 对象 RealInterceptorChain next = new RealInterceptorChain( interceptors, streamAllocation, httpCodec, connection, index + 1, request); // 当前的 interceptor Interceptor interceptor = interceptors.get(index); // 当前的 intercept 处理下一个 intercept 包装的 chain 对象 Response response = interceptor.intercept(next); // ---- return response; } 复制代码
proceed 方法也很简单,proceed方法每次从拦截器列表中取出拦截器,并调用 interceptor.intercept(next)。
熟悉 Okhttp 的应该都在回到,我们在 addInterceptor 创建 Interceptor 实例,最终都会调用 chain.proceed(Request request)
,从而形成一种链式调用。关于责任链模式的可以看我的这一篇文章 责任链模式以及在 Android 中的应用
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Request.Builder builder = request.newBuilder().addHeader("name","test"); return chain.proceed(builder.build()); } }).build(); 复制代码
而 OkHttp 是怎样结束循环调用的,这是因为最后一个拦截器 CallServerInterceptor 并没有调用chain.proceed(request),所以能够结束循环调用。
public final class Dispatcher { private int maxRequests = 64; private int maxRequestsPerHost = 5; private Runnable idleCallback; /** Executes calls. Created lazily. */ private ExecutorService executorService; // 异步的请求等待队列 private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); // 异步的正在请求的队列 private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); // 绒布的正在请求的队列 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); } 复制代码
@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); client.dispatcher().enqueue(new AsyncCall(responseCallback)); } 复制代码
首先,我们先来看一下 AsyncCall 这个类
final class AsyncCall extends NamedRunnable { private final Callback responseCallback; // ---- @Override protected void execute() { boolean signalledCallback = false; try { Response response = getResponseWithInterceptorChain(); // 判断请求是否取消了,如果取消了,直接回调 onFailure if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { // 请求成功 signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); } } } 复制代码
public abstract class NamedRunnable implements Runnable { protected final String name; public NamedRunnable(String format, Object... args) { this.name = Util.format(format, args); } @Override public final void run() { String oldName = Thread.currentThread().getName(); Thread.currentThread().setName(name); try { execute(); } finally { Thread.currentThread().setName(oldName); } } protected abstract void execute(); } 复制代码
可以看到 AsyncCall 继承 NamedRunnable, 而 NamedRunnable 是 Runnable 的子类,当执行 run 方法时,会执行 execute 方法。
我们再来看一下 dispatcher 的 enqueue 方法
synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } } 复制代码
可以看到当正在请求的请求数小于 maxRequests
的时候并且当前正在请求的队列里面相当 host 的小于 maxRequestsPerHost
, 直接添加到 runningAsyncCalls
队列中,并添加到线程池里面执行,否则添加到准备队列 readyAsyncCalls
里面。
当执行 executorService().execute(call)
的时候,会调用 run 方法, run 方法又会调用到 execute 方法进行网络请求,请求完成之后,会调用 client.dispatcher().finished(this)
从队列里面移除。
到此, Okhttp 的主要流程已经讲完
责任链模式以及在 Android 中的应用
观察者设计模式 Vs 事件委托(java)
装饰者模式及其应用
建造者模式(Builder)及其应用
二次封装图片第三方框架——简单工厂模式的运用
Android 二次封装网络加载框架
java 代理模式详解
Rxjava 2.x 源码系列 - 基础框架分析
Rxjava 2.x 源码系列 - 线程切换 (上)
Rxjava 2.x 源码系列 - 线程切换 (下)
Rxjava 2.x 源码系列 - 变换操作符 Map(上)
butterknife 源码分析
一步步拆解 LeakCanary
java 源码系列 - 带你读懂 Reference 和 ReferenceQueue
扫一扫,欢迎关注我的微信公众号 stormjun94(徐公码字) , 目前是一名程序员,不仅分享 Android开发相关知识,同时还分享技术人成长历程,包括个人总结,职场经验,面试经验等,希望能让你少走一点弯路。