Android-Okhttp3源码解析

533 阅读13分钟

网络库的选择

  理论上来说几乎所有的网络库都是基于Socket实现的,在Socket的基础上可以实现各种应用层的通信协议,比http、ftp等。Java实现的网络库理论上来说都可以在Android端上使用,但由于Android这种嵌入式系统的特性,对网络请求库的要求可能会更严格。
  HttpUrlConnection和HttpClient在API23之后已经从系统源码中移除了,开发时如果要使用到网络请求,可能需要额外引入一些第三方库。目前常用的网络请求库包括android-async-http、Volley、Okhttp以及Retrofit。Retrofit其实并不是一个网络请求库,更准确地说它是一个网络封装库,能够将底层的网络请求库封装成RESTful API设计风格。其中android-async-http是基于HttpClient封装的,主要是封装了异步线程与main线程之间的切换以及智能请求重试,持久化、cookie保存到SP等。Vollery是曾经最有名的Android请求库,基于HttpUrlConnection,支持图片加载,网络请求排序,优先级处理缓存与Activity生命周期联运等。其实Vollery的底层网络请求也可以使用其他网络请求库替换。OkHttp是近几年来最受欢迎的网络库了,现在几乎所有的app的网络请求库都使用OkHttp或者基于OkHttp来定制的。OkHttp已经不是基于HttpUrlConnection了,而是使用Socket来实现了一套网络请求。OkHttp支持同步、异步,封装了线程池、数据转换、参数使用,错误处理等。并且Socket还使用了NIO,在非阻塞的技术下更大程度地提高网络请求的性能。

OkHttp的demo使用

package com.benson.android.network

import android.graphics.Color
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.Request
import java.lang.StringBuilder
import java.util.concurrent.TimeUnit

class Okhttp3DemoActivity: AppCompatActivity() {

    val client by lazy { OkHttpClient.Builder()
        .retryOnConnectionFailure(true) // 连接超时重试
        .connectTimeout(2L, TimeUnit.MINUTES) // 2s 连接超时
        .readTimeout(2L, TimeUnit.MINUTES) // 2s 读超时
        .writeTimeout(2L, TimeUnit.MINUTES) // 2s 写超时
        .build()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val content = LinearLayout(this)
        content.orientation = LinearLayout.VERTICAL
        initView(content)
        setContentView(content)
    }

    private fun initView(content: ViewGroup) {
        val searchBar = LinearLayout(this)
        searchBar.orientation = LinearLayout.HORIZONTAL
        searchBar.gravity = Gravity.CENTER
        val input = EditText(this)
        input.textSize = DisplayUtil.sp2px(this, 10.0F) * 1.0F
        searchBar.addView(input, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 7.0F))
        val searchBtn = Button(this)
        searchBtn.text = "search"
        searchBtn.textSize = DisplayUtil.sp2px(this, 10.0F) * 1.0F
        searchBar.addView(searchBtn, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 2.0F))
        content.addView(searchBar, LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.0F))
        val result = TextView(this)
        result.setBackgroundColor(Color.BLACK)
        result.setTextColor(Color.WHITE)
        result.textSize = DisplayUtil.sp2px(this, 15.0F) * 1.0F
        val scrollView = ScrollView(this)
        scrollView.addView(result)
        content.addView(scrollView, LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 10.0F))
        searchBtn.setOnClickListener {
            search(result, input.text.toString())
        }
    }

    private fun search(result: TextView, url: String) {
        GlobalScope.launch {
            val request = Request.Builder()
                .url(url)
                .get()
                .build()
            val response = client.newCall(request).execute()
            val content = response.body?.string()
            val resultStr = StringBuilder()
            resultStr.append("code=").append(response.code).append("\n")
                .append("msg=").append(response.message).append("\n")
                .append("headers=").append(response.headers).append("\n")
                .append(content)
            result.post {
                result.text = resultStr.toString()
            }
        }
    }

}

执行之后效果图如下 这里是使用了同步调用,执行了execute返回一个Response对象,在这个Response中就可以得到Http请求结果的参数。

源码解析——OkHttpClient的创建

  OkHttp3的请求过程比较简单,使用也很方便。那还是以创建过程和请求过程两个方面来剖析OkHttp3的原理吧。
  OkHttp的Manager对象是OkHttpClient,也是使用Builder模式创建出来的。以下是OkHttpClient.Builder的属性

    // 异步请求调度器
    internal var dispatcher: Dispatcher = Dispatcher()
    // 请求连接池
    internal var connectionPool: ConnectionPool = ConnectionPool()
    // 各种拦截器
    internal val interceptors: MutableList<Interceptor> = mutableListOf()
    // 网络拦截器,非websocket的请求才会使用
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
    // 事件监听器工厂
    internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
    // 是否支持连接失败重试
    internal var retryOnConnectionFailure = true
    // 校验相关
    internal var authenticator: Authenticator = Authenticator.NONE
    internal var followRedirects = true
    internal var followSslRedirects = true
    internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
    internal var cache: Cache? = null
    internal var dns: Dns = Dns.SYSTEM
    // 代理相关
    internal var proxy: Proxy? = null
    internal var proxySelector: ProxySelector? = null
    internal var proxyAuthenticator: Authenticator = Authenticator.NONE
    internal var socketFactory: SocketFactory = SocketFactory.getDefault()
    internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
    internal var x509TrustManagerOrNull: X509TrustManager? = null
    internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
    internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
    internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
    internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
    internal var certificateChainCleaner: CertificateChainCleaner? = null
    internal var callTimeout = 0
    // 连接超时
    internal var connectTimeout = 10_000
    // 读取数据超时
    internal var readTimeout = 10_000
    // 写数据超时
    internal var writeTimeout = 10_000
    internal var pingInterval = 0
    internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
    internal var routeDatabase: RouteDatabase? = null

每个属性的意义如注释所示,这些属性用Builder构造出一个OkHttpClient之后,相当于就是一个配置了,每次执行请求时都会应用这些配置。

源码解析——Request的创建

Request的创建过程也是使用了Builder模式,主要包括url、method、headers以及body四个参数,这也是http的request的参数了。url主要是请求的地址,可以包含GET请求的参数,method表示http请求类型,一般有GET、POST、PUT、DELETE等。header就是请求头了,主要是包含一些cookie和校验之类的信息,body一般用于POST请求的请求体,例如文件上传。Request也只是一个配置类,真正请求时会读取这些配置。

源码解析——请求过程

OkHttpClient首先使用request构造出一个Call对象,Call接口的实现类是RealCall。这个Call对象就有同步和异步两种执行方式了,分别是调用execute和enqueue方法。RealCall中execute实现代码如下

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)
    }
  }

首先检查executed是否为false,如果为false则置为true,否则抛异步,这里是保存每个Call对象只能执行一次。然后调用超时计时器接着调用callStart,这是为了通知事件监听,已经调用了callStart。execute执行的重点是最后三个调用,调用OkHttpClient的dispatcher的executed方法,将Call对象添加到runningSyncCalls队列中,getResponseWithInterceptorChain方法取得请求结果对象,在finally中调用OkHttpClient的dispatcher的finished方法,将call对象从call队列中移除,同时执行idleCallback的run方法。在这三个方法调用中getResponseWithInterceptorChain方法又是最重要的那一个。
  看下RealCall中getResponseWithInterceptorChain的实现

@Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors
    interceptors += RetryAndFollowUpInterceptor(client)
    interceptors += BridgeInterceptor(client.cookieJar)
    interceptors += CacheInterceptor(client.cache)
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)

    val chain = RealInterceptorChain(
        call = this,
        interceptors = interceptors,
        index = 0,
        exchange = null,
        request = originalRequest,
        connectTimeoutMillis = client.connectTimeoutMillis,
        readTimeoutMillis = client.readTimeoutMillis,
        writeTimeoutMillis = client.writeTimeoutMillis
    )

    var calledNoMoreExchanges = false
    try {
      val response = chain.proceed(originalRequest)
      if (isCanceled()) {
        response.closeQuietly()
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
      calledNoMoreExchanges = true
      throw noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        noMoreExchanges(null)
      }
    }
  }

首先创建了一个Interceptor的列表,将OkHttpClient创建时传入的interceptor添加进来,同时还添加请求重定向拦截器RetryAndFollowUpInterceptor、cookie处理拦截器BridgeInterceptor、缓存拦截器CacheInterceptor、以及网络连接拦截器ConnectInterceptor,如果这个请求不是WebSocket请求,还会把OkHttpClient中的networkInterceptors添加到拦截器列表中,最后一个是CallServerInterceptor,是为了真正执行网络请求,并解析请求结果的。拦截器列表构造好后,new出一个拦截器链RealInterceptorChain,调用RealInterceptorChain的proceed方法开始执行逐个拦截器的intercept方法。RealInterceptorChain的proceed方法中每次都会调用copy方法再创建出一个新的RealInterceptorChain对象,使用这个新的chain对象继续执行下一个Interceptor,我理解这里是为了让每个Interceptor都拥有一个新的执行环境,这个chain里就包含了拦截器的执行环境,这样每个interceptor执行都不会相互影响。
  总体上来看,chain的拦截器执行是个职责链模式,每个拦截器都可以实现网络请求过程中的部分功能,最后由CallInterceptor收尾,进行最终的网络请求,每个中间interceptor在拦截处理方法中都最终调用chain的proceed方法返回结果,这就相当于递归调用,让职责链一节一节地执行。那OkHttp的请求执行过程其实就蕴含在提供的几个默认拦截器中了。

OkHttp的默认拦截器

  首先是请求重定向拦截器RetryAndFollowUpInterceptor,这个拦截器还会处理请求错误重试的操作。在这个拦截器的intercept方法中有个while(true)循环,在循环体中调用chain的proceed方法,也就是会不断地执行后面的拦截器列表。proceed被catch起来,在catch中调用了RetryAndFollowUpInterceptor的recover方法,这个recover是用来判断是否还可以进行重试的。如果能执行重试,就调用followUpRequest,判断是否可重定向,floowUpRequest方法中取出了response中的code参数,如果code是HPPT_PROXY_AUTH(407),表示使用了代理,如果代理不是HTTP请求,则抛异常,否则使用代码认证并返回一个request对象。如果code是HTTP_UNAUTHORIZED(401)则表示要用户认证。如果code是HTTP_PERM_REDIRECT(308), HTTP_TEMP_REDIRECT(307), HTTP_MULT_CHOICE(300), HTTP_MOVED_PERM(301), HTTP_MOVED_TEMP(302), HTTP_SEE_OTHER(303)则表示重定向,调用buildRedirectRequest构造出一个新的重定向Request对象。如果code是HTTP_CLIENT_TIMEOUT(408)并且未命中过,则执行返回response的request。followUpRequest得到一个新的request对象后,继续while循环中的请求过程,把后面的拦截器再执行一遍。这个while循环中没有看到break关键字,所以这个while循环只有抛出异常和return两条出路。return就是返回一个response给开始的execute,抛异常则有多种,比如不支持重试并且请求异常了,或者重试次数达到最大限制数了,这里的最大重试数据为MAX_FOLLOW_UPS=20。
  然后是BridgeInterceptor,这个拦截器是把OkHttp定义的字段转化成Http协议的字段同时处理cookie,这里主要是给request添加一些http请求的默认参数,当请求构造时未传入这些参数时,就会使用BridgeInterceptor的默认参数,以此来构造一个Http协议完整的请求头和请求体。在BridgeInterceptor的intercept中间位置,调用了chain.proceed继续执行后面的拦截器并得到一个Response,这个response被BridgeInterceptor进一步解析。因此这个BridgeInterceptor的真正作用就是在真正请求前给request添加一些默认参数,并将request中传入的参数转化为http协议的参数,例如将content_type转化为header中的"Content-Type",将请求后的结果进行进一步地解析处理。
  第三个是缓存相关的CacheInterceptor,在其intercept方法中首先就从传入的cache对象中根据request取出一个cacheCandidate对象,而cache是个Cache对象,默认是以url作为缓存的key标识。然后使用CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()取出缓存的request和response。这里如果request为null并且response也为null,则表示本应从缓存中取,但缓存没有了,那就给一个空的response。如果request为null而response不为null,则表示从缓存中取成功,用缓存的response构造出最终的response。如果request也不为null,表表示还是要进行网络请求,执行chain.proceed,如果response的code是HTTP_NOT_MODIFIED(304),表示服务端告诉客户端,缓存还可以使用,则response还是使用缓存的response来构造,否则就使用网络请求回来的response,并将request和response存入缓存中。
  ConnectInterceptor的作用就比较简单了,只是开启一个网络连接,这个网络连接可能会被已经返回的response使用,也可能会被失败了的缓存response使用,OkHttp框架的网络连接使用Exchange对象来表示。
  最后就是CallServierInterceptor,最终会使用这个拦截器进行真正的网络请求。

  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.exchange!!
    val request = realChain.request
    val requestBody = request.body
    val sentRequestMillis = System.currentTimeMillis()

    exchange.writeRequestHeaders(request)

    var invokeStartEvent = true
    var responseBuilder: Response.Builder? = null
    if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
        exchange.flushRequest()
        responseBuilder = exchange.readResponseHeaders(expectContinue = true)
        exchange.responseHeadersStart()
        invokeStartEvent = false
      }
      if (responseBuilder == null) {
        if (requestBody.isDuplex()) {
          // Prepare a duplex body so that the application can send a request body later.
          exchange.flushRequest()
          val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
          requestBody.writeTo(bufferedRequestBody)
        } else {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
          requestBody.writeTo(bufferedRequestBody)
          bufferedRequestBody.close()
        }
      } else {
        exchange.noRequestBody()
        if (!exchange.connection.isMultiplexed) {
          // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
          // from being reused. Otherwise we're still obligated to transmit the request body to
          // leave the connection in a consistent state.
          exchange.noNewExchangesOnConnection()
        }
      }
    } else {
      exchange.noRequestBody()
    }

    if (requestBody == null || !requestBody.isDuplex()) {
      exchange.finishRequest()
    }
    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
      if (invokeStartEvent) {
        exchange.responseHeadersStart()
        invokeStartEvent = false
      }
    }
    var response = responseBuilder
        .request(request)
        .handshake(exchange.connection.handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build()
    var code = response.code
    if (code == 100) {
      // Server sent a 100-continue even though we did not request one. Try again to read the actual
      // response status.
      responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
      if (invokeStartEvent) {
        exchange.responseHeadersStart()
      }
      response = responseBuilder
          .request(request)
          .handshake(exchange.connection.handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build()
      code = response.code
    }

    exchange.responseHeadersEnd(response)

    response = if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response.newBuilder()
          .body(EMPTY_RESPONSE)
          .build()
    } else {
      response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build()
    }
    if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
        "close".equals(response.header("Connection"), ignoreCase = true)) {
      exchange.noNewExchangesOnConnection()
    }
    if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
      throw ProtocolException(
          "HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
    }
    return response
  }

首先调用了exchange.writeRequestHeaders将请求头写入到连接,如果请求不是GET或者PUT的,就要先处理请求体,使用exchange创建Sink对象并将requestBody写入到Sink中去。
  这里需要再回到ConnectInterceptor中去看如果创建的一个连接,一个连接的底层是用什么实现的。前面说到过,interceptor chain中的exchange就是一个连接的抽象,在ConnectInterceptor中就对exchange进行了初始化。调用了RealCall的initExchange方法,在initExchange方法中使用了exchangeFinder,这个finder中拥有一个连接池和一个地址,地址由请求的url构建,而连接池是OkHttpClient的连接池,也就是说同一个OkHttpClient发出的请求,共用一个连接池,当同一个OkHttpClient对一个地址发送多个请求时,可能就可以复用连接。使用exchangeFinder调用其find方法是为创建一个ExchangeCodec对象,在find方法中调用findHealthyConnection方法先获取一个连接对象,根据方法名就可以看出,这个方法会去连接池找可以复用的连接。
  findHealthConnection方法中在一个while循环中调用findConnection方法获取一个连接,如果判断连接isHealth,则return。继续跟到findConnection方法中去,首先判断这个call对象中是否已有connection,如果有则判断其可用的话,就复用这个connection。否则再调用RealConnectionPool的callAcquirePooledConnection方法从连接池中获取,连接池中有个RealConnection的ConcurrentLinkedQueue用来保存连接,循环判断连接池中的连接可以被传入的地址使用的话,就将这个连接设置给call对象并将这个连接对象返回。如果从连接池中取不到连接,则新建一个RealConnection对象,并调用其connect方法新建一个连接。connect方法中调用了connectSocket方法创建了Socket对象,并将Socket保存在RealConnection的rawSocket属性中,创建Socket后调用其connect方法与服务端建立一个网络连接。
  再回到CallServerInterceptor中,当请求体写完后,调用exchanged的flushRequest方法,flushRequest方法中调用了BufferSink的flush方法,这个方法里最终调用了Socket的OutputStream的write,将数据写到服务端连接中去。
  写完请求头数据了就需要开始读服务端返回的http结果了,先调用exchange.readResponseHeaders读取响应头,读取响应头时,把response的code、message、protocol也同时给解析了。如果判断响应体为null,则将请求体数据写到服务端。将读取到的responseBuilder构造成一个response对象,判断如果不是websocket并且code不是101,则还需要请求响应体数据,这样一个请求Response才算完全构造成功,最后将这个response返回给拦截链。

总结

  OkHttp框架基于职责链模式,将一个请求的过程分拆为多个小过程,每个小过程由一个Interceptor来完成,并且使用者可以在所有内置interceptor之前插入自定义的interceptor对请求过程加以扩展。网络连接部分OkHttp框架使用Exchange对象予以抽象,Interceptor只实现请求逻辑,具体的数据读写由Exchange来完成,因此理论来说底层的数据请求我们还可以替换。整个框架对于逻辑处理与数据处理耦合较低,扩展性较强。