编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.LeakCanary设计思想
  • 02.Glide图片加载设计
  • 03.Gson序列化方案设计
  • 04.ARouter路由实践设计
  • 05.EventBus事件设计
  • 07.OkHttp网络请求设计
  • 08.gRPC框架设计思想

07.OkHttp网络请求设计

目录介绍

  • 01.整体概述介绍
    • 1.1 项目设计思想
    • 1.2 一些基础概念
    • 1.3 设计目标
    • 1.4 最简单的使用
    • 1.5 简单版网络库
    • 1.6 Get请求数据
    • 1.7 Post提交数据
    • 1.8 问题思考一下
  • 02.开发设计思路
    • 2.1 整体设计思路
    • 2.2 创建请求Client
    • 2.3 封装Request
    • 2.4 设计Call请求
    • 2.5 同步和异步设计
    • 2.6 Dispatcher设计
    • 2.7 拦截器的设计
    • 2.8 缓存拦截的设计
    • 2.9 连接请求设计
    • 2.10 返回response
  • 03.OkHttp原理思考
    • 3.2 创建Client操作
    • 3.3 request实践
    • 3.4 Call请求流程
    • 3.5 同步和异步原理
    • 3.6 拦截器实践
    • 3.7 缓存拦截实现
    • 3.8 连接请求实现
    • 3.9 response处理
  • 04.一些技术点思考
    • 4.1 如何解析接口url
    • 4.2 如何做域名解析
    • 4.3 Dispatcher调度
    • 4.4 如何设计高效流
    • 4.5 如何设计缓存
    • 4.6 如何设计连接请求
    • 4.8 如何设计线程池
  • 05.一些应用实践
    • 5.1 如何统计网络耗时
    • 5.2 如何上传图片&文件
    • 5.3
    • 5.4
  • 06.必要的基础知识
    • 6.1 URI和URL基础概念
    • 6.2 为何要用Https
    • 6.3

01.整体概述介绍

1.1 项目设计思想

  • 设计Okhttp必须要高性能
    • 并发和异步性能设计:OkHttp通过使用连接池、请求复用、异步执行等技术,提供了高性能的HTTP通信能力。它能够自动管理连接、复用连接以减少网络延迟,并支持异步执行请求以提高并发性能。
  • 支持HTTP/2和WebSocket
    • OkHttp支持HTTP/2协议和WebSocket通信,这些协议提供了更高效的数据传输和双向通信能力。它能够自动适应和升级到HTTP/2协议,以提供更好的性能和效率。
  • 异步和回调机制设计
    • OkHttp支持异步执行HTTP请求,并提供了回调机制来处理请求的结果。能够方便地进行网络请求和处理响应。
  • 如何考虑安全性
    • OkHttp提供了对HTTPS的支持,包括证书验证、TLS版本选择等。它能够自动处理证书验证和安全连接的建立,以确保通信的安全性和可靠性。

1.2 一些基础概念

1.4 最简单的使用

  • 最简单的同步和异步调用
    Response response = okHttpClient.newCall(request).execute();
    okHttpClient.newCall(request).enqueue(callback);
  • 有哪些比较重要的类
    • OKHttpClient类,这个是创建请求客户端对象
    • Request类和Response类,这个是发送请求和接收响应
    • Call类和RealCall类,这个是创建call请求操作,Call对象(一个准备好了的可以执行和取消的请求)。
    • Dispatcher类,这个是分发器。

1.5 简单版网络库

1.6 Get请求数据

  • Get同步请求数据
    String url = "www.baidu.com/index";
    OkHttpClient okHttpClient = new OkHttpClient();
    Request request = new Request.Builder().url(url).build();
    try {
        Response response = okHttpClient.newCall(request).execute();
        String body = response.body().string();
        Log.d("body---yc---",body);
    } catch (IOException e) {
        e.printStackTrace();
    }
  • Get异步请求数据
    okHttpClient.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NonNull Call call, @NonNull IOException e) {
    
        }
        @Override
        public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
            String body = response.body().string();
            Log.d("body---yc---",body);
        }
    });

1.7 Post提交数据

  • Post提交数据四种方式如下
    • 第一种:Map 通过 GSON 转为 JSON
    • 第二种:使用MultipartBody提交表单 form-data
    • 第三种:JSONObject
    • 第四种:使用FormBody提交表单数据,x-www-form-urlencoded
  • 第一种方式核心代码【Map 通过 GSON 转为 JSON】
    RequestBody requestBody = FormBody.create(mediaType, gson.toJson(map));
    Request.Builder builder = new Request.Builder().url(url);
    Request request = builder.post(requestBody).build();
    Call call = client.newCall(request);
    Response resp = call.execute();
  • 第二种方式核心代码【表单 form-data】
    MultipartBody.Builder bodyBuilder = new MultipartBody
        .Builder()
        .setType(MultipartBody.FORM);
    bodyBuilder.addFormDataPart("word", "西红柿炒鸡蛋");
    Request.Builder builder = new Request.Builder().url(url);
    builder.post(bodyBuilder.build());
    Call call = client.newCall(builder.build());
    Response resp = call.execute();
  • 第三种方式核心代码【JSONObject方式】
    JSONObject json = new JSONObject();
    json.put("word", "西红柿炒鸡蛋");
    json.put("page", "1");
    RequestBody bodyBuilder = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json.toString());
    Request.Builder builder = new Request.Builder().url(url);
    builder.post(bodyBuilder).build();
    Call call = client.newCall(builder.build());
    Response resp = call.execute();

1.8 问题思考一下

  • OkHttp简单介绍
    • 1.支持HTTP2/SPDY
    • 2.socket自动选择最好路线,并支持自动重连
    • 3.拥有自动维护的socket连接池,减少握手次数
    • 4.拥有队列线程池,轻松写并发
    • 5.拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩)基于Headers的缓存策略
  • OkHttp问题思考分析
    • OkHttp设计:OkHttp整体设计思路是什么样的?request和respond分别如何设计?如何设计call请求操作?
    • OkHttp同步异步:如何设计同步和异步请求?同步操作做了什么?异步操作如何处理逻辑?
    • OkHttp拦截器:拦截器是如何设计的?为什么需要拦截器?拦截器如何处理拦截,和向下分发逻辑?如何做重试机制的设计?
    • OkHttp缓存:如何设计缓存?什么情况下会用到网络缓存?缓存拦截器的核心思想是什么?
    • OkHttp分发器:同步和异步请求的Dispatcher是如何调度的?设计的巧妙之处是什么?
    • OkHttp线程池:使用了什么线程池?是如何管理线程任务?跟普通线程池使用有何区别?

02.开发设计思路

2.1 整体设计思路

  • 首先放一张完整流程图(看不懂没关系,慢慢往后看):
    • image
      image
  • 网络请求到响应大概流程图如下所示
    • image
      image
  • 整体设计思路大概如下所示
    • 第一步:创建OkHttpClient对象,由于创建这个对象十分复杂,因此采用builder设计模式构造
    • 第二步:包装Request请求体对象,主要是存放url,header,get请求,post请求等等属性
    • 第三步:通过newCall(request)去创建一个call请求
    • 第四步:开始执行同步execute或者enqueue请求,这里会使用到线程池
    • 第五步:添加各种拦截器,缓存拦截器,
    • 第六步:处理缓存拦截,数据复用的技术逻辑
    • 第七步:创建连接请求的操作,给服务端发送请求
    • 第八步:获取返回response数据,这里主要是处理code和body数据

2.2 创建请求Client

  • 为何要创建Client
    • 创建OkHttpClient对象,主要是用于Api网络请求的对象。类似于初始化网络请求,可以设置超时时间,日志打印拦截器,代理,ssl校验,域名校验等等。
  • 创建OkHttpClient如下所示
    okHttpClient = new OkHttpClient.Builder()
            .addInterceptor(new LoggerInterceptor())
            .connectTimeout(15, TimeUnit.SECONDS)
            .writeTimeout(20, TimeUnit.SECONDS)
            .readTimeout(20, TimeUnit.SECONDS)
            .build();

2.3 封装Request

  • Request抽象成请求数据
    • Request包括Headers和RequestBody,而RequestBody是abstract的,他的子类是有FormBody(表单提交的)和MultipartBody(文件上传),分别对应了两种不同的MIME类型。
    FormBody :"application/x-www-form-urlencoded"
    MultipartBody:"multipart/"+xxx.
  • 关于Headers说明,头部信息不是随便写的
    • OKHttp的封装类Request和Response为了应用程序编程方便,会把一些常用的Header信息专门提取出来,作为局部变量。
    • 比如contentType,contentLength,code,message,cacheControl,tag...它们其实都是以name-value对的形势,存储在网络请求的头部信息中。
  • Request类用于表示一个HTTP请求。它的设计主要包括以下几个方面
    • URL和HTTP方法:Request类包含了请求的URL和HTTP方法(GET、POST、PUT等)。
    • 请求头:Request类提供了方法来设置和获取请求头信息。可以通过addHeader()方法添加单个请求头,或者通过headers()方法获取请求头的构建器。
    • 请求体:对于POST、PUT等需要发送请求体的请求,Request类提供了设置和获取请求体的方法。可以通过RequestBody对象来表示请求体的内容。
    • 缓存控制:Request类支持设置缓存控制相关的信息,例如设置Cache-Control头部字段,以控制缓存的行为。
    • 用户认证:Request类支持设置基本的HTTP身份验证,可以通过设置Authorization头部字段来进行用户认证。

2.4 设计Call请求

  • 如何设计Call请求
    • 基于接口开发,设计了Call接口,里面主要做同步请求execute,异步请求enqueue,取消请求cancel等等。
  • Call类详解
    • 有道词典翻译该类注释:调用是准备执行的请求。call可以取消。由于此对象表示单个请求/响应对(流),因此不能执行两次。
  • 主要是HTTP请求任务封装
    • 可以说我们能用到的操纵基本上都定义在这个接口里面了,可以通过Call对象来操作请求,同步请求execute,异步请求enqueue。
    • 而Call接口内部提供了Factory工厂方法模式(将对象的创建延迟到工厂类的子类去进行,从而实现动态配置)。
  • 来看一下Call操作的代码流程
    final Call call = okHttpClient.newCall(request);
  • Call核心请求操作放到了RealCall类中,它是Call接口的实现类,提供一个可靠、高效的HTTP请求执行机制。
    • 1.提供核心异步/同步执行请求:RealCall使用线程池来执行请求,使得请求可以在后台线程中进行,而不会阻塞主线程。
    • 2.拦截器链:RealCall使用拦截器链来处理请求和响应。通过拦截器链,可以实现请求的修改、日志记录、缓存控制、身份验证等功能。

2.5 同步和异步设计

  • 先思考一个问题
    • 为什么要设计同步和异步?如果让你来设计,你该如何设计同步和异步操作,如何将异步请求按照顺序执行?
  • 同步和异步设计Api设计
    okHttpClient.newCall(request).execute();
    okHttpClient.newCall(request).enqueue(callback);
  • 同步和异步处理任务流程图?
    • image
      image
  • 网络请求肯定涉及多线程,如何处理大量任务
    • 采用Dispatcher作为调度,与线程池配合实现了高并发,低阻塞的的运行。针对请求任务,采用Deque作为集合,按照入队的顺序先进先出。

2.6 Dispatcher设计

  • 先思考一个问题
    • 假设有100个网络接口请求,那么你是如何控制并发数量,如何设计取消某个请求操作,如何设置请求优先级?
  • OkHttp设计Dispatcher的主要目的是为了管理和调度多个并发的HTTP请求。
    • 并发请求管理:Dispatcher负责管理并发的HTTP请求。它使用线程池来控制并发请求的数量,可以限制同时执行的请求数量。
    • 请求队列:Dispatcher维护一个请求队列,用于存储等待执行的请求。
    • 异步执行:Dispatcher支持异步执行HTTP请求。它使用线程池来执行请求,使得请求可以在后台线程中进行,而不会阻塞主线程。
    • 请求取消和中断:Dispatcher允许取消和中断正在执行的请求。
    • 优先级调度:Dispatcher支持为请求设置优先级。通过设置请求的优先级,可以控制请求的执行顺序,优先处理高优先级的请求。

2.7 拦截器的设计

  • OKHttp有两种调用方式,一种是阻塞的同步请求,一种是异步的非阻塞的请求。
    • 但是无论同步还是异步都会调用下RealCall的getResponseWithInterceptorChain方法来完成请求,同时将返回数据或者状态通过Callback来完成。
  • 设计拦截器的核心原理:Interceptor 负责拦截和分发。
    • 先来看看含义:观察,修改以及可能短路的请求输出和响应请求的回来。通常情况下拦截器用来添加,移除或者转换请求或者回应的头部信息。
    • 拦截器,就像水管一样,把一节一节的水管(拦截器)连起来,形成一个回路。实际上client到server也是如此,通过一个又一个的interceptor串起来,然后把数据发送到服务器,又能接受返回的数据,每一个拦截器(水管)都有自己的作用,分别处理不同东西,比如消毒,净化,去杂质,就像一层层过滤网一样。
  • 接口是如何设计的
    public interface Interceptor {
       //负责拦截
      Response intercept(Chain chain) throws IOException;
      interface Chain {
         //负责分发、前行
        Response proceed(Request request) throws IOException;
      }
    }
  • 那么如何分发操作。这块可以看:RealInterceptorChain
    • 大概思路:RealInterceptorChain持有一个List的Interceptor,通过对这个List的Interceptor进行迭代和递归推进调用
    • 每一个RealInterceptorChain对应一个interceptor,然后每一个interceptor再产生下一个RealInterceptorChain,直到List迭代完成。

2.8 缓存拦截的设计

2.9 连接请求设计

  • ConnectInterceptor的设计
    • 连接拦截器,这才是真行的开始向服务器发起器连接。

2.10 返回response

  • Response抽象成响应数据
    • Response包括Headers和RequestBody,而ResponseBody是abstract的,所以他的子类也是有两个:RealResponseBody和CacheResponseBody,分别代表真实响应和缓存响应。

03.OkHttp原理思考

3.2 创建Client操作

  • OKHttpClient
    • 1、里面包含了很多对象,其实OKHttp的很多功能模块都包装进这个类,让这个类单独提供对外的API,这种外观模式的设计十分的优雅。外观模式。
    • 2、而内部模块比较多,就使用了Builder模式(建造器模式)。Builder模式(建造器模式)
    • 3、它的方法只有一个:newCall.返回一个Call对象(一个准备好了的可以执行和取消的请求)。

3.3 request实践

  • 主要是封装一个Request请求体。
    Request request = new Request.Builder()
            .url(url)
            .addHeader("cookie","yangchong")
            .get()
            .build();

3.4 Call请求流程

  • 相当于调用newCall去创建一个call请求,这里分为同步和异步操作
    okHttpClient.newCall(request).execute();
    okHttpClient.newCall(request).enqueue(callback);
  • RealCall类构造创建对象,它是Call接口的具体实现类。
    • 在源码中,OKHttpClient实现了Call.Factory接口,返回了一个RealCall对象。那我们就来看下RealCall这个类。
    • 1、OkHttpClient的newCall方法里面new了RealCall的对象,但是RealCall的构造函数需要传入一个OKHttpClient对象和Request对象(PS:第三个参数false表示不是webSokcet)。因此RealCall包装了Request对象。所以RealCall可以很方便地使用这两个对象。
    • 2、RealCall里面的两个关键方法是:execute 和 enqueue。分别用于同步和异步得执行网络请求。
    • 3、RealCall还有一个重要方法是:getResponseWithInterceptorChain,添加拦截器,通过拦截器可以将一个流式工作分解为可配置的分段流程,既增加了灵活性也实现了解耦,关键还可以自有配置,非常完美。

3.5 同步和异步原理

  • execute同步调用流程

    RealCall#execute(),这个主要是执行同步操作的核心方法。首先是判断call是否执行过,可以看出每个Call对象只能使用一次原则。 Dispatcher#executed(),将任务添加到Deque队列中 Dispatcher#executed(),里面发现是runningSyncCalls执行了add方法,可以发现runningSyncCalls是ArrayDeque对象。 Dispatcher#runningSyncCalls,它是双向队列,runningSyncCalls是一个存放同步请求的双向队列。 RealCall#getResponseWithInterceptorChain(),这个是添加各种拦截器,然后创建RealInterceptorChain对象执行请求操作 getResponseWithInterceptorChain()#interceptors.add,不断地add各种拦截器。有重定向,缓存,连接,自定义拦截器等等。 RealInterceptorChain#proceed,在这个里面获取拦截器,看到在proceed方面里面又new了一个RealInterceptorChain类的next对象 这个next对象和chain最大的区别就是index属性值不同chain是0.而next是1,然后取interceptors下标为1的对象的interceptor。 由从上文可知,如果没有开发者自定义的Interceptor时,首先调用的RetryAndFollowUpInterceptor,如果有开发者自己定义的interceptor则调用开发者interceptor。 interceptor#intercept(),最后通过拦截器请求操作,获取response响应数据

  • enqueue异步调用流程

    RealCall#enqueue(callback),由于executed默认为false,所以先进行判断是否为true,为true则直接跑异常,没有则设置为true,可以看出executed这个是一个标志,标志这个请求是否已经正在请求中 Dispatcher#enqueue(),是Dispatcher对象所以实际调用的是Dispatcher的enqueue() Dispatcher#readyAsyncCalls,readyAsyncCalls是一个存放异步请求的双向队列。 Dispatcher#promoteAndExecute(),将符合条件的调用从{readyAsyncCalls}提升到{runningAsyncCalls},并在executor服务上运行它们。 Dispatcher#promoteAndExecute()#asyncCall.executeOn,遍历去执行异步任务 RealCall#AsyncCall#executeOn(),创建一个executorService去执行runnable任务。在子线程中做任务 NamedRunnable#run(),然后去执行execute()方法,该方法是抽象的。具体看子类对该方法的实现。 RealCall#AsyncCall#execute,又调用了getResponseWithInterceptorChain(),这块跟同步调用逻辑一样。然后通过responseCallback处理结果。

3.6 拦截器实践

  • interceptor接口详解
    • Interceptor是一个接口,主要是对请求和相应的过滤处理,其中有一个抽象方法即Response intercept(Chain chain) throws IOException负责具体的过滤。
    • 在他的子类里面又调用了Chain,从而实现拦截器调用链(chain),所以真正实现拦截作用的是其内部接口Chain。
  • Interceptor.Chain的实现类都是RealInterceptorChain,也就说说处理调用过程的实现是RealInterceptorChain。
    • 所以RealInterceptorChain持有一个List的Interceptor,通过对这个List的Interceptor进行迭代和递归推进。让我们看看源码实现。
  • RealInterceptorChain#proceed()很核心的操作步骤理解如下
    • 第一步,先判断是否超过list的size,如果超过则遍历结束,如果没有超过则继续执行
    • 第二步calls+1
    • 第三步new了一个RealInterceptorChain,其中然后下标index+1
    • 第四步 从list取出下一个interceptor对象
    • 第五步 执行interceptor的intercept方法
  • RealInterceptorChain#proceed()核心思想设计
    • 每一个RealInterceptorChain对应一个interceptor,然后每一个interceptor再产生下一个RealInterceptorChain,直到List迭代完成。
  • 拦截器中的设计模式
    • 既一个网络请求,按一定的顺序,经由多个拦截器进行处理,该拦截器可以决定自己处理并且返回我的结果,也可以选择向下继续传递,让后面的拦截器处理返回它的结果。这个设计模式叫做责任链模式。

3.7 缓存拦截实现

3.8 连接请求实现

3.9 response处理

  • Response类说明
    • Response抽象成响应数据
    • Response包括Headers和RequestBody,而ResponseBody是abstract的,所以他的子类也是有两个:RealResponseBody和CacheResponseBody,分别代表真实响应和缓存响应。

04.一些技术点思考

4.1 如何解析接口url

  • 如果让你设计网络请求框架,你是如何保证请求的接口是可靠合规的?
    • 可以设计一个处理url验证和规范化的类,提供解析URL是一种可靠和推荐的方式。
  • 可以使用HttpUrl类来解析和操作URL。
    • 要解析URL,可以使用HttpUrl的静态方法parse(),将URL字符串作为参数传入,返回一个HttpUrl对象。例如:
    • 通过HttpUrl对象,可以获取URL的各个部分,例如协议、主机、路径、查询参数等。
    String urlString = "https://www.example.com/path?param1=value1&param2=value2";
    HttpUrl httpUrl = HttpUrl.parse(urlString);

4.2 如何做域名解析

4.3 Dispatcher调度

  • 线程池executeService的设计
    • 创建一个自定义ThreadPoolExecutor线程池对象。需要注意两点:
    • 第一点:在OKHttp中,创建了一个阀值是Integer.MAX_VALUE的线程池;第二点:SynchronousQueue每个插入操作必须等待另一个线程的移除操作。
  • 发起网络请求
    • 整个框架主要通过Call来封装每一次的请求。同时Call持有OkHttpClient和一份Request。而每一次的同步或者异步请求都会有Dispatcher的参与。
    • 同步:Dispatcher在执行同步的Call,接加入到runningSyncCall队列中,实际上并没有执行该Call,而是交给外部执行。
    • 异步:将Call加入readyAsyncCall排队等待,将符合条件的调用从{readyAsyncCalls}提升到{runningAsyncCalls},并在executor服务上运行它们。
    • 异步操作中符合条件是指,如果当前正在执行的call的数量大于maxRequest(64),或者该call的Host上的call超过maxRequestsPerHos(5),则加入readyAsyncCall排队等待,否则加入runningAsyncCalls并执行。
  • 结束网络请求
    • 具体看 RealCall#AsyncCall#executeOn()和execute():从ready到running,在每个call结束的时候都会调用finished【这个是在try-finally中执行的】。
  • OKHttp同步调度流程分析
    • 第一步:是调用了RealCall的execute()方法里面调用executed(this);
    • 第二步:在Dispatcher里面的executed执行入队操作
    • 第三步:执行getResponseWithInterceptorChain();进入拦截器链流程,然后进行请求,获取Response,并返回Response result 。
    • 第四步:执行client.dispatcher().finished(this)操作
  • OKHttp异步调度流程分析
    • 第一步:是调用了RealCall的enqueue()方法
    • 第二步:在Dispatcher里面的enqueue执行入队操作。这个地方有条件
      • runningAsyncCalls.size() < maxRequests 表示当前正在运行的AsyncCall是否小于maxRequests = 64
      • runningCallsForHost(call) < maxRequestsPerHos 表示同一个地址访问的AsyncCall是否小于maxRequestsPerHost = 5;即 当前正在并发的请求不能超过64且同一个地址的访问不能超过5个
    • 第A步:第一种情况A
      • 第三步:可以直接入队。把任务放到runningAsyncCalls中
      • 第四步:线程池executorService执行execute()方法
      • 第五步:执行AsyncCall的execute()方法
      • 第六步:执行getResponseWithInterceptorChain();进入拦截器链流程,然后进行请求,获取Response。
      • 第七步:执行client.dispatcher().finished(this)操作 进行出队操作
    • 第B步:第二种情况B
      • 第三步:不能直接入队,需要等待。把任务放到readyAsyncCalls中,能进入等待则说明当前要么有64条正在进行的并发,要么同一个地址有5个请求,所以要等待。
      • 第四步:先判断是否满足 初步入队条件。如果此时 并发的数量还是大于maxRequests=64则return并继续等待;如果此时,没有等待的任务,则直接return并继续等待。
      • 第五步:在Dispatcher#promoteAndExecute()中,此时满足条件,则从等待队列面移除这个call【i.remove()】,然后添加到正在运行的队列中【runningAsyncCalls.add(asyncCall)】。
      • 第六步:线程池executorService执行execute()方法
      • 第七步:执行AsyncCall的execute()方法
      • 第八步:执行getResponseWithInterceptorChain();进入拦截器链流程,然后进行请求,获取Response。
      • 第九步:如果是正常的获取到Response,则执行responseCallback.onResponse()
      • 第十步:执行client.dispatcher().finished(this)操作 进行出队操作

05.一些应用实践

5.1 如何统计网络耗时

  • OkHttp如何进行各个请求环节的耗时统计呢?
    • OkHttp 版本提供了EventListener接口,可以让调用者接收一系列网络请求过程中的事件,例如DNS解析、TSL/SSL连接、Response接收等。
    • 通过继承此接口,调用者可以监视整个应用中网络请求次数、流量大小、耗时(比如dns解析时间,请求时间,响应时间等等)情况。
  • 如何消耗记录时间
    • 在OkHttp库中有一个EventListener类。该类是网络事件的侦听器。扩展这个类以监视应用程序的HTTP调用的数量、大小和持续时间。
    • 所有启动/连接/获取事件最终将接收到匹配的结束/释放事件,要么成功(非空参数),要么失败(非空可抛出)。
    • 比如,可以在开始链接记录时间;dns开始,结束等方法解析记录时间,可以计算dns的解析时间。
    • 比如,可以在开始请求记录时间,记录connectStart,connectEnd等方法时间,则可以计算出connect连接时间。
  • 关于网络耗时请求的内容
    • Okhttp统计耗时技术博客:OkHttp请求耗时统计
    • Okhttp统计耗时技术库:MonitorNetLib

6.2 为何要用Https

  • 在没有HTTPS之前使用HTTP的协议的,可见HTTPS肯定是弥补的HTTP的安全缺陷,那么咱们来看下HTTP协议的那些安全缺点:
    • 1、通信使用明文(不加密),内容可能被窃听(抓包工具可以获取请求和响应内容),如下图:
    • image
      image
    • 2、不验证通讯方的身分,任何人都坑你发送请求,不管对方是谁都返回相应,如下图:
    • image
      image
    • 3、无法证明报文的完整性,可能会遭到篡改,即没有办法确认发出的请求/相应前后一致。
    • image
      image
    • 所以为了解决上述问题,需要在HTTP上加入加密处理和认证机制,我们把添加了加密和认证机制的http称之为http。
  • HTTP+加密+认证+完成性保护=HTTPS。简单的说下HTTPS说下他的优势
    • 1、内容加密,建立一个信息的安全通道,来保证数据传输过程的安全性。
    • 2、身份认证,确认网站的真是性。
    • 3、数据完整性,防止内容被第三方冒充或者篡改。
  • 补充一下:HTTPS并非应用层的一种新协议
    • 只是HTTP通讯接口部分用SSL(secure socket layer)和TLS(transport layer security)协议代替。
    • 通常HTTP直接和TCP通信时,当使用SSL时,则演变成先和SSL通信,再由SSL和TCP通讯,因此所以HTTPS其实就是身披SSL保护外衣的HTTP。
贡献者: yangchong211
上一篇
05.EventBus事件设计
下一篇
08.gRPC框架设计思想