OkHttp源码简单分析

OkHttp简单使用

val client = OkHttpClient.Builder().build()
val request = Request.Builder().url("www.baidu.com").get().build()
//同步请求
val response = client.newCall(request).execute()
//异步请求
client.newCall(request).enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) { }
    override fun onFailure(call: Call, e: IOException) { }
})

OkHttpClient是OkHttp对外开放用于网络请求的核心类,通过建造者模式可以进行一些配置,如设置超时时间、添加拦截器等等。Request是请求体对象用于构造url、请求类型、请求参数等,Response是响应体对象,包含完整的http响应体内容。

RealCall请求

OkHttp所有请求都是通过Call对象来处理的,Call只是一个接口,定义了请求的相关方法。RealCall是Call的实现类,每一次真正的请求都被封装成RealCall对象。

interface Call : Cloneable {
    fun request(): Request                   //返回当前请求
    fun execute(): Response                  //同步请求,阻塞线程
    fun enqueue(responseCallback: Callback)  //异步请求,进入请求队列等待返回
    fun cancel()                             //取消请求
    fun isExecuted(): Boolean                //请求是否在执行
    fun isCanceled(): Boolean                //请求是否在执行
    fun timeout(): Timeout                   //返回超时对象
    public override fun clone(): Call        //克隆新的请求
}

我们具体看看RealCall的同步和异步请求有什么区别

override fun execute(): Response {
    check(executed.compareAndSet(false, true)) { "Already Executed" }
    timeout.enter()
    callStart()
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
}
override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }
    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
}

从代码来看,不管是同步请求还是异步请求都是通过Dispatcher来调度的。同步请求直接执行,返回Response对象。异步请求构造AsyncCall,传入callback作为回调接口。AsyncCall本质是一个Runnable,Dispatcher通过ExecutorService(线程池)来执行这些Runnable。

Dispatcher调度器

Dispatcher任务调度器,内部维护了三个队列。当发起一个同步或者异步请求时,都会被 Dispatcher保存下来,Dispatcher对请求进行统一的管理,例如结束所有请求、调度线程池资源等等。

val readyAsyncCalls = ArrayDeque<AsyncCall>()    //准备运行的异步请求
val runningAsyncCalls = ArrayDeque<AsyncCall>()  //正在运行的异步请求
val runningSyncCalls = ArrayDeque<RealCall>()    //正在运行的同步请求

拦截器链

拦截器是OkHttp的精髓,包含了从请求到响应整个过程。

不论是同步还是异步最后都会通过getResponseWithInterceptorChina()获取Response,只不过异步请求多了线程调度,异步执行的过程。在getResponseWithInterceptorChina()可以看到所有拦截器。

internal fun getResponseWithInterceptorChain(): Response {
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors
    //负责失败重试以及重定向
    interceptors += RetryAndFollowUpInterceptor(client)
    //负责将request转化为http请求,http返回数据转化为response
    interceptors += BridgeInterceptor(client.cookieJar)
    //负责读取缓存以及更新缓存
    interceptors += CacheInterceptor(client.cache)
    //负责与服务器建立连接
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
        interceptors += client.networkInterceptors
    }
    //负责数据收发
    interceptors += CallServerInterceptor(forWebSocket)
    val chain = RealInterceptorChain()
    val response = chain.proceed(originalRequest)
    return response
    ......
}

RealInterceptorChain是带有整个拦截器的具体的拦截器链,proceed方法是执行点。可以理解为在具体拦截器的intercept中,在执行点proceed之前的是用于request请求的,之后则是response响应的过程。这也是OkHttp双向责任链的核心。

  override fun proceed(request: Request): Response {
    ......
    calls++
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]
    val response = interceptor.intercept(next)
    return response
  }

RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor主要负责失败重试以及重定向,由于代理及 DNS 的原因,对于同一个 url 可能会有多个 IP 地址,连接时通过 RouteSelector 选择合适的 Route 进行连接,所以这里的失败重试并不是指对同一 IP 地址的多次重试,是逐个尝试路由表中的地址。

override fun intercept(chain: Interceptor.Chain): Response {
    ......
    var newExchangeFinder = true
    while(true) {
        call.enterNetworkInterceptorExchange(request, newExchangeFinder)
        try {
            response = realChain.proceed(request)
        } catch(e: Exception) {
            //判断是否可以重试
            !recover(e.lastConnectException, call, request, false)
            newExchangeFinder = false
            continue
        }
        //判断是否需要重定向
        val followUp = followUpRequest(response, exchange)
        ......
        return response
    }
}

BridgeInterceptor

BridgeInterceptor负责将request转化为http请求,http返回数据转化为response。其中也包括cookie和gzip的处理。

override fun intercept(chain: Interceptor.Chain): Response {
    ......
    val cookies = cookieJar.loadForRequest(userRequest.url)
    if (cookies.isNotEmpty()) {
        //添加cookie
        requestBuilder.header("Cookie", cookieHeader(cookies))
    }
    //进入下个拦截器CacheInterceptor
    val networkResponse = chain.proceed(requestBuilder.build())
    //保存cookie
    cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
    if (transparentGzip &&
        "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&networkResponse.promisesBody()) {
        //gzip解析
    }
    ......
}

CacheInterceptor

CacheInterceptor主要用于读取缓存直接返回以及更新缓存。默认是没有开启缓存的,需要在OhHttpClient中设置开启。

override fun intercept(chain: Interceptor.Chain): Response {
    //获取缓存策略
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse
    //缓存策略中设置禁止使用网络,并且缓存又为空,返回504
    if (networkRequest == null && cacheResponse == null) {
      return Response.Builder()
          .request(chain.request())
          .code(HTTP_GATEWAY_TIMEOUT)
          .body(EMPTY_RESPONSE)
          .build()
    }
    //缓存策略中设置不使用网络,但是有缓存,直接返回缓存
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }
    chain.proceed(networkRequest)
    if (cache != null) {
        //缓存response
        val cacheRequest = cache.put(response)
    }
    return response
}

缓存主要使用了策略工厂的设计模式,通过response,time,request创建一个缓存策略,用于判断怎样使用缓存。其中CacheStrategy类主要用于根据Http中的Cache-control协议,它根据取出来的缓存结果与当前发送的Request的header进行策略计算,得到缓存是否可用的结果。请求响应时,当缓存存在的时候,如果网络返回的Resposne为304,则使用缓存的Resposne。构建网络请求的Resposne当在OkHttpClient中配置了缓存,则将这个Resposne缓存起来。缓存的步骤也是先缓存header,再缓存body,最后返回Resposne。

ConnectInterceptor

ConnectInterceptor主要负责了dns解析和socket连接。

override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    //关键代码,dns和socket在此完成
    val exchange = realChain.call.initExchange(chain)
    val connectedChain = realChain.copy(exchange = exchange)
    return connectedChain.proceed(realChain.request)
}

CallServerInterceptor

CalllServerInterceptor是最后的拦截器,ConnectInterceptor已经完成了socket连接和tls连接,这一步就是传输http的头部和body数据。这部分的IO操作就是通过Okio来完成的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容