Okhttp基础使用一

从okhttp最基本的使用方式解析okhttp

okhttp的使用很简单

  • 导入依赖 implementation 'com.squareup.okhttp3:okhttp:4.5.0'使用的是4.x跟3.x版本相比代码已经转变为kotlin
  • new OkHttpClient;
  • 构造Request对象;
  • 通过前两步中的对象构建Call对象;
  • 通过Call.enqueue(Callback)方法来提交异步请求;
 val mOk = OkHttpClient()
        val request = Request.Builder()
            .url("请求地址")
            .get()//请求方式
            .build()
        val call = mOk.newCall(request)
        call.enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                Log.e(TAG, "请求成功")
            }

            override fun onFailure(call: Call, e: IOException) {
                Log.e(TAG, "请求失败")
            }
        })

步骤一:创建okhttpClicent实例

val mOk = OkHttpClient()
okhttp在这一步内进行了很多的默认配置,都在OkHttpClient()构造方法内的Builder()对象中,如连接超时,读取超时,写入超时,请求协议,重连,缓存等,所以如果要配置这些的话就要通过OkHttpClient.Builder()来构建OkhttpClient()对象。如果没有特别要求,使用默认的就可以,一句代码就搞定,使用起来是很方便的(●ˇ∀ˇ●)

 class Builder constructor() {
   //调度器
    internal var dispatcher: Dispatcher = Dispatcher()
   //连接池管理HTTP和HTTP/2连接的重用以减少网络延迟
    internal var connectionPool: ConnectionPool = ConnectionPool()
   //存储拦截器实例,本质责任链模式
    internal val interceptors: MutableList<Interceptor> = mutableListOf()
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
   //事件侦听器工厂 监听http通信
    internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
   //允许失败重连
    internal var retryOnConnectionFailure = true
    internal var authenticator: Authenticator = Authenticator.NONE
   //允许重定向
    internal var followRedirects = true
   //是否允许 SSL 重定向
    internal var followSslRedirects = true
   //存储cookie 不设置则不存储 cookie。
    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 val DEFAULT_PROTOCOLS = immutableListOf(HTTP_2, HTTP_1_1)
    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

在OkhttpClick构造方法内有一个调度器,主要有①并发执行的最大请求数②每个主机并发请求的最大数③有一个线程池,这个线程池内理论上允许的线程数是不受限制的,空闲线程超过60秒会自动被回收,线程池为了解决执行大量异步任务时,减少每个任务的调用开销,所提供的一种限制和管理资源的方法 ④调度器内还定义了三个队列两个异步一个同步⑤取消请求

 /**
   * 并发执行的最大请求数
   */
  @get:Synchronized var maxRequests = 64
    set(maxRequests) {
      require(maxRequests >= 1) { "max < 1: $maxRequests" }
      synchronized(this) {
        field = maxRequests
      }
      promoteAndExecute()
    }

  /**
   * 每个主机并发执行的最大请求数
   */
  @get:Synchronized var maxRequestsPerHost = 5
    set(maxRequestsPerHost) {
      require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
      synchronized(this) {
        field = maxRequestsPerHost
      }
      promoteAndExecute()
    }

  private var executorServiceOrNull: ExecutorService? = null

  @get:Synchronized
  @get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
       /**
        * 线程池 
        * 第一个参数要保留的线程,第二个参数允许的线程数,第三个参数多余空闲线程在终止前等待新任务的 
        * 最长时间
        */
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }

 /**按运行顺序准备异步调用 */
  private val readyAsyncCalls = ArrayDeque<AsyncCall>()

  /** 正在运行异步调用。 */
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()

  /** 运行的同步调用。 */
  private val runningSyncCalls = ArrayDeque<RealCall>()

  @Synchronized fun cancelAll() {
    for (call in readyAsyncCalls) {
      call.call.cancel()
    }
    for (call in runningAsyncCalls) {
      call.call.cancel()
    }
    for (call in runningSyncCalls) {
      call.cancel()
    }
  }

构造方法内还有一个连接池internal var connectionPool: ConnectionPool = ConnectionPool(),主要作用是管理HTTP和HTTP/2连接的重用以减少网络延迟,实现了多路复用,这也是okhttp的特点之一,目前默认是允许5个空闲连接数

/**一个参数最大空闲连接数,第二个参数保留连接时间,第三个时间单位*/
  constructor() : this(5, 5, TimeUnit.MINUTES)

步骤二:构造Request对象

 val request = Request.Builder()
            .url("请求地址")
            .get()//请求方式
            .build()

Request内主要是决定了请求地址,请求方式(post,get等),请求头,请求体,比较好理解。只贴关键代码

class Request internal constructor(
  @get:JvmName("url") val url: HttpUrl,
  @get:JvmName("method") val method: String,
  @get:JvmName("headers") val headers: Headers,
  @get:JvmName("body") val body: RequestBody?,
  internal val tags: Map<Class<*>, Any>
) {
 open class Builder {
    internal var url: HttpUrl? = null
    internal var method: String
    internal var headers: Headers.Builder
    internal var body: RequestBody? = null

    constructor() {
  //默认是“GET”请求
      this.method = "GET"
      this.headers = Headers.Builder()
    }

    /**
     * 设置URL,如果URL无效时会返回空
     */
    open fun url(url: String): Builder {
      // Silently replace web socket URLs with HTTP URLs.
      val finalUrl: String = when {
        url.startsWith("ws:", ignoreCase = true) -> {
          "http:${url.substring(3)}"
        }
        url.startsWith("wss:", ignoreCase = true) -> {
          "https:${url.substring(4)}"
        }
        else -> url
      }
      return url(finalUrl.toHttpUrl())
     /**@JvmStatic
        @JvmName("get") fun String.toHttpUrl(): HttpUrl = Builder().parse(null, this).build()*/
    }

    /**
     *设置请求头
     */
    open fun header(name: String, value: String) = apply {
      headers[name] = value
    }

    open fun addHeader(name: String, value: String) = apply {
      headers.add(name, value)
    }

    /** 删除请求头 */
    open fun removeHeader(name: String) = apply {
      headers.removeAll(name)
    }

    /**
     * 设置缓存
     */
    open fun cacheControl(cacheControl: CacheControl): Builder {
      val value = cacheControl.toString()
      return when {
        value.isEmpty() -> removeHeader("Cache-Control")
        else -> header("Cache-Control", value)
      }
    }

   //各种请求方式
    open fun get() = method("GET", null)
    open fun post(body: RequestBody) = method("POST", body)
  //...
 //构建Request
    open fun build(): Request {
      return Request(
          checkNotNull(url) { "url == null" },
          method,
          headers.build(),
          body,
          tags.toImmutableMap()
      )
    }
  }

可以看到在构造方法内Okhttp存在默认“GET”请求方式,所以这个可以忽略不写。

步骤三:通过OkHttpClient和Request构造Call对象

  val call = mOk.newCall(request)
  /** Prepares the [request] to be executed at some point in the future. */
  override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)

通过上面两段代码看到,okhttp的整个网络请求处理都是通过RealCall来进行的,是OkHttp应用程序和网络层之间的桥梁。看RealCall的关键代码

class RealCall(
  val client: OkHttpClient,
  /** The application's original request unadulterated by redirects or auth headers. */
  val originalRequest: Request,
  val forWebSocket: Boolean
) : Call {
//连接池
  private val connectionPool: RealConnectionPool = client.connectionPool.delegate
//监听http通信
  private val eventListener: EventListener = client.eventListenerFactory.create(this)
//超时
  private val timeout = object : AsyncTimeout() {
    override fun timedOut() {
      cancel()
    }
  }.apply {
    timeout(client.callTimeoutMillis.toLong(), MILLISECONDS)
  }
//同步请求
  override fun execute(): Response {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    timeout.enter()
    callStart()
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }
//异步请求
  override fun enqueue(responseCallback: Callback) {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

//完整的拦截器堆栈
  @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
    )
//...
拦截链的调用
   val response = chain.proceed(originalRequest)
//...
  }

RealCall类中对网络的连接、请求、响应进行了处理,并且建立了完整的拦截器链是重要组成部分,拦截链的调用

/**
 *承载整个拦截器链的具体拦截器链:所有应用程序
 *拦截器、OkHttp核心、所有网络拦截器,最后是网络调用者。
 */
  @Throws(IOException::class)
  override fun proceed(request: Request): Response {
   //检查机制
    check(index < interceptors.size)

    calls++

    if (exchange != null) {
      check(exchange.finder.sameHostAndPort(request.url)) {
        "network interceptor ${interceptors[index - 1]} must retain the same host and port"
      }
      check(calls == 1) {
        "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
      }
    }

    // 调用链中的下一个拦截器
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]

    @Suppress("USELESS_ELVIS")
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")

    if (exchange != null) {
      check(index + 1 >= interceptors.size || next.calls == 1) {
        "network interceptor $interceptor must call proceed() exactly once"
      }
    }

    check(response.body != null) { "interceptor $interceptor returned a response with no body" }

    return response
  }

这块儿代码说明了拦截器的调用是根据添加到 interceptors 集合的顺序,逐个往下调用拦截器(关于各个拦截器的具体作用,嗯~ o( ̄▽ ̄)o,随后再说)

最后一步发起请求

发动网络请求会调RealCall类中的同步 execute() or异步方法 enqueue(responseCallback: Callback) 也就是

    call.enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                Log.e(TAG, "请求成功")
            }

            override fun onFailure(call: Call, e: IOException) {
                Log.e(TAG, "请求失败")
            }
        })

至此,最简单的okhttp网络请求流程结束

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

推荐阅读更多精彩内容