Android OkHttp使用和源码详解

目录

介绍

要求

网络请求流程分析

OkHttpClient.newCall

RealCall.enqueue

Dispatcher.enqueue

NamedRunnable

AsyncCall.execute

RealCall.getResponseWithInterceptorChain

CacheInterceptor 缓存拦截器

CacheInterceptor.intercept

通过 Cache 实现缓存功能

CallServerInterceptor 详解

CallServerInterceptor.intercept

小结

往期回顾 

RecyclerView 绘制流程及Recycler缓存

Glide 缓存机制及源码(二)

Glide 的简单使用(一)


介绍

        OkHttp 是一套处理 HTTP 网络请求的依赖库,由 Square 公司设计研发并开源,目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说,OkHttp 现在几乎已经占据了所有的网络请求操作,RetroFit + OkHttp 实现网络请求似乎成了一种标配。因此它也是每一个 Android 开发工程师的必备技能,了解其内部实现原理可以更好地进行功能扩展、封装以及优化。

        适用于 Android 和 Java 应用程序的 HTTP 和 HTTP/2 客户端。

        OkHttp的4.0.x版本已经全部由java替换到了Kotlin,API的一些使用也会有些不同。

        Github传送门

        文档和 API

要求

支持的版本

        4.0.x :Android 5.0+(API 级别 21+)和 Java 8+。

        3.12.x :Android 2.3+(API 级别 9+)和 Java 7+。平台可能不支持 TLSv1.2。(2021-12-31不再支持)

        OkHttp有一个库的依赖Okio,用于高性能I/O一个小型library。它适用于 Okio 1.x(用 Java 实现)或 Okio 2.x(升级到 Kotlin)。

                本文使用的OkHttp的版本为3.14.2,不是不会接入高版本,主要是4.0.x版本已经全部由java替换到了Kotlin,Kotlin不太熟怕理解错了,误导人民群众。

dependencies {
    //本文使用
    implementation 'com.squareup.okio:okio:1.15.0'
    implementation 'com.squareup.okhttp3:okhttp:3.14.2'
    
    //高版本
    // define a BOM and its version
    implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.0"))

    // define any required OkHttp artifacts without version
    implementation("com.squareup.okhttp3:okhttp")
    implementation("com.squareup.okhttp3:logging-interceptor")
}


网络请求流程分析

        OkHttp 经过几次迭代后,已经发生了很多变化。更好的 WebSocket 支持、更多的 Interceptor 责任链,甚至连最核心的 HttpEngine 也变成了 HttpCodec。本文会重新梳理整个网络请求的流程,以及实现机制。

        先看下 OkHttp 的基本使用:

public void okHttp(String url){
        //创建OkHttpClient对象
        OkHttpClient client = new OkHttpClient();
        //创建Request
        Request request = new Request.Builder()
                .url(url)
                .build();
        //创建Call对象client.newCall(request)
        //通过execute()方法获得请求响应的Response对象
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {}

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if(response.isSuccessful()){
                    String result = response.body().string();
                    //处理UI需要切换到UI线程处理
                }
            }
        });
    }

        除了直接 new OkHttpClient 之外,还可以使用内部工厂类 Builder 来设置 OkHttpClient。如下所示:

    public void buildHttp(String url){
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(15, TimeUnit.SECONDS)//设置超时
                .addInterceptor(interceptor)    //拦截器
                .proxy(proxy)       //设置代理
                .cache(cache);      //设置缓存
        Request request = new Request.Builder()
                .url(url)
                .build();
        builder.build().newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {}

            @Override
            public void onResponse(Call call, Response response) throws IOException {}
        });
    }

        请求操作的起点从 OkHttpClient.newCall().enqueue() 方法开始

OkHttpClient.newCall

  @Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }

        RealCall.newRealCall.java 


  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.transmitter = new Transmitter(client, call);
    return call;
  }

        这个方法会返回一个 RealCall 对象,通过它将网络请求操作添加到请求队列中。

RealCall.enqueue

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

        client.dispatcher()返回Dispatcher,调用 Dispatcher 的 enqueue 方法,执行一个异步网络请求的操作。

Dispatcher 是 OkHttpClient 的调度器,是一种门户模式。主要用来实现执行、取消异步请求操作。本质上是内部维护了一个线程池去执行异步操作,并且在 Dispatcher 内部根据一定的策略,保证最大并发个数、同一 host 主机允许执行请求的线程个数等。

Dispatcher.enqueue

  void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    promoteAndExecute();
  }

        实际上就是使用线程池执行了一个 AsyncCall,而 AsyncCall 继承了 NamedRunnable,NamedRunnable 实现了 Runnable 接口,因此整个操作会在一个子线程(非 UI 线程)中执行。

NamedRunnable

/**
 * Runnable implementation which always sets its thread name.
 */
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();
}

        在 run 方法中执行了 一个抽象方法 execute 这个抽象方法被 AsyncCall 实现。

AsyncCall.execute

@Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
        Response response = getResponseWithInterceptorChain();
        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 {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }

        从上面看出而真正获取请求结果的方法是在 getResponseWithInterceptorChain 方法中,从名字也能看出其内部是一个拦截器的调用链。

RealCall.getResponseWithInterceptorChain

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

        Interceptor:拦截器是一种强大的机制,可以监视、重写和重试调用。

每一个拦截器的作用如下:

  • BridgeInterceptor:主要对 Request 中的 Head 设置默认值,比如 Content-Type、Keep-Alive、Cookie 等。

  • CacheInterceptor:负责 HTTP 请求的缓存处理。

  • ConnectInterceptor:负责建立与服务器地址之间的连接,也就是 TCP 链接。

  • CallServerInterceptor:负责向服务器发送请求,并从服务器拿到远端数据结果。

  • RetryAndFollowUpInterceptor:此拦截器从故障中恢复,并根据需要执行重定向。如果呼叫被取消,它可能会引发IOException。

在添加上述几个拦截器之前,会调用 client.interceptors 将开发人员设置的拦截器添加到列表当中。

        对于 Request 的 Head 以及 TCP 链接,我们能控制修改的成分不是很多。所以咱们了解 CacheInterceptorCallServerInterceptor

CacheInterceptor 缓存拦截器

        CacheInterceptor 主要做以下几件事情:

        1、根据 Request 获取当前已有缓存的 Response(有可能为 null),并根据获取到的缓存 Response,创建 CacheStrategy 对象。

        2、 通过 CacheStrategy 判断当前缓存中的 Response 是否有效(比如是否过期),如果缓存 Response 可用则直接返回,否则调用 chain.proceed() 继续执行下一个拦截器,也就是发送网络请求从服务器获取远端 Response。

         3、如果从服务器端成功获取 Response,再判断是否将此 Response 进行缓存操作。

CacheInterceptor.intercept

@Override public Response intercept(Chain chain) throws IOException {
  //根据 Request 获取当前已有缓存的 Response(有可能为 null),并根据获取到的缓存 Response
  Response cacheCandidate = cache != null
      ? cache.get(chain.request())
       : null;
  //获取当前时间
  long now = System.currentTimeMillis();
  //创建 CacheStrategy 对象
  //通过 CacheStrategy 来判断缓存是否有效
  CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
  Request networkRequest = strategy.networkRequest;
  Response cacheResponse = strategy.cacheResponse;
  if (cache != null) {
    cache.trackResponse(strategy);
  }
  if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
  }
  //如果我们被禁止使用网络,并且缓存不足,则失败。返回空相应(Util.EMPTY_RESPONSE)
  if (networkRequest == null && cacheResponse == null) {
    return new Response.Builder()
        .request(chain.request())
        .protocol(Protocol.HTTP_1_1)
        .code(504)
        .message("Unsatisfiable Request (only-if-cached)")
        .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();
  }

  // 如果缓存有效,缓存 Response 可用则直接返回
  if (networkRequest == null) {
    return cacheResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .build();
  }
  //没有缓存或者缓存失败,则发送网络请求从服务器获取Response
  Response networkResponse = null;
  try {
    //执行下一个拦截器,networkRequest
    //发起网络请求
    networkResponse = chain.proceed(networkRequest);
  } finally {
    //如果我们在I/O或其他方面崩溃,请不要泄漏cache body。
    if (networkResponse == null && cacheCandidate != null) {
      closeQuietly(cacheCandidate.body());
    }
  }
  。。。
  //通过网络获取最新的Response
  Response response = networkResponse.newBuilder()
      .cacheResponse(stripBody(cacheResponse))
      .networkResponse(stripBody(networkResponse))
      .build();
  //如果开发人员有设置自定义cache,则将最新response缓存
  if (cache != null) {
    if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
      // Offer this request to the cache.
      CacheRequest cacheRequest = cache.put(response);
      return cacheWritingResponse(cacheRequest, response);
    }
    //返回response(缓存或网络)
    return response;
  }

通过 Cache 实现缓存功能

        通过上面缓存拦截器的流程可以看出,OkHttp 只是规范了一套缓存策略,但是具体使用何种方式将数据缓存到本地,以及如何从本地缓存中取出数据,都是由开发人员自己定义并实现,并通过 OkHttpClient.Builder 的 cache 方法设置。

        OkHttp 提供了一个默认的缓存类 Cache.java,我们可以在构建 OkHttpClient 时,直接使用 Cache 来实现缓存功能。只需要指定缓存的路径,以及最大可用空间即可,如下所示:

OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.connectTimeout(15, TimeUnit.SECONDS)//设置超时
            拦截器
            .addInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    return null;
                }
            })
            //设置代理
            .proxy(new Proxy(Proxy.Type.HTTP,null)) 
            //设置缓存
            //AppGlobalUtils.getApplication() 通过反射得到Application实例
            //getCacheDir内置 cache 目录作为缓存路径
            //maxSize 10*1024*1024 设置最大缓存10MB
            .cache(new Cache(AppGlobalUtils.getApplication().getCacheDir(),
                        10*1024*1024));

Cache 内部使用了 DiskLruCach 来实现具体的缓存功能,如下所示:

 /**
   * Create a cache of at most {@code maxSize} bytes in {@code directory}.
   */
  public Cache(File directory, long maxSize) {
    this(directory, maxSize, FileSystem.SYSTEM);
  }

  Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
  }

        DiskLruCache 最终会将需要缓存的数据保存在本地。如果感觉 OkHttp 自带的这套缓存策略太过复杂,我们可以设置使用 DiskLruCache 自己实现缓存机制。

 

LRU:是近期最少使用的算法(缓存淘汰算法),它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有两种:LrhCache和DisLruCache,分别用于实现内存缓存和硬盘缓存,其核心思想都是LRU缓存算法。

CallServerInterceptor 详解

        CallServerInterceptor 是 OkHttp 中最后一个拦截器,也是 OkHttp 中最核心的网路请求部分。

CallServerInterceptor.intercept

  @Override public Response intercept(Chain chain) throws IOException {
    //获取RealInterceptorChain
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    //获取Exchange
    Exchange exchange = realChain.exchange();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();

    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        exchange.flushRequest();
        responseHeadersStarted = true;
        exchange.responseHeadersStart();
        responseBuilder = exchange.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        if (request.body().isDuplex()) {
          exchange.flushRequest();
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, true));
          request.body().writeTo(bufferedRequestBody);
        } else {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          BufferedSink bufferedRequestBody = Okio.buffer(
              exchange.createRequestBody(request, false));
          request.body().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 (request.body() == null || !request.body().isDuplex()) {
      exchange.finishRequest();
    }

    上面是向服务器端发送请求数据
    
    -----强大的分割线----------
    
    下面是从服务端获取相应数据
    并构建 Response 对象
    
    if (!responseHeadersStarted) {
      exchange.responseHeadersStart();
    }

    if (responseBuilder == null) {
      responseBuilder = exchange.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int 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
      response = exchange.readResponseHeaders(false)
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

      code = response.code();
    }

    exchange.responseHeadersEnd(response);

    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build();
    }
    。。。
    return response;
  }

小结

        首先 OkHttp 内部是一个门户模式,所有的下发工作都是通过一个门户 Dispatcher 来进行分发。

        然后在网络请求阶段通过责任链模式,链式的调用各个拦截器的 intercept 方法。重点介绍了 2 个比较重要的拦截器:CacheInterceptor 和 CallServerInterceptor。它们分别用来做请求缓存和执行网络请求操作。

往期回顾 

RecyclerView 绘制流程及Recycler缓存

Glide 缓存机制及源码(二)

Glide 的简单使用(一)

评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帅次

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值