Android技能树 — 网络小结(6)之 OkHttp超超超超超超超详细解析

本文系列介绍了Android网络体系结构、TCP/UDP、HTTP/HTTPS等基础知识,重点解析了OkHttp和Retrofit的源码,包括参数配置、请求分发和拦截器原理。还涉及了网络缓存、Cookie管理等内容。
摘要由CSDN通过智能技术生成

===

本文也做了一次标题党,哈哈,其实写的还是很水,各位原谅我O(∩_∩)O。

介于自己的网络方面知识烂的一塌糊涂,所以准备写相关网络的文章,但是考虑全部写在一篇太长了,所以分开写,希望大家能仔细看,最好可以指出我的错误,让我也能纠正。

1.讲解相关的整个网络体系结构:

Android技能树 — 网络小结(1)之网络体系结构

2.讲解相关网络的重要知识点,比如很多人都听过相关网络方面的名词,但是仅限于听过而已,什么tcp ,udp ,socket ,websocket, http ,https ,然后webservice是啥,跟websocket很像,socket和websocket啥关系长的也很像,session,token,cookie又是啥。

Android技能树 — 网络小结(2)之TCP/UDP

Android技能树 — 网络小结(3)之HTTP/HTTPS

Android技能树 — 网络小结(4)之socket/websocket/webservice

相关网络知识点小结- cookie/session/token(待写)

3.相关的第三方框架的源码解析,毕竟现在面试个大点的公司,okhttp和retrofit源码是必问的。

Android技能树 — 网络小结(6)之 OkHttp超超超超超超超详细解析

Android技能树 — 网络小结(7)之 Retrofit源码详细解析


这里提一个本文无关的小知识点,很多文章开头都会提到,我们以okhttp3.xxx版本来讲解,那怎么看当前最新的已经是几了呢?(主要以前也有人问过我在哪里查看xxx第三方库最新的版本,所以想到提一下这个)其实很简单,我们以okhttp为例:

  1. Android Studio直接查看:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. JCenter上查看: JCenter上搜索Okhttp版本

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. Maven上查看: Maven上搜索Okhttp版本

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. …其他方式

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

正文

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

看不清楚的,可以右键,选择新标签页中打开,然后点击图片放大

首先我们来确定总体大纲:

  1. okhttp相关参数配置,比如设置超时时间,网络路径等等等等等…
  2. 我们知道在使用okhttp的时候可以使用同步请求,也可以使用异步请求,所以肯定不同的请求,在分发的时候有不同的处理。
  3. 我们以前网络系列的文章提过,发送到后台,肯定是一个完整的请求包,但是我们使用okhttp的时候,只是转入了我们需要给后台的参数,甚至我们如果是get请求,只是传入了相应的url网络地址就能拿到数据,说明okhttp帮我们把简单的参数输入,然后通过一系列的添加封装,然后变成一个完整的网络请求包出去,然后我们在使用okhttp的时候,拿到返回的数据也已经是我们可以直接用的对象,说明接受的时候,已经帮我们把拿到的返回网络包,解析成我们直接用的对象了。所以在一系列帮我们发送的时候添加参数变成完整网络请求包,收到时候帮我们解析返回请求包的过程,是Okhttp的一个个拦截器们所处理,它拦截到我们的数据,然后进行处理,比如添加一些数据,变成完整的网络请求包等操作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所以我们大概就知道了okhttp一般的主要内容为这三大块。

1.okhttp基础使用:

讲解源码前,先写上okhttp基本使用,这样才更方便讲解源码:

String url = “http://www.baidu.com”;
//‘1. 生成OkHttpClient实例对象’
OkHttpClient okHttpClient = new OkHttpClient();
//‘2. 生成Request对象’
Request request = new Request.Builder().url(url).build();
//‘3. 生成Call对象’
Call call = okHttpClient.newCall(request);
//‘4. 如果要执行同步请求:’
try {
call.execute();
} catch (IOException e) {
e.printStackTrace();
}
//‘5. 如果要执行异步请求:’
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}

@Override
public void onResponse(Call call, Response response) throws IOException {
}
});

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2. 初始化相关参数解析:

我们来看我们最刚开始的完整流程图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后配合上面第一步的okhttp基本使用,发现在执行同步和异步前,我们要先准备好OkhttpClientRequestCall对象。我们一步步来看相关源码:

2.1 OkHttpClient相关:

我们上面的代码实例化OkHttpClient对象的代码是:

OkHttpClient okHttpClient = new OkHttpClient();

我们进入查看:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

发现OkHttpClient除了空参数的构造函数,还有一个传入Builder的构造函数,而我们的new OkHttpClient()最终也是调用了传入Builder的构造函数,只不过传入默认的Builder对象值,如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以看到最后几个值:


connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;

默认的连接超时,读取超时,写入超时,都为10秒,然后还有其他等默认属性,那我们加入想要改变这些属性值呢,比如超时时间改为20秒,很简单。我们不使用默认的Builder对象即可:

OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(20,TimeUnit.SECONDS);
builder.readTimeout(20,TimeUnit.SECONDS);
builder.writeTimeout(20,TimeUnit.SECONDS);
OkHttpClient okHttpClient = builder.build();

//这里不能直接使用那个传入Builder对象的OkHttpClient的构造函数,因为该构造函数的方法不是public的
OkHttpClient okHttpClient = new OkHttpClient(builder);//这样是错误的
builder.build();的源码是:
public OkHttpClient build() {
return new OkHttpClient(this);
}

我们再回过头来看看OkHttpClient里面设置的属性值都有什么用:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • Dispatch:分发器,后面会提到

  • Proxy:设置代理,通常为类型(http、socks)和套接字地址。参考文章:直接使用Proxy创建连接

  • ProxySelector: 设置全局的代理,通过继承该类,设置具体代理的类型、地址和端口。参考文章:Java代理 通过ProxySelector设置全局代理

  • Protocol: 网络协议类,比如我们经常听到的http1.0、http1.1、http2.0协议等。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • ConnectionSpec: 指定HTTP流量通过的套接字连接的配置。我们直接可以翻译该类头部的英文介绍,具体的内容原谅我也不是很懂:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • Interceptor:拦截器,后面会提到

  • EventListener:指标事件的监听器。扩展此类以监视应用程序的HTTP调用的数量,大小和持续时间。所有start/connect/acquire事件最终都会收到匹配的end /release事件,要么成功(非null参数)要么失败(非null throwable)。每个事件对的第一个公共参数用于在并发或重复事件的情况下链接事件,例如dnsStart(call,domainName);和dnsEnd(call,domainName,inetAddressList); 我们可以看到一系列的xxxStart和xxxEnd方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • CookieJar:向传出的HTTP请求添加cookie,收到的HTTP返回数据的cookie处理。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

参考文章:okhttp3带cookie请求

  • Cache:网络缓存,okhttp默认只能设置缓存GET请求,不缓存POST请求,毕竟POST请求很多都是交互的,缓存下来也没有什么意义。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们看到Cache的构造函数,可以看到的是需要设置缓存文件夹,缓存的大小,还有一个是缓存内部的操作方式,因为缓存是需要写入文件的,默认操作使用的是Okio来操作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

参考文章:
教你如何使用okhttp缓存
OKHTTP之缓存配置详解

  • InternalCache:Okhttp内部缓存的接口,我们直接使用的时候不需要去实现这个接口,而是直接去使用上面的Cache类。

  • SocketFactory:从字面意思就看的出来,Android 自带的Socket的工厂类。
    参考文章: 类SocketFactory

  • SSLSocketFactory:Android自带的SSLSocket的工厂类。
    参考文章:Java SSLSocket的使用
    用SSLSocketFactory 连接https的地址

  • CertificateChainCleaner:不是很了解,所以还是老样子,通过谷歌翻译,翻译该类的顶部备注说明:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • HostnameVerifier:字面意思,Host name 验证,这个一个基础接口,而且只有一个方法:

/**

  • Verify that the host name is an acceptable match with

  • the server ‘s authentication scheme.

  • @param hostname the host name

  • @param session SSLSession used on the connection to host

  • @return true if the host name is acceptable
    */
    public boolean verify(String hostname, SSLSession session);

  • Dns:DNS(Domain Name System,域名系统),dns用于将域名解析解析为ip地址。
    参考文章:Android DNS更新与DNS-Prefetch

  • 还有其他等等…

2.2 Request相关

我们查看Request代码:

public final class Request {
final HttpUrl url; //网络请求路径
final String method; //get、post…
final Headers headers;//请求头
final @Nullable RequestBody body;//请求体
/**
你可以通过tags来同时取消多个请求。
当你构建一请求时,使用RequestBuilder.tag(tag)来分配一个标签。
之后你就可以用OkHttpClient.cancel(tag)来取消所有带有这个tag的call。.
*/
final Map<Class<?>, Object> tags;



}

这个估计很多人都清楚,如果对请求头请求体等不清楚的,可以看下以前我们这个系列的文章:Android技能树 — 网络小结(3)之HTTP/HTTPS

2.3 Call相关

我们可以看到我们生成的Request实例,会传给OkHttpClient实例的newÇall方法:

Request request = new Request.Builder().url(url).build();
Call call = okHttpClient.newCall(request);
call.execute();或者 call.enqueue(…);

我们Request和OkHttpClient大致都了解过了,我们来具体看下newCall执行了什么和Call的具体内容。

Call类代码:

@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的实例,同时把我们各种参数都配置好的OkHttpClient和Request都传入了。

所以后面call.execute()/call.enqueue()都是执行的RealCall的相对应的方法。但目前位置我们上面的图已经讲解好了,我这里再贴一次:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

恭喜你,下次别人考你Okhttp前面的相关参数配置方面的代码你已经都理解了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.请求分发Dispatcher

我们继续看我们的流程图下面的内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.1 Dispatcher 同步操作

我们先来讲同步执行:

@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException(“Already Executed”);
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
//‘1. 执行了dispatcher的executed方法’
client.dispatcher().executed(this);
//‘2. 调用了getResponseWithInterceptorChain方法’
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException(“Canceled”);
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
//‘3. 最后一定会执行dispatcher的finished方法’
client.dispatcher().finished(this);
}
}

我们一步步来具体看,第一步看Dispatcher类中的executed方法了:

/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}

可以看到把我们的RealCall加入到了一个同步线程runningSyncCalls中,然后中间调用了getResponseWithInterceptorChain方法*(这个第二个操作我们会放在后面很具体的讲解),我们既然加入到了一个同步线程中,肯定用完了要移除,然后第三步finished方法会做处理:

/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}

private void finished(Deque calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {

//‘if语句里面我们可以看到这里把我们的队列中移除了call对象’
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();
}
}

3.2 Dispatcher 异步操作

我们先来看RealCall里面的enqueue代码:

@Override public void enqueue(Callback responseCallback) {
//‘1. 这里有个同步锁的抛异常操作’
synchronized (this) {
if (executed) throw new IllegalStateException(“Already Executed”);
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
//‘2. 调用Dispatcher里面的enqueue方法’
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

我们一步步来看,第一个同步锁抛异常的操作,我们知道一个Call应对一个网络请求,加入你这么写是错误的:

Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}

@Override
public void onResponse(Call call, Response response) throws IOException {}
});
//‘同一个call对象再次发起请求’
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}

@Override
public void onResponse(Call call, Response response) throws IOException {}
});

同一个Call对象,同时请求了二次。这时候就会进入我们的同步锁判断,只要一个执行过了,里面 executed会为true,也就会抛出异常。

我们再来看第二步操作:

我们知道异步请求,肯定会代表很多请求都在各自的线程中去执行,那么我们在不看OkHttp源码前,让你去实现,你怎么实现,是不是第一个反应是使用线程池。

Java/Android线程池框架的结构主要包括3个部分

1.任务:包括被执行任务需要实现的接口类:Runnable 或 Callable

2.任务的执行器:包括任务执行机制的核心接口类Executor,以及继承自Executor的EexcutorService接口。

3.执行器的创建者,工厂类Executors

具体可以参考:Android 线程池框架、Executor、ThreadPoolExecutor详解

client.dispatcher().enqueue(new AsyncCall(responseCallback));,不再是像同步操作一样,直接把RealCall传入,而是传入一个AsyncCall对象。没错,按照我们上面提到的线程池架构,任务是使用Runnable 或 Callable接口,我们查看AsyncCall的代码:

final class AsyncCall extends NamedRunnable {


}

public abstract class NamedRunnable implements Runnable {


}

果然如我们预计,是使用了Runnable接口。

client.dispatcher().enqueue(new AsyncCall(responseCallback));,不再是像同步操作一样,直接把RealCall传入,而是传入一个AsyncCall对象。

调用Dispatcher里面的enqueue方法:

synchronized void enqueue(AsyncCall call) {
//‘1. 判断当前异步队列里面的数量是否小于最大值,当前请求数是否小于最大值’
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//‘2. 如果没有大于最大值,则将call加入到异步请求队列中’
runningAsyncCalls.add(call);
//‘3. 并且运行call的任务’
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}

我么直接看第三步,按照我们上面提到过的Java/Android线程池框架的结构主要包括3个部分,可以看到执行我们的Runnable对象的,说明他是一个任务执行器,也就是Executor的继承类。说明executorService()返回了一个Executor的实现类,我们点进去查看:

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;
}

果然创建一个可缓存线程池,线程池的最大长度无限制,但如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

那我们知道是线程池执行了Runnable的任务,那我们只要具体看我们的okhttp的Runnable到底执行了什么即可:

final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;

AsyncCall(Callback responseCallback) {
super(“OkHttp %s”, redactedUrl());
this.responseCallback = responseCallback;
}



@Override protected void execute() {
boolean signalledCallback = false;
try {

//‘1. 我们可以发现最后线程池执行的任务就是getResponseWithInterceptorChain方法’
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 {
//‘2. 最后再从Dispatcher里面的异步队列中移除’
client.dispatcher().finished(this);
}
}
}

我们发现不管是异步还是同步,都是一样的三部曲:1.加入到Dispatcher里面的同步(或异步)队列,2.执行getResponseWithInterceptorChain方法,3.从Dispatcher里面的同步(或异步)队列移除。(只不过同步操作是直接运行了getResponseWithInterceptorChain方法,而异步是通过线程池执行Runnable再去执行getResponseWithInterceptorChain方法)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

4 Okhttp拦截
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

结尾

我还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料分享给大家。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

image

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

结尾

我还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料分享给大家。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

[外链图片转存中…(img-EsSVliPF-1713264502897)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值