美文网首页框架【库】面试题网络
手把手讲解 OkHttp硬核知识点

手把手讲解 OkHttp硬核知识点

作者: 波澜步惊 | 来源:发表于2019-08-13 17:40 被阅读34次

    前言

    手把手讲解系列文章,是我写给各位看官,也是写给我自己的。
    文章可能过分详细,但是这是为了帮助到尽量多的人,毕竟工作5,6年,不能老吸血,也到了回馈开源的时候.
    这个系列的文章:
    1、用通俗易懂的讲解方式,讲解一门技术的实用价值
    2、详细书写源码的追踪,源码截图,绘制类的结构图,尽量详细地解释原理的探索过程
    3、提供Github 的 可运行的Demo工程,但是我所提供代码,更多是提供思路,抛砖引玉,请酌情cv
    4、集合整理原理探索过程中的一些坑,或者demo的运行过程中的注意事项
    5、用gif图,最直观地展示demo运行效果

    如果觉得细节太细,直接跳过看结论即可。
    本人能力有限,如若发现描述不当之处,欢迎留言批评指正。

    学到老活到老,路漫漫其修远兮。与众君共勉 !


    引子

    OkHttp 知名第三方网络框架SDK,使用简单,性能优秀,但是内核并不简单,此系列文章,专挑硬核知识点详细讲解. 何为硬核,就是要想深入研究,你绝对绕不过去的知识点

    正文大纲

    OkHttp是什么
    OkHttp怎么用
    OkHttp源码核心类之一:分发器详解
    OkHttp源码核心类之一:拦截器简述

    正文

    OkHttp是什么

    OkHttp是时下非常流行的网络编程框架,由行业巨佬Square公司开源,很多其他的流行框架比如retrofit的底层也是okhttp,只不过使用了注解反射动态代理将其进行了封装。
    流行版本为:3.10.0,最新版本为:4.0.1,只不过将实现语言从java改成了kotlin。

    相对于其他网络框架,有如下优点:

    • 支持SpdyHttp1.XHttp2Quic以及WebSocket
    • 连接池复用底层TCP(Socket),减少请求延时
    • 无缝的支持GZIP减少数据流量
    • 缓存响应数据减少重复的网络请求
    • 请求失败自动重试主机的其他ip,自动重定向

    OkHttp怎么用

    添加gradle依赖

    dependencies {
       ....
        implementation("com.squareup.okhttp3:okhttp:4.0.1")
    }
    

    Java调用(同步请求,异步请求)

    public class MyRequest {
        /**
         * 异步请求
         */
        public void sendReqAsync() {
            OkHttpClient client = new OkHttpClient.Builder().build();
            Request request = new Request.Builder().url("http://www.baidu.com").build();
            Call call = client.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(@NotNull Call call, @NotNull IOException e) {
                    Log.d("sendReqTag", "onFailure:" + e.getLocalizedMessage());
                }
    
                @Override
                public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                    String s = new String().concat(response.code() + "\n")
                            .concat(response.message() + "\n")
                            .concat(response.body().string());
                    Log.d("sendReqTag", "onSuccess\n " + s);
                }
            });
        }
    
        /**
         * 同步请求
         */
        public void sendReqSync() {
            OkHttpClient client = new OkHttpClient.Builder().build();
            Request request = new Request.Builder().url("http://www.baidu.com").build();
            Call call = client.newCall(request);
            try {
                Response response = call.execute();
                String s = new String().concat(response.code() + "\n")
                        .concat(response.message() + "\n")
                        .concat(response.body().string());
                Log.d("sendReqTag", "onSuccess\n " + s);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    

    OkHttp的简单使用方法大致使用如上,其中也有一些细节需要注意:

    1、在应用层使用OkHttp,必然会涉及到4个重要元素:

    • OkHttpClient类(产生OkHttp客户端实例)
    • Request类(请求封装)
    • Call类(网络任务封装,并决定是要同步执行还是异步执行,注意,同步请求不可以放在主线程中,但是异步请求可以 )
    • Response类(网络任务执行之后的回调)

    2、执行网络请求必须在manifest中申请INTERNET权限,不然会抛异常.

    3、完整的一个请求执行出去,流程如下图:


    image.png

    OkHttp源码核心类之一:分发器详解

    上述,提到Call类,可以选择性执行 同步或者异步请求,但是无论同步异步,都一定会经过一个门户:"分发器" :
    索引进源码(okhttp v3.10.0):

    异步请求.png
    同步请求.png
    虽然用户不需要直接操作分发器,但是 分发器,作为OkHttp架构的一个门户层,是所有请求的必经之路,其中的代码还是有必要了解细节的。

    同步请求进入分发器 Dispatcher之后, 会执行 getResponseWithInterceptorChain() 来执行这个Call任务,得到一个Response,其中的细节分为两步:

    1、client.dispatcher().executed(this); ,进入源码可以看到 仅仅是执行了 runningSyncCalls.add(call);,将call对象加入到了一个双端队列 Deque<RealCall> runningSyncCalls 中。
    2、

    异步请求进入分发器之后,


    image.png

    可能会被加入到 Deque<AsyncCall> runningAsyncCalls 这么一个双端队列中,然后 executorService().execute(call);实际上是用了线程池来执行了这个异步任务。
    但是,请注意(还是刚才的enqueue方法代码)这里有一个判断条件 if分支 :

    image.png
    这个条件是否满足,将会直接决定是直接执行这个任务,还是将任务加入到 readyAsyncCalls 双端队列.

    那么设置这个条件的目的是什么呢?从变量命名来看:
    runningAsyncCalls 执行中的异步任务
    runningCallsForHost 同一个域名正在执行的任务数
    readyAsyncCalls 预备执行的任务队列(尚未执行)

    当正在执行的任务数小于最大值(默认为64)并且,同一个域名正在请求的任务数小于最大值(默认5)时,才会立即执行,否则,这个任务会被加入到 readyAsyncCalls中等待安排。

    那么问题来了,readyAsyncCalls中的任务什么时候会被执行?
    追踪代码:追踪 readyAsyncCalls 的使用代码,找到遍历这个队列的地方:

    image.png

    继续追踪,找到了这个 finish方法:


    image.png

    继续追踪finish在哪里调用的,找到两处:

    image.png
    image.png

    所以,得出结论:
    在一个任务(无论同步还是异步)结束之后,分发器中的异步任务,存在两个队列,一个running队列,一个ready队列,当 running队列的size小于最大值,并且同一个域名正在执行的任务数小于最大值时,可以直接加入到running队列,立即执行。 如果不满足这条件,这个异步任务就会被加入到 ready队列,在任意一个任务执行完毕(无论成败)之后,就会从ready队列中取出一个任务,加入到running队列,并且立即执行。周而复始,直到所有的异步任务都执行完。

    文字不够形象,画个图表示。


    OkHttp源码核心类之一:拦截器简述


    结语


    相关文章

      网友评论

        本文标题:手把手讲解 OkHttp硬核知识点

        本文链接:https://www.haomeiwen.com/subject/kyfpjctx.html