网络框架1——OkHttp网络框架

344 阅读6分钟

一、OkHttp简介

OkHttp一个处理网络请求的开源项目,是安卓端最火热的轻量级框架,由移动支付Square公司贡献(该公司还贡献了Picasso)

二、 OKHttp优点

  1. 支持get请求和post请求
  2. 支持基于Http的文件上传和下载
  3. 支持加载图片
  4. 支持GZIP压缩
  5. 支持响应缓存避免重复的网络请求
  6. 支持使用连接池来降低响应延迟问题。

三、 OkHttp使用简介

  1. 创建一个OkHttpClient对象
  2. 创建一个request对象,通过内部类Builder调用生成Request对象
  3. 创建一个Call对象,调用execute(同步请求)/enqueue(异步请求)

示例代码 1. execute(同步get请求)

public void getSyncRequest(View view) {
        // 1. 拿到OkHttpClient对象
        OkHttpClient client = new OkHttpClient();
        // 2. 构造Request对象
        final Request request = new Request.Builder()
                .get()
                .url("https:www.baidu.com")
                .build();
        // 3.Request封装为Call
        final Call call = client.newCall(request);
        // 4. 根据需要调用同步请求方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 同步调用,返回Response,会抛出IO异常
                    Response response = call.execute();
                    Log.d(TAG, "response.code() = " + response.code() +
                            ", response.message() = " + response.message() +
                            ", response.body() = " + response.body().string() +
                            "");
                } catch (IOException e) {
                    Log.d(TAG, "" + e +
                            "");
                    e.printStackTrace();
                }
            }
        }).start();
    }

2. enqueue(异步get请求)

public void getAsyncRequest(View view) {
        // 1. 拿到OkHttpClient对象
        OkHttpClient client = new OkHttpClient();
        // 2. 构造Request对象
        final Request request = new Request.Builder()
                .get()
                .url("https:www.baidu.com")
                .build();
        // 3. 将Request封装为Call
        final Call call = client.newCall(request);
        // 4. 根据需要调用异步请求方法
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Log.d(TAG, "get failed" +
                        "");
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                final String res = response.body().string();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText(res);
                    }
                });
            }
        });
    }

post请求

public void postFormRequest(View view) {
        // 1. 拿到OkHttpClient对象
        OkHttpClient client = new OkHttpClient();
        // 2. 构建FormBody,传入参数
        FormBody formBody = new FormBody.Builder()
                .add("username", "admin")
                .add("username", "admin")
                .build();
        // 3. 构建Request,将FormBody作为Post方法的参数传入
        final Request request = new Request.Builder()
                .url("http://www.jianshu.com")
                .post(formBody)
                .build();
        // 4. 将Request封装为Call
        Call call = client.newCall(request);
        // 5. 调用请求,重写回调方法
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                Log.d(TAG, "get failed" +
                        ", e.getMessage() = " + e.getMessage() +
                        ", e = " + e +
                        "");
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                final String res = response.body().string();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText(res);
                    }
                });
            }
        });

    }

四、 OkHttp源码分析(kotlin)

OkHttpClient算是执行调用请求Call的工厂,这个工厂将会被用来发送Http请求和读取他们的返回,这里强调OkHttp的使用最好创建一个单例OkHttpClient实例,并且重复使用。这是因为每一个Client都有自己的一个连接池connection pool和线程池thread pools。重用这些连接池和线程池可以减少延迟和节约内存。

		// 1. 拿到OkHttpClient对象
        OkHttpClient client = new OkHttpClient();
        // 2. 构造Request对象
        final Request request = new Request.Builder()
                .get()
                .url("https:www.baidu.com")
                .build();
        // 3.Request封装为Call
        final Call call = client.newCall(request);

查看newCall方法

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

RealCall是Call的实现类,RealCall中实现了Execute和enqueue等方法。查看Real方法的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)
    }
  }

同步方法中做了4件事

  1. 检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完全一样的 call,可以利用 call#clone 方法进行克隆。
  2. 利用 client.dispatcher().executed(this) 来进行实际执行,dispatcher 是刚才看到的OkHttpClient.Builder 的成员之一,它的文档说自己是异步 HTTP 请求的执行策略,现在看来,同步请求它也有掺和。
  3. 调用 getResponseWithInterceptorChain() 函数获取 HTTP 返回结果,从函数名可以看出,这一步还会进行一系列“拦截”操作。
  4. 最后还要通知 dispatcher 自己已经执行完毕。

这里同步请求,只是把当前请求添加到队列而已

  /** Used by [Call.execute] to signal it is in-flight. */
  @Synchronized internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
  }

真正发出网络请求,解析返回结果的,还是 getResponseWithInterceptorChain,这个下面说,最后再调用了dispatch.finish方法

接下来看下异步请求RealCall.enqueue

  override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

先判断当前Call是否在执行,再调用dispatch.enqueue方法

  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      readyAsyncCalls.add(call)

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }

调用在promoteAndExecute方法中

  private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      while (i.hasNext()) {
        val asyncCall = i.next()

        if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.

        i.remove()
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

,在promoteAndExecute方法中,方法中,这里先有个判断,如果当前运行的异步请求队列长度小于最大请求数maxRequests,也就是64,并且主机的请求数小于每个主机的请求数maxRequestsPerHost也就是5,则把当前请求添加到 运行队列,接着交给线程池ExecutorService处理,否则则放置到readAsyncCall进行缓存,等待执行。 可以看到同步与异步一点区别就是,异步的执行交给了线程池去操作。

我们看下OkHttp里面的线程池ExecutorService

  @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!!
    }

这里则是通过ThreadPoolExecutor来创建线程池

dispatch.enqueue方法d参数AsyncCall,AsyncCall继承于NamedRunnable,而NamaedRunnable则实现了Runnable方法

    /**
     * Attempt to enqueue this async call on [executorService]. This will attempt to clean up
     * if the executor has been shut down by reporting the call as failed.
     */
    fun executeOn(executorService: ExecutorService) {
      client.dispatcher.assertThreadDoesntHoldLock()

      var success = false
      try {
        executorService.execute(this)
        success = true
      } catch (e: RejectedExecutionException) {
        val ioException = InterruptedIOException("executor rejected")
        ioException.initCause(e)
        noMoreExchanges(ioException)
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        if (!success) {
          client.dispatcher.finished(this) // This call is no longer running!
        }
      }
    }

    override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        timeout.enter()
        try {
          val response = getResponseWithInterceptorChain()
          signalledCallback = true
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
          if (signalledCallback) {
            // Do not signal the callback twice!
            Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
          } else {
            responseCallback.onFailure(this@RealCall, e)
          }
        } catch (t: Throwable) {
          cancel()
          if (!signalledCallback) {
            val canceledException = IOException("canceled due to $t")
            canceledException.addSuppressed(t)
            responseCallback.onFailure(this@RealCall, canceledException)
          }
          throw t
        } finally {
          client.dispatcher.finished(this)
        }
      }
    }
  }

可以看到在它的构造器中,Callback就是我们设置的创建的,带有onFail和onResponse方法。 而线程池中最终调用到的则是我们的Runnable。

这里通过

val response = getResponseWithInterceptorChain()

方法来进行连接访问,这里跟同步请求一样。最后根据返回值调用callback.onFailure/onResponse

我们关键还是看OkHttp如何连接返回的,我们看下这个方法

  @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拦截器的使用

    1. RetryAndFollowUpInterceptor : 负责失败重试以及重定向,创建StreamAllocation对象,处理http的重定向,出错重试。对后续Interceptor的执行的影响:修改request及StreamAllocation。
    1. BridgeInterceptor:负责把用户构造的请求转换为发送到服务器的请求把服务器返回的响应转换为用户友好的响应。补全缺失的一些http header,Cookie设置。对后续Interceptor的执行的影响:修改request。
    1. CacheInterceptor:负责读取缓存直接返回、更新缓存。处理http缓存。对后续Interceptor的执行的影响:若缓存中有所需请求的响应,则后续Interceptor不再执行。
    1. ConnectInterceptor:负责和服务器建立连接。借助于前面分配的StreamAllocation对象建立与服务器之间的连接(具体建立是在newStream方法中),并选定交互所用的协议是HTTP 1.1还是HTTP 2。对后续Interceptor的执行的影响:创建了httpStream和connection。
    1. NetworkInterceptors:配置 OkHttpClient
    1. CallServerInterceptor:负责向服务器发送请求数据从服务器读取响应数据。处理IO,与服务器进行数据交换。对后续Interceptor的执行的影响:为Interceptor链中的最后一个Interceptor,没有后续Interceptor。

[责任链模式]在这个 Interceptor 链条中得到了很好的实践

image.png

项目代码

参考资料:
OkHttp使用详解
OkHttp解析(一)从用法看清原理 # 面试官:听说你熟悉OkHttp原理?