OKHttp3 从使用到原理分析

简介: Okhttp3 是我们经常使用的一个网络框架,可扩展性强,支持 get 缓存, spdy、http2.0,gzip 压缩减少数据流量,同步和异步请求,连接池复用机制等特性让广大 android 开发者深爱不已,今天我就带大家从 Okhttp 简单使用,到各种好用拦截器原理了解 Okhttp3

Okhttp3 是我们经常使用的一个网络框架,可扩展性强,支持 get 缓存, spdy、http2.0,gzip 压缩减少数据流量,同步和异步请求,连接池复用机制等特性让广大 android 开发者深爱不已,今天我就带大家从 Okhttp 简单使用,到各种好用拦截器原理了解 Okhttp3

一. OkHttp3使用指南

1.1 引入

implementation  'com.squareup.okhttp3:okhttp:4.9.0'
implementation  'com.squareup.okio:okio:2.9.0'

清单文件注册一下权限:

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

1.2 OkHttp3 使用方式

  • 构建客户端对象OkHttpClient
  • 构建请求Request
  • 生成Call对象
  • Call发起请求(同步/异步)

1.2.1 OkHttp3 post 请求

1.2.1.1 post 请求提交String文件

getpost 请求 的区别是 在构造Request对象时,需要多构造一个RequestBody对象,用它来携带我们要提交的数据,其他都是一样的。

 OkHttpClient httpClient = new OkHttpClient();
        MediaType contentType = MediaType.Companion.parse("text/x-markdown; charset=utf-8");
        String content = "hello!";
        RequestBody body = RequestBody.Companion.create(content,contentType);
        Request getRequest = new Request.Builder()
                .url("https://api.github.com/markdown/raw")
                .post(body)
                .build();
        Call call = httpClient.newCall(getRequest);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
            }
            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                Log.i("MainActivity", "okHttpPost enqueue: \n onResponse:"+ response.toString() +"\n body:" + Objects.requireNonNull(response.body()).string());
            }
        });
    }

构建Request时的get()改成post(body),并传入RequestBody实例。RequestBody实例是通过create方法创建,需要指定请求体内容类型、请求体内容。这里是传入了一个指定为markdown格式的文本

1.2.1.2 post 请求提交复杂请求体

复杂请求体可以同时包含多种类型的的请求体数据。

  OkHttpClient httpClient = new OkHttpClient();
//        MediaType contentType = MediaType.parse("text/x-markdown; charset=utf-8");
//        String content = "hello!";
//        RequestBody body = RequestBody.create(contentType, content);
        //RequestBody:fileBody,上传文件
        File file = drawableToFile(this, R.mipmap.bigpic, new File("00.jpg"));
        RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpg"), file);
        //RequestBody:multipartBody, 多类型 (用户名、密码、头像)
        MultipartBody multipartBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("username", "hufeiyang")
                .addFormDataPart("phone", "123456")
                .addFormDataPart("touxiang", "00.png", fileBody)
                .build();
        Request getRequest = new Request.Builder()
                .url("http://yun918.cn/study/public/file_upload.php")
                .post(multipartBody)
                .build();
        Call call = httpClient.newCall(getRequest);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "okHttpPost enqueue: \n onFailure:"+ call.request().toString() +"\n body:" +call.request().body().contentType()
                +"\n IOException:"+e.getMessage());
            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "okHttpPost enqueue: \n onResponse:"+ response.toString() +"\n body:" +response.body().string());
            }
        });

1.2.2 OkHttp3 get 请求

同步请求
// 创建 OkHttpClient 实例 
        OkHttpClient httpClient = new OkHttpClient();
// 将 MicroKibaco github 实例 url 传入Request.Builder 实例
        String url = "https://github.com/MicroKibaco";
        Request getRequest = new Request.Builder()
                .url(url)
                .get()
                .build();
// httpClient.newCall 传入 Request实例生成call
        Call call = httpClient.newCall(getRequest);
             //同步请求,要放到子线程执行
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Response response = call.execute();
                    Log.i(TAG, "okHttpGet run: response:"+ response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
异步请求
        OkHttpClient httpClient = new OkHttpClient();
      String url = "https://www.baidu.com/";
        Request getRequest = new Request.Builder()
                .url(url)
                .get()
                .build();
        Call call = httpClient.newCall(getRequest);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
            }
            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                Log.i("MainActivity", "okHttpGet run: response:"+ Objects.requireNonNull(response.body()).string());
            }
        });
    }
取消请求

每一个Call只能执行一次。 如果想要取消正在执行的请求,可以使用call.cancel(),通常在离开页面时都要取消执行的请求的。

结果处理
  • onFailure
    connection连接失败或读写超时
  • onResponse
    成功的从服务器获取到了结果,但是这个结果的response.code()可能是404、500等,也可能就是200
  • response.code() 200
    表示应用层请求成功了。此时我们可以获取Response的ResponseBody,这是响应体。从面看到,可以从ResponseBody获取string、byte[]、InputStream,这样就可以对结果进行很多操作了,比如UI上展示string(要用Handler切换到UI线程)、通过InputStream写入文件等等

1.3 OkHttp3 请求配置项

  • 如何全局设置超时时长?
  • 缓存位置、最大缓存大小 呢?
  • 考虑有这样一个需求,我要监控App通过 OkHttp 发出的 所有 原始请求,以及整个请求所耗费的时间,如何做?
 OkHttpClient client = new OkHttpClient.Builder()
 // 设置了连接、读取、写入的超时时长
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
// cache()方法传入了由缓存目录、缓存大小构成的Cache实例
.cache(new Cache(getExternalCacheDir(),500 * 1024 * 1024))
.addInterceptor(new Interceptor() {
// intercept()方法会在开始执行请求时调用。其中chain.proceed(request)内部是真正请求的过程,是阻塞操作,执行完后会就会得到请求结果ResponseBody
                    @Override
                    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
       String url = request.url().toString();
       Log.i(TAG, "intercept: proceed start: url"+ url+ ", at "+System.currentTimeMillis());
// chain.proceed(request)的前后取当前时间,那么就知道整个请求所耗费的时间。上面chain.proceed(request)的前后分别打印的日志和时间
      Response response = chain.proceed(request);
      ResponseBody body = response.body();
      Log.i(TAG, "intercept: proceed end: url"+ url+ ", at "+System.currentTimeMillis());
                        return response;
                    }
                })
.build();

单独配置

 Request getRequest = new Request.Builder()
                .url("http://yun918.cn/study/public/file_upload.php")
                .post(multipartBody)
                .addHeader("key","value")
                .cacheControl(CacheControl.FORCE_NETWORK)
                .build();

二. OkHttp3 重点 API 介绍

2.1 RealCall

RealCall作用:

RealCall是 OKHttp 真正的请求执行者,有两个比较重要的方法: execute 同步方法 和 enqueue 异步 方法,我们来看看同步方法是怎么做的吧

1681563370867.png

首先检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完全一样的 call ,可以利用 clone 方法进行克隆;

然后利用 client.dispatcher().executed(this) 来进行实际执行

1681563405656.png

首先,它会检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完全一样的 call ,可以利用 clone 方法进行克隆

1681563450758.png

再调用 getResponseWithInterceptorChain() 函数获取 HTTP 返回结果,从函数名可以看出,这一步还会进行一系列“拦截”操作。

1681563481981.png

最后将涉及到 dispatcher 的内容只不过是告知它我们的执行状态加入到running队列,

  • 开始执行了(调用 executed )
  • 执行完毕了(调用 finished )

我们在来看看异步方法是怎么做的吧

1681563516736.png

在子线程中,asyncCall是线程,UI需要切换线程

1681563566415.png

1681563593551.png

真正任务执行是在调用我们的 dispatcher.equeue

1681563632220.png

如果中的 runningAsynCalls 不满,且 call 占用的 host 小于最大数量,则将 call 加入到 runningAsyncCalls 队列中执行,同时利用线程池执行 call ;

否则将 call 加入到 readyAsyncCalls 中,这也符合生产者消费者模式双端队列模型设计

2.2 OKHttpClient

1681563703081.png

1681563730669.png

OKHttpClient是OKHttp的 流程的总控制者 ,通过Builder模式配置我们的参数如: http协议, http版本,缓存socketfactory针对的是http还是 socket ;端口80还是443;dns等等

三. OkHttp3 工作流程

3.1 总的大纲:

1681563783979.png

1681563827924.png

用了 责任链设计模式 ,它将请求一层一层向下传,直到有一层能够得到Resposne就停止向下传递,并 Response 向上面的拦截器传递,然后各个拦截器会对 respone 进行一些处理,最后会传到 RealCall 类中通过 execute 来得到 Response 。

1681563890109.png

  • 3.1.1 通过 OkHttpClient 实例化 初始化我们需要初始化的 http连接协议 代理地址 缓存控制 Cookie 证书连接 代理选择器等
  • 3.1.2 通过 Request Builder模式 拿到我们传入的 url method Headers RequestBody 以及 tags
  • 在 OkHttpClient 我们用RealCall接口回调执行Dispacher的 回调实现,入: equeqe(同步) ennque(异步请求) 其中 Dispacher 的同步请求 是 将我们的请求塞到同步队列里面,这个有可能会导致线程阻塞 ennque(异步请求) 是 生产者消费者模型 会将满足 小于并发64 同host请求小于4 的请求塞到我们的 运行队列里面 执行,不满足该条件放到 等待队列里面

然后交给线程池 去执行具体请求事宜 ,拿到响应后

  • 3.1.3 我们 会将 响应 通过 getResourceChainPain 通过拦截器网下透传到 重定向拦截器, 桥拦截器 ,缓存拦截器 ,连接池拦截器,网络拦截器,请求拦截器 如果中间有被拦截,就进行一系列的拦截处理,追钟会返回我们客户端,我们客户端再通过 Response 去解析 code message Header ReponseBody 这些参数信息

四. OkHttp3 拦截器

拦截器按照职责主要分为RetryAndFollowUpInterceptor,BridgeInterceptor,CacheInterceptor,ConnectInterceptor,NetworkInterceptors这几类。 当我们执行同步或异步请求的时候,所有的拦截器会被添加到RealInterceptorChain这个类里面

1681563946277.png

当我们实例化实例化下一个拦截器对应的 RealIterceptorChain 对象,这个对象会在传递给当前的拦截器

1681563979571.png

然后得到当前的拦截器: interceptors 是存放拦截器的 ArrayList

1681564009020.png

调用当前拦截器的 intercept() 方法,并将下一个拦截器的 RealIterceptorChain 对象传递下去

1681564055554.png

除了在client中自己设置的 interceptor ,第一个调用的就是 retryAndFollowUpInterceptor

1681564095685.png

4.1 RetryAndFollowUpInterceptor

1681564129766.png

1681564167072.png

RetryAndFollowUpInterceptor 主要的职责是: 负责失败重试以及重定向,一次okhttp请求,是一个个拦截器执行interrept获取到response,对response处理再传给上一个Interrupt,重试最大次数20,超过的话释放资源

4.2 CacheInterceptor

4.2.1 Http缓存

http缓存分为两种:一种是强制缓存,另一种是对比缓存,字段表示为: Cache-Control

1681564204006.png

对比缓存标识字段是

1681564241991.png

1681564266389.png

实际开发中服务端可以控制缓存,然后交给客户端控制缓存时间

  • 第一请求:

1681564301157.png

  • 第二次请求:
  • 1681564332696.png

4.2.2 okHttp 缓存

1681564400913.png

首先根据 request 来判断 cache 中是否有缓存的 response

1681564429424.png

if 有,得到这个 response

1681564463724.png

然后进行判断当前 response 是否有效

1681564493914.png

if 没有将 cacheCandate 赋值为空。

1681564532941.png

根据request判断缓存的策略,是否要使用了网络,缓存 或两者 都使用

1681564568061.png

调用下一个拦截器,决定从网络上来得到 response

1681564598114.png

如果本地已经存在 cacheResponse ,那么让它和网络得到的 networkResponse 做比较,决定是否来更新缓存的 cacheResponse

1681564629800.png

缓存未经缓存过的 response,我们这么处理

1681564659372.png

1681564687858.png

4.3 ConnectInterceptor

了解ConnectInterceptor前,我们得了解一下什么是连接复用

1681564717185.png

当我们打开一个新的连接,写入http请求数据的时候,读取响应数据,释放连接这个过程中,我们再释放连接我们可以复用connection写入http请求数据,这就是复用原理

接下来我们看看 ConnectInterceptor 的 原理吧,这里给大家总结了一份原理图

1681564752148.png


1681564795738.png

  • 4.3.1 判断 streamAllocation 对象内部是否有可复用的 connection 对象
  • 1681564834978.png4.3.2 如果 streamAllocation 对象无可用的 connection 对象,则通过 Internal.instance 从连接池中查找可用的 connection 对象;
  • 1681564867687.png
  • 4.3.3 如果连接池中仍未找到,则遍历所有路由路径,尝试再次从 ConnectionPool 中寻找可复用的连接;

1681564903142.png

4.3.4 前面三步都未找到,则新建一个连接,进行 TCP+ TLS 握手

1681564932204.png

1681564956855.png

1681565030966.png

  • 4.3.5 将新建的连接放入连接池中,并返回结果
  • 1681565064982.png1681565090701.png

4.4 CallServerInterceptor

正地去进行网络IO读写了——写入http请求的header和body数据、读取响应的header和body。

  • 检查请求方法,用 Httpcode 处理 request
  • 进行网络请求得到 response
  • 返回 response

4.5 自定义拦截器

  • 异常上报
  • 异地登陆,根据返回 code 处理
  • 添加 cookie

五. OkHttp的优点

  • 5.1 无缝支持GZIP来减少数据流量 ,发送的数据和接受的收据在传递过程中都是经过gzip压缩的,BridgeInterceptintercept方法中设置的
  • 5.2 通过连接池来减少请求延时 在okhttp中,我们每次的request请求都可以理解为一个connection,而每次发送请求的时候我们都要经过tcp的三次握手,然后传输数据,最后在释放连接。在高并发或者多个客户端请求的情况下,多次创建就会导致性能低下。如果能够connection复用的话,就能够很好地解决这个问题了。能够复用的关键就是客户端和服务端能够保持长连接,并让一个或者多个连接复用。
  • 5.3 在BridgeInterceptor的 intercept() 方法中requestBuilder.header("Connection", "Keep-Alive"),我们在request的请求头添加了("Connection", "Keep-Alive")的键值对,这样就能够保持长连接。而连接池ConnectionPoll就是专门负责管理这些长连接的类。需要注意的是,我们在初始化 ConnectionPoll的时候,会设置 闲置的connections 的最大数量为5个,每个最长的存活时间为5分钟。
  • 5.4 通过连接池来减少请求延时 在okhttp中,我们每次的request请求都可以理解为一个connection,而每次发送请求的时候我们都要经过tcp的三次握手,然后传输数据,最后在释放连接。在高并发或者多个客户端请求的情况下,多次创建就会导致性能低下。如果能够connection复用的话,就能够很好地解决这个问题了。能够复用的关键就是客户端和服务端能够保持长连接,并让一个或者多个连接复用。

六 . OKHttp 缓存策阅

  • GET 请求

1681565134834.png

1681565159908.png

  • Post 请求
  • 1681565199220.png
  • 1681565227954.png

    1681565255083.png


相关文章
|
Java
使用OkHttp3框架获取服务器数据
使用OkHttp3框架获取服务器数据
174 0
|
存储 缓存 网络协议
源码阅读 | Okhttp
源码阅读 | Okhttp
|
存储 Android开发
OkHttp源码详解之Okio源码详解
OkHttp源码详解之Okio源码详解
OkHttp源码详解之Okio源码详解
|
设计模式 Java API
【OkHttp】OkHttp 源码分析 ( 网络框架封装 | OkHttp 4 迁移 | OkHttp 建造者模式 )
【OkHttp】OkHttp 源码分析 ( 网络框架封装 | OkHttp 4 迁移 | OkHttp 建造者模式 )
349 0
【OkHttp】OkHttp 源码分析 ( 网络框架封装 | OkHttp 4 迁移 | OkHttp 建造者模式 )
|
Java 调度
【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )(二)
【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )(二)
190 0
【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )(二)
|
调度
【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )(一)
【OkHttp】OkHttp 源码分析 ( 同步 / 异步 Request 请求执行原理分析 )(一)
299 0
|
Java
OkHttp 3.7源码分析(二)——拦截器&一个实际网络请求的实现
前一篇博客中我们介绍了OkHttp的总体架构,接下来我们以一个具体的网络请求来讲述OkHttp进行网络访问的具体过程。由于该部分与OkHttp的拦截器概念紧密联系在一起,所以将这两部分放在一起进行讲解。
13414 0
|
缓存 监控 Java
OkHttp 3.7源码分析(四)——缓存策略
合理地利用本地缓存可以有效地减少网络开销,减少响应延迟。HTTP报头也定义了很多与缓存有关的域来控制缓存。今天就来讲讲OkHttp中关于缓存部分的实现细节。
6407 0
|
网络协议 Java
OkHttp 3.7源码分析(五)——连接池
接下来讲下OkHttp的连接池管理,这也是OkHttp的核心部分。通过维护连接池,最大限度重用现有连接,减少网络连接的创建开销,以此提升网络请求效率。
9643 0
|
Java 调度 安全
OkHttp3源码详解(六)Okhttp任务队列工作原理
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 1 概述 1.1 引言 android完成非阻塞式的异步请求的时候都是通过启动子线程的方式来解决,子线程执行完任务的之后通过handler的方式来和主线程来完成通信。