美文网首页Android开发经验谈Android开发Android开发
带你学开源项目:OkHttp-- 自己动手实现 okhttp

带你学开源项目:OkHttp-- 自己动手实现 okhttp

作者: Android高级架构探索 | 来源:发表于2018-10-14 22:35 被阅读26次

    一、开源项目 OkHttp

    在 Android、Java 开发领域中,相信大家都听过或者在使用 Square 家大名鼎鼎的网络请求库——OkHttp——https://github.com/square/okhttp ,当前多数著名的开源项目如 Fresco、Glide、 Picasso、 Retrofit都在使用 OkHttp,这足以说明其质量,而且该项目仍处在 不断维护中。

    二、问题

    在分析 okhttp 源码之前,我想先提出一个问题,如果我们自己来设计一个网络请求库,这个库应该长什么样子?大致是什么结构呢?

    下面我和大家一起来构建一个网络请求库,并在其中融入 okhttp 中核心的设计思想,希望借此让读者感受并学习到 okhttp 中的精华之处,而非仅限于了解其实现。

    笔者相信,如果你能耐心阅读完本篇,不仅能对 http 协议有进一步理解,更能够学习到世界级项目的思维精华,提高自身思维方式。

    三、思考

    首先,我们假设要构建的的网络请求库叫做WingjayHttpClient,那么,作为一个网络请求库,它最基本功能是什么呢?

    在我看来应该是:接收用户的请求 -> 发出请求 -> 接收响应结果并返回给用户。

    那么从使用者角度而言,需要做的事是:

    创建一个Request:在里面设置好目标 URL;请求 method 如 GET/POST 等;一些 header 如 Host、User-Agent 等;如果你在 POST 上传一个表单,那么还需要 body。
    将创建好的 Request 传递给WingjayHttpClient。
    WingjayHttpClient去执行 Request,并把返回结果封装成一个Response 给用户。而一个 Response 里应该包括 statusCode 如 200,一些 header 如 content-type 等,可能还有 body
    到此即为一次完整请求的雏形。那么下面我们来具体实现这三步。

    四、雏形实现
    下面我们先来实现一个 httpClient 的雏形,只具备最基本的功能。

    1. 创建 Request 类
      首先,我们要建立一个 Request 类,利用 Request 类用户可以把自己需要的参数传入进去,基本形式如下:


      image.png
    2. 将 Request 对象传递给 WingjayHttpClient
      我们可以设计 WingjayHttpClient 如下:


      image.png
    3. 执行 Request,并把返回结果封装成一个Response 返回


      image.png

    五、功能扩展

    利用上面的雏形,可以得到其使用方法如下:


    image.png

    三、思考然而,上面的雏形是远远不能胜任常规的应用需求的,因此,下面再来对它添加一些常用的功能模块。

    1. 重新把简陋的 user Request 组装成一个规范的 http request
      一般的 request 中,往往用户只会指定一个 URL 和 method,这个简单的 user request 是不足以成为一个 http request,我们还需要为它添加一些 header,如 Content-Length, Transfer-Encoding, User-Agent, Host, Connection, 和 Content-Type,如果这个 request 使用了 cookie,那我们还要将 cookie 添加到这个 request 中。

    我们可以扩展上面的 sendRequest(request) 方法:


    image.png

    2. 支持自动重定向

    有时我们请求的 URL 已经被移走了,此时 server 会返回 301 状态码和一个重定向的新 URL,此时我们要能够支持自动访问新 URL 而不是向用户报错。

    对于重定向这里有一个测试性 URL:http://www.publicobject.com/helloworld.txt ,通过访问并抓包,可以看到如下信息:

    image

    因此,我们在接收到 Response 后要根据 status_code 是否为重定向,如果是,则要从 Response Header 里解析出新的 URL-Location并自动请求新 URL。那么,我们可以继续改写 sendRequest(request) 方法:

    image.png
    利用上面的代码,我们通过获取原始 userRequest 的返回结果,判断结果是否为重定向,并做出自动 followup 处理。

    一些常用的状态码
    100~199:指示信息,表示请求已接收,继续处理
    200~299:请求成功,表示请求已被成功接收、理解、接受
    300~399:重定向,要完成请求必须进行更进一步的操作
    400~499:客户端错误,请求有语法错误或请求无法实现
    500~599:服务器端错误,服务器未能实现合法的请求

    1. 支持重试机制
      所谓重试,和重定向非常类似,即通过判断 Response 状态,如果连接服务器失败等,那么可以尝试获取一个新的路径进行重新连接,大致的实现和重定向非常类似,此不赘述。

    2. Request & Response 拦截机制
      这是非常核心的部分。

    通过上面的重新组装 request 和重定向机制,我们可以感受的,一个 request 从 user 创建出来后,会经过层层处理后,才真正发出去,而一个response,也会经过各种处理,最终返回给用户。

    笔者认为这和网络协议栈非常相似,用户在应用层发出简单的数据,然后经过传输层、网络层等,层层封装后真正把请求从物理层发出去,当请求结果回来后又层层解析,最终把最直接的结果返回给用户使用。

    最重要的是,每一层都是抽象的,互不相关的!

    因此在我们设计时,也可以借鉴这个思想,通过设置 拦截器 Interceptor,每个拦截器会做两件事情:

    1 .接收上一层拦截器封装后的 request,然后自身对这个 request 进行处理,例如添加一些 header,处理后向下传递;

    2 .接收下一层拦截器传递回来的 response,然后自身对 response 进行处理,例如判断返回的 statusCode,然后进一步处理。

    那么,我们可以为拦截器定义一个抽象接口,然后去实现具体的拦截器。

    image.png

    大家可以看下上面这个拦截器设计是否有问题?

    image

    我们想象这个拦截器能够接收一个 request,进行拦截处理,并返回结果。

    但实际上,它无法返回结果,而且它在处理 request 后,并不能继续向下传递,因为它并不知道下一个 Interceptor 在哪里,也就无法继续向下传递。

    那么,如何解决才能把所有 Interceptor 串在一起,并能够依次传递下去。

    image.png
    使用方法如下:假如我们现在有三个 Interceptor 需要依次拦截:
    image.png
    里面的 RealInterceptorChain 的基本思想是:我们把所有 interceptors 传进去,然后 chain 去依次把 request 传入到每一个 interceptors 进行拦截即可。

    通过下面的示意图可以明确看出拦截流程:

    image

    其中,RetryAndFollowupInterceptor是用来做自动重试和自动重定向的拦截器;BridgeInterceptor是用来扩展 requestheader的拦截器。这两个拦截器存在于 okhttp 里,实际上在 okhttp 里还有好几个拦截器,这里暂时不做深入分析。

    image

    1 .CacheInterceptor
    这是用来拦截请求并提供缓存的,当 request 进入这一层,它会自动去检查缓存,如果有,就直接返回缓存结果;否则的话才将 request 继续向下传递。而且,当下层把 response 返回到这一层,它会根据需求进行缓存处理;

    2 .ConnectInterceptor
    这一层是用来与目标服务器建立连接

    3 .CallServerInterceptor
    这一层位于最底层,直接向服务器发出请求,并接收服务器返回的 response,并向上层层传递。

    上面几个都是 okhttp 自带的,也就是说需要在 WingjayHttpClient 自己实现的。除了这几个功能性的拦截器,我们还要支持用户 自定义拦截器,主要有以下两种(见图中非虚线框蓝色字部分):

    1. interceptors
      这里的拦截器是拦截用户最原始的 request。

    2. NetworkInterceptor
      这是最底层的 request 拦截器。

    如何区分这两个呢?举个例子,我创建两个 LoggingInterceptor,分别放在interceptors 层和 NetworkInterceptor 层,然后访问一个会重定向的 URL_1,当访问完URL_1 后会再去访问重定向后的新地址 URL_2。对于这个过程,interceptors 层的拦截器只会拦截到 URL_1 的 request,而在 NetworkInterceptor 层的拦截器则会同时拦截到 URL_1 和URL_2两个 request。具体原因可以看上面的图。

    5. 同步、异步 Request 池管理机制

    这是非常核心的部分。

    通过上面的工作,我们修改 WingjayHttpClient 后得到了下面的样子:


    image.png

    也就是说,WingjayHttpClient现在能够 同步 地处理单个 Request 了。

    然而,在实际应用中,一个 WingjayHttpClient 可能会被用于同时处理几十个用户 request,而且这些 request 里还分成了 同步 和异步 两种不同的请求方式,所以我们显然不能简单把一个 request 直接塞给WingjayHttpClient。

    我们知道,一个 request 除了上面定义的 http 协议相关的内容,还应该要设置其处理方式 同步 和异步。那这些信息应该存在哪里呢?两种选择:

    直接放入 Request
    从理论上来讲是可以的,但是却违背了初衷。我们最开始是希望用 Request 来构造符合 http 协议的一个请求,里面应该包含的是请求目标网址 URL,请求端口,请求方法等等信息,而 http 协议是不关心这个 request 是同步还是异步之类的信息

    创建一个类,专门来管理 Request 的状态
    这是更为合适的,我们可以更好的拆分职责。

    因此,这里选择创建两个类 SyncCall 和AsyncCall,用来区分 同步 和异步。


    image.png

    基于上面两个类,我们的使用场景如下:


    image.png
    从上面的代码可以看到,WingjayHttpClient的职责发生了变化:以前是response = client.sendRequest(request);,而现在变成了
    image.png

    那么,我们也需要对 WingjayHttpClient 进行改造,基本思路是在内部添加 请求池 来对所有 request 进行管理。那么这个 请求池 我们怎么来设计呢?有两个方法:

    1 .直接在 WingjayHttpClient 内部创建几个容器
    同样,从理论上而言是可行的。当用户把(a)syncCall 传给 client 后,client 自动把 call 存入对应的容器进行管理。

    2 .创建一个独立的类进行管理
    显然这样可以更好的分配职责。我们把 WingjayHttpClient 的职责定义为,接收一个 call,内部进行处理后返回结果。这就是 WingjayHttpClient 的任务,那么具体如何去管理这些 request 的执行顺序和生命周期,自然不需要由它来管。

    因此,我们创建一个新的类:Dispatcher,这个类的作用是:

    1 .存储外界不断传入的 SyncCall 和AsyncCall,如果用户想取消则可以遍历所有的 call 进行 cancel 操作;

    2 .对于 SyncCall,由于它是即时运行的,因此Dispatcher 只需要在 SyncCall 运行前存储进来,在运行结束后移除即可;

    3 .对于 AsyncCall,Dispatcher 首先启动一个 ExecutorService,不断取出 AsyncCall 去进行执行,然后,我们设置最多执行的 request 数量为 64,如果已经有 64 个 request 在执行中,那么就将这个 asyncCall 存入等待区。

    根据设计可以得到 Dispatcher 构造:


    image.png

    有了这个 Dispatcher,那我们就可以去修改WingjayHttpClient 以实现



    这两个方法了。具体实现如下
    image.png

    基于以上,我们能够很好的处理 同步 和异步 两种请求,使用场景如下:


    image.png
    六、总结
    到此,我们基本把 okhttp 里核心的机制都讲解了一遍,相信读者对于 okhttp 的整体结构和核心机制都有了较为详细的了解。

    如果有问题欢迎联系我。

    谢谢!

    相关文章

      网友评论

        本文标题:带你学开源项目:OkHttp-- 自己动手实现 okhttp

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