安卓开源框架学习|OKHttp原理讲解之基本概念

前言: 1.1为什么要写这篇文章? OKHttp是square出品的开源通信框架,目前市面上大多数的APP都使用了该框架,那为什么这么多的APP都选择使用该框架呢?有的人说别人都用我也就用呗,那肯定不是的。本文就带你了解为什么我们要使用OKHttp,以及OKHttp的原理。

1.2 OKHttp项目地址: https://github.com/square/okhttp

1.3 系列简介: OKHttp项目本身还是很大的,而且涉及到的知识点很多,所以一篇文章是很难概括全的,所以准备写一个系列的文章来详细讲解OKHttp。
准备写以下几篇。
OKHttp原理讲解之基本概念(预计03.15发布)
OKHttp原理讲解之责任链模式及扩展(预计03.18发布)
OKHttp原理讲解之重试拦截器(预计03.22发布)
OKHttp原理讲解之路由拦截器
OKHttp原理讲解之缓存拦截器
OKHttp原理讲解之连接池拦截器
OKHttp原理讲解之请求拦截器
PS:预计每周更新1-2篇的进度进行

1.4 OKHttp系列第一篇 本篇主要讲解以下部分:
1.OKHttp的简单使用
2.OKHttp的几个主要概念
3.OKHttp发送请求的基本流程
4.调度器中线程的管理
5.为什么选择使用OKHttp

第一章:OKHttp的几个主要概念 PS:以下几个中文名字都是按照我自己的理解取的,不是官方起名。
1.配置中心,OKHttpClient
OKHttp是一个可以高度定制化的框架,它提供了很多可配置的功能项,可以由开发者根据自主选择去配置。比如Dns,Proxy,connectTimeout等等。
而OKHttpClient就是这些配置的一个容器,它使用构造者模式去创建,开发者可以根据自己的需求自主选择。

2.请求体,RealCall
对于每一个请求,OKHttp都封装了了一个RealCall来执行具体请求的流程。所以自然的,五层责任链也是由它来创建和调用的。

3.调度器,Dispatcher
调度器负责每个请求体的具体执行。所以自然的,调度器中包含了一个请求线程池,以及请求队列,执行队列,等待队列等等。

4.责任链,List
OKHttp中默认包含五层责任链,分别为
RetryAndFollowUpInterceptor:重试跟进拦截器
BridgeInterceptor:桥接拦截器
CacheInterceptor:缓存拦截器
ConnectInterceptor:连接池拦截器
CallServerInterceptor:请求拦截器
一个请求的完整发送流程,会从上向下依次执行,如果某一个拦截器判断执行完成,则一层一层向上返回最终结果。
所以五层拦截器也是OKHttp的核心,后面会有专门的篇章来一一进行讲解。

5.请求,Request
顾名思义,包含请求所需要的所有信息。

6.响应,Response
顾名思义,包含响应所需要的所有信息。

第二章:OKHttp基本使用 这不是本文要讲解的重点,所以只是简单的介绍下使用方式。
2.1配置OKHttpClient 这里我只进行了一个配置,配置了一个缓存目录。

val builder = OkHttpClient.Builder() builder.cache(Cache(File(context?.filesDir?.absolutePath + File.separator + "ok"), 100)) val client = builder.build()


2.2发送请求, 请求分两种,同步请求和异步请求,同步请求需要在子线程执行。
第一种同步的方式:
val builder = Request.Builder() val request = builder.url("https://www.baidu.com").build() val newCall = client.newCall(request) service.execute { val response = newCall.execute() }


第二种异步的方式:
val builder = Request.Builder() val request = builder.url("https://www.baidu.com").build() val newCall = client.newCall(request) newCall.enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { logI(call.toString()); }override fun onResponse(call: Call, response: Response) {}})


2.3 处理返回值 reponse中包含返回的数据流,我们只要序列化返回的data数组即可。
val content = IOHelper.readStrByCode(response.body()?.byteStream(), "utf-8") logI(content)

readStrByCode方法链接:
//todo

第三章:OKHttp发送主体流程 异步和同步请求其实流程上差不多,只不过同步请求是直接返回reponse,而异步则是通过回调的方式通知。这里就以异步请求为例,讲解整个请求的发送流程。
3.1 构建请求体RealCall 这里构建的RealCall中,主要包含原始的request,以及事件监听。
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; }


3.2 开始请求流程RealCall.enqueue() 这里主要做两件事,
第一件事,对于观察者进行回调
第二件事,交由调度器,在线程中执行AsyncCall的execute方法。
public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); client.dispatcher().enqueue(new AsyncCall(responseCallback)); }


3.3 执行请求流程execute() 首先调用责任链,去获取最终的响应。
然后进行通知callBack进行对应的回调。
最终通过调度器完整的结束该请求
protected void execute() { boolean signalledCallback = false; try { Response response = getResponseWithInterceptorChain(); 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 { eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); } } }



第四章:调度器中线程的管理 上面讲了由调度器负责把一个一个的请求任务,分发给线程去管理。那么线程是如何管理的呢?
4.1 Dispatcher中的enqueue()方法 4.1.1 方法加锁:
这个方法是加锁的,避免多线程添加任务时,出现冲突。
4.1.2 判断数量是否超标
这里判断了正在执行的请求数量是否大于64,以及单个链接的请求数是否大于5(这里的64和5是可以配置的,单个链接指的是请求接口地址是一样的),如果超过,则排队;否则则可以立即执行。
synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }

4.1.3 获取并创建线程池
1.判断是否存在线程池
如果不存在,则创建线程池。否则直接返回
这里扩展一个小问题,为什么使用的时候才创建线程池呢?我猜测是为了性能,提早创建了线程池不用浪费性能。
2.创建线程池
这里使用的线程池配置参数,核心线程数为0,最大线程数为MAX_VALUE,非核心线程存活时间为60秒。
队列使用的是SynchronousQueue,适合生产者消费者模型。具体SynchronousQueue可以自行百度,这里就不扩展了。
public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; }

3.为什么不会出现性能问题
我们这里看到,OKHttp请求线程池数量并没有设置上线,那么不会存在性能问题吗?实际上由于之前已经有了maxRequest的限制,所以线程数量并不会真的达到MAX_VALUE的级别。
所以可以这么理解OK的线程池策略,同时并发每一个请求,那么每一个请求都是一个单独线程去处理。如果一个请求完成后,那么其所占用的线程就可以被释放供后续请求使用。
4.自定义线程池策略
即使已经限制了最大请求数64,对于某些性能较差的手机,有可能真的大并发请求时还是吃力的。所以没关系,OK的一大特色就是可配置性强。没关系,线程池我们可以自定义传入,让OK使用我们自定义的线程池。
我们可以通过Dispatcher的有参构造函数传入自定义线程池。
val newSingleThreadExecutor = Executors.newSingleThreadExecutor() val dispatcher = Dispatcher(newSingleThreadExecutor) val builder1 = OkHttpClient.Builder() builder1.dispatcher(dispatcher); val build = builder.build()


4.1.4 线程池调度调用Call
1.执行AsyncCall
获取到线程池后,就可以交给其最终的call任务,让其去执行。最终会调用到AsyncCall
的execute方法。
executorService().execute(call);

2.为什么会执行AsyncCall的execute方法
AsyncCall继承自NameRunnable,NameRunnable继承自Runnable。NameRunnable中的实现如下,所以会执行NameRunnable的execute方法。
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(); }


第五章:为什么选择使用OKHttp 常规答案 这里面试经常被问到的一道题,我面试别人的时候也经常问。很常规的答案如下:
1.大家都在用,所以我也就用。
2.大厂出门的,稳定性好,比较放心。
3.使用简单,用起来比较方便。
4.持续维护中,不担心出了问题没人解决
更为合适的答案 但是其实这题面试官更想考验的是对OKHttp的了解程度,所以除了上面那些优势,我们还可以从OKHttp框架的优势的角度去谈一谈:
1.可扩展性高。类似于缓存,Dns,请求/连接/响应超时时间等等都可以通过配置传入,甚至线程池都可以根据自己的需求来配置。
2.OKHttp使用了连接池缓存,提高通信效率。
3.责任链五层拦截器模式,每层功能清晰明了,并且提供了两层可扩展的拦截器方便进行所需要的改造。
4.层次结构清晰,方便进行问题的排查。
5.观察者模式的充分使用,查看请求状态和监控请求状态变得十分简单。
6.使用了OKIO框架进行数据的处理,效率和安全性上更高。
等等,随着文章的不断详细逐渐补充。



【安卓开源框架学习|OKHttp原理讲解之基本概念】

    推荐阅读