OKHttp原理讲解之基本概念

前言:

1.1为什么要写这篇文章?

OKHttp是square出品的开源通信框架,目前市面上大多数的APP都使用了该框架,那为什么这么多的APP都选择使用该框架呢?有的人说别人都用我也就用呗,那肯定不是的。本文就带你了解为什么我们要使用OKHttp,以及OKHttp的原理。

1.2 OKHttp项目地址:

https://github.com/square/okhttp

1.3 系列简介:

OKHttp项目本身还是很大的,而且涉及到的知识点很多,所以一篇文章是很难概括全的,所以准备写一个系列的文章来详细讲解OKHttp。

准备写以下几篇。

OKHttp原理讲解之基本概念(预计03.15发布)

OKHttp原理讲解之责任链模式及扩展(预计03.18发布)

OKHttp原理讲解之重试拦截器(预计03.22发布)

OKHttp原理讲解之路由拦截器

OKHttp原理讲解之缓存拦截器

OKHttp原理讲解之连接池拦截器

OKHttp原理讲解之请求拦截器

PS:预计每周更新1-2篇的进度进行

1.4 OKHttp系列第一篇

本篇主要讲解以下部分:

1.OKHttp的简单使用

2.OKHttp的几个主要概念

3.OKHttp发送请求的基本流程

4.调度器中线程的管理

5.为什么选择使用OKHttp

OKHttp的几个主要概念

PS:以下几个中文名字都是按照我自己的理解取的,不是官方起名。

1.配置中心,OKHttpClient

OKHttp是一个可以高度定制化的框架,它提供了很多可配置的功能项,可以由开发者根据自主选择去配置。比如Dns,Proxy,connectTimeout等等。

而OKHttpClient就是这些配置的一个容器,它使用构造者模式去创建,开发者可以根据自己的需求自主选择。

2.请求体,RealCall

对于每一个请求,OKHttp都封装了了一个RealCall来执行具体请求的流程。所以自然的,五层责任链也是由它来创建和调用的。

3.调度器,Dispatcher

调度器负责每个请求体的具体执行。所以自然的,调度器中包含了一个请求线程池,以及请求队列,执行队列,等待队列等等。 

4.责任链,List<Interceptor>

OKHttp中默认包含五层责任链,分别为

RetryAndFollowUpInterceptor:重试跟进拦截器

BridgeInterceptor:桥接拦截器

CacheInterceptor:缓存拦截器

ConnectInterceptor:连接池拦截器

CallServerInterceptor:请求拦截器

一个请求的完整发送流程,会从上向下依次执行,如果某一个拦截器判断执行完成,则一层一层向上返回最终结果。

所以五层拦截器也是OKHttp的核心,后面会有专门的篇章来一一进行讲解。

5.请求,Request

顾名思义,包含请求所需要的所有信息。

6.响应,Response

顾名思义,包含响应所需要的所有信息。

OKHttp基本使用

这不是本文要讲解的重点,所以只是简单的介绍下使用方式。

2.1配置OKHttpClient

这里我只进行了一个配置,配置了一个缓存目录。

 val builder = OkHttpClient.Builder()
 builder.cache(Cache(File(context?.filesDir?.absolutePath + File.separator + "ok"), 100))
 val client = builder.build()

2.2发送请求,

请求分两种,同步请求和异步请求,同步请求需要在子线程执行。

第一种同步的方式:

        val builder = Request.Builder()
        val request = builder.url("https://www.baidu.com").build()
        val newCall = client.newCall(request)
            service.execute {
                val response = newCall.execute()
            }
        

第二种异步的方式:

            val builder = Request.Builder()
            val request = builder.url("https://www.baidu.com").build()
            val newCall = client.newCall(request)
            newCall.enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    logI(call.toString());
                }

                override fun onResponse(call: Call, response: Response) {
                
                }

            })

2.3 处理返回值

reponse中包含返回的数据流,我们只要序列化返回的data数组即可。

 val content = IOHelper.readStrByCode(response.body()?.byteStream(), "utf-8")
 logI(content)

readStrByCode方法链接,作用是把数组转换为字符串。

https://github.com/aa5279aa/android_all_demo/blob/master/DemoClient/app/src/main/java/com/xt/client/util/IOHelper.java

OKHttp发送主体流程

异步和同步请求其实流程上差不多,只不过同步请求是直接返回reponse,而异步则是通过回调的方式通知。这里就以异步请求为例,讲解整个请求的发送流程。

3.1 构建请求体RealCall

这里构建的RealCall中,主要包含原始的request,以及事件监听。

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

3.2 开始请求流程RealCall.enqueue()

这里主要做两件事,

第一件事,对于观察者进行回调

第二件事,交由调度器,在线程中执行AsyncCall的execute方法。

public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

3.3 执行请求流程execute()

首先调用责任链,去获取最终的响应。

然后进行通知callBack进行对应的回调。

最终通过调度器完整的结束该请求

protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

、调度器中线程的管理

上面讲了由调度器负责把一个一个的请求任务,分发给线程去管理。那么线程是如何管理的呢?

4.1 Dispatcher中的enqueue()方法

4.1.1 方法加锁:

这个方法是加锁的,避免多线程添加任务时,出现冲突。

4.1.2 判断数量是否超标

这里判断了正在执行的请求数量是否大于64,以及单个链接的请求数是否大于5(这里的64和5是可以配置的,单个链接指的是请求接口地址是一样的),如果超过,则排队;否则则可以立即执行。

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

4.1.3 获取并创建线程池

1.判断是否存在线程池

如果不存在,则创建线程池。否则直接返回

这里扩展一个小问题,为什么使用的时候才创建线程池呢?我猜测是为了性能,提早创建了线程池不用浪费性能。

2.创建线程池

这里使用的线程池配置参数,核心线程数为0,最大线程数为MAX_VALUE,非核心线程存活时间为60秒。

队列使用的是SynchronousQueue,适合生产者消费者模型。具体SynchronousQueue可以自行百度,这里就不扩展了。

public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

3.为什么不会出现性能问题

我们这里看到,OKHttp请求线程池数量并没有设置上线,那么不会存在性能问题吗?实际上由于之前已经有了maxRequest的限制,所以线程数量并不会真的达到MAX_VALUE的级别。

所以可以这么理解OK的线程池策略,同时并发每一个请求,那么每一个请求都是一个单独线程去处理。如果一个请求完成后,那么其所占用的线程就可以被释放供后续请求使用。

4.自定义线程池策略

即使已经限制了最大请求数64,对于某些性能较差的手机,有可能真的大并发请求时还是吃力的。所以没关系,OK的一大特色就是可配置性强。没关系,线程池我们可以自定义传入,让OK使用我们自定义的线程池。

我们可以通过Dispatcher的有参构造函数传入自定义线程池。

            val newSingleThreadExecutor = Executors.newSingleThreadExecutor()
            val dispatcher = Dispatcher(newSingleThreadExecutor)
            val builder1 = OkHttpClient.Builder()
            builder1.dispatcher(dispatcher);
            val build = builder.build()

4.1.4 线程池调度调用Call

1.执行AsyncCall

获取到线程池后,就可以交给其最终的call任务,让其去执行。最终会调用到AsyncCall

的execute方法。

 executorService().execute(call);

2.为什么会执行AsyncCall的execute方法

AsyncCall继承自NameRunnable,NameRunnable继承自Runnable。NameRunnable中的实现如下,所以会执行NameRunnable的execute方法。

public abstract class NamedRunnable implements Runnable {
  protected final String name;

  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);
  }

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

  protected abstract void execute();
}

、为什么选择使用OKHttp

常规答案

这里面试经常被问到的一道题,我面试别人的时候也经常问。很常规的答案如下:

1.大家都在用,所以我也就用。

2.大厂出门的,稳定性好,比较放心。

3.使用简单,用起来比较方便。

4.持续维护中,不担心出了问题没人解决

更为合适的答案

但是其实这题面试官更想考验的是对OKHttp的了解程度,所以除了上面那些优势,我们还可以从OKHttp框架的优势的角度去谈一谈:

1.可扩展性高。类似于缓存,Dns,请求/连接/响应超时时间等等都可以通过配置传入,甚至线程池都可以根据自己的需求来配置。

2.OKHttp使用了连接池缓存,提高通信效率。

3.责任链五层拦截器模式,每层功能清晰明了,并且提供了两层可扩展的拦截器方便进行所需要的改造。

4.层次结构清晰,方便进行问题的排查。

5.观察者模式的充分使用,查看请求状态和监控请求状态变得十分简单。

6.使用了OKIO框架进行数据的处理,效率和安全性上更高。

等等,随着文章的不断详细逐渐补充。

猜你喜欢

转载自blog.csdn.net/AA5279AA/article/details/123505544