首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐
2025年5月24日 星期六 7:06pm

Android OkHttp使用和源码详解

  • 25-03-02 14:02
  • 3275
  • 13404
blog.csdn.net

目录

介绍

要求

网络请求流程分析

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不太熟怕理解错了,误导人民群众。

  1. dependencies {
  2.     //本文使用
  3.     implementation 'com.squareup.okio:okio:1.15.0'
  4.     implementation 'com.squareup.okhttp3:okhttp:3.14.2'
  5.     
  6.     //高版本
  7.     // define a BOM and its version
  8.     implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.0"))
  9.     // define any required OkHttp artifacts without version
  10.     implementation("com.squareup.okhttp3:okhttp")
  11.     implementation("com.squareup.okhttp3:logging-interceptor")
  12. }

网络请求流程分析

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

        先看下 OkHttp 的基本使用:

  1. public void okHttp(String url){
  2.         //创建OkHttpClient对象
  3.         OkHttpClient client = new OkHttpClient();
  4.         //创建Request
  5.         Request request = new Request.Builder()
  6.                 .url(url)
  7.                 .build();
  8.         //创建Call对象client.newCall(request)
  9.         //通过execute()方法获得请求响应的Response对象
  10.         client.newCall(request).enqueue(new Callback() {
  11.             @Override
  12.             public void onFailure(Call call, IOException e) {}
  13.             @Override
  14.             public void onResponse(Call call, Response response) throws IOException {
  15.                 if(response.isSuccessful()){
  16.                     String result = response.body().string();
  17.                     //处理UI需要切换到UI线程处理
  18.                 }
  19.             }
  20.         });
  21.     }

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

  1.     public void buildHttp(String url){
  2.         OkHttpClient.Builder builder = new OkHttpClient.Builder();
  3.         builder.connectTimeout(15, TimeUnit.SECONDS)//设置超时
  4.                 .addInterceptor(interceptor)    //拦截器
  5.                 .proxy(proxy)       //设置代理
  6.                 .cache(cache);      //设置缓存
  7.         Request request = new Request.Builder()
  8.                 .url(url)
  9.                 .build();
  10.         builder.build().newCall(request).enqueue(new Callback() {
  11.             @Override
  12.             public void onFailure(Call call, IOException e) {}
  13.             @Override
  14.             public void onResponse(Call call, Response response) throws IOException {}
  15.         });
  16.     }

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

OkHttpClient.newCall

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

        RealCall.newRealCall.java 

  1.   static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  2.     // Safely publish the Call instance to the EventListener.
  3.     RealCall call = new RealCall(client, originalRequest, forWebSocket);
  4.     call.transmitter = new Transmitter(client, call);
  5.     return call;
  6.   }

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

RealCall.enqueue

  1.   @Override public void enqueue(Callback responseCallback) {
  2.     synchronized (this) {
  3.       if (executed) throw new IllegalStateException("Already Executed");
  4.       executed = true;
  5.     }
  6.     transmitter.callStart();
  7.     client.dispatcher().enqueue(new AsyncCall(responseCallback));
  8.   }

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

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

Dispatcher.enqueue

  1.   void enqueue(AsyncCall call) {
  2.     synchronized (this) {
  3.       readyAsyncCalls.add(call);
  4.       if (!call.get().forWebSocket) {
  5.         AsyncCall existingCall = findExistingCallWithHost(call.host());
  6.         if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
  7.       }
  8.     }
  9.     promoteAndExecute();
  10.   }

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

NamedRunnable

  1. /**
  2.  * Runnable implementation which always sets its thread name.
  3.  */
  4. public abstract class NamedRunnable implements Runnable {
  5.   protected final String name;
  6.   public NamedRunnable(String format, Object... args) {
  7.     this.name = Util.format(format, args);
  8.   }
  9.   @Override public final void run() {
  10.     String oldName = Thread.currentThread().getName();
  11.     Thread.currentThread().setName(name);
  12.     try {
  13.       execute();
  14.     } finally {
  15.       Thread.currentThread().setName(oldName);
  16.     }
  17.   }
  18.   protected abstract void execute();
  19. }

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

AsyncCall.execute

  1. @Override protected void execute() {
  2.       boolean signalledCallback = false;
  3.       transmitter.timeoutEnter();
  4.       try {
  5.         Response response = getResponseWithInterceptorChain();
  6.         signalledCallback = true;
  7.         responseCallback.onResponse(RealCall.this, response);
  8.       } catch (IOException e) {
  9.         if (signalledCallback) {
  10.           // Do not signal the callback twice!
  11.           Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
  12.         } else {
  13.           responseCallback.onFailure(RealCall.this, e);
  14.         }
  15.       } finally {
  16.         client.dispatcher().finished(this);
  17.       }
  18.     }

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

RealCall.getResponseWithInterceptorChain

  1.   Response getResponseWithInterceptorChain() throws IOException {
  2.     // Build a full stack of interceptors.
  3.     List interceptors = new ArrayList<>();
  4.     interceptors.addAll(client.interceptors());
  5.     interceptors.add(new RetryAndFollowUpInterceptor(client));
  6.     interceptors.add(new BridgeInterceptor(client.cookieJar()));
  7.     interceptors.add(new CacheInterceptor(client.internalCache()));
  8.     interceptors.add(new ConnectInterceptor(client));
  9.     if (!forWebSocket) {
  10.       interceptors.addAll(client.networkInterceptors());
  11.     }
  12.     interceptors.add(new CallServerInterceptor(forWebSocket));
  13.     Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
  14.         originalRequest, this, client.connectTimeoutMillis(),
  15.         client.readTimeoutMillis(), client.writeTimeoutMillis());
  16.     boolean calledNoMoreExchanges = false;
  17.     try {
  18.       Response response = chain.proceed(originalRequest);
  19.       if (transmitter.isCanceled()) {
  20.         closeQuietly(response);
  21.         throw new IOException("Canceled");
  22.       }
  23.       return response;
  24.     } catch (IOException e) {
  25.       calledNoMoreExchanges = true;
  26.       throw transmitter.noMoreExchanges(e);
  27.     } finally {
  28.       if (!calledNoMoreExchanges) {
  29.         transmitter.noMoreExchanges(null);
  30.       }
  31.     }
  32.   }

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

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

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

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

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

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

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

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

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

CacheInterceptor 缓存拦截器

        CacheInterceptor 主要做以下几件事情:

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

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

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

CacheInterceptor.intercept

  1. @Override public Response intercept(Chain chain) throws IOException {
  2.   //根据 Request 获取当前已有缓存的 Response(有可能为 null),并根据获取到的缓存 Response
  3.   Response cacheCandidate = cache != null
  4.       ? cache.get(chain.request())
  5.        : null;
  6.   //获取当前时间
  7.   long now = System.currentTimeMillis();
  8.   //创建 CacheStrategy 对象
  9.   //通过 CacheStrategy 来判断缓存是否有效
  10.   CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
  11.   Request networkRequest = strategy.networkRequest;
  12.   Response cacheResponse = strategy.cacheResponse;
  13.   if (cache != null) {
  14.     cache.trackResponse(strategy);
  15.   }
  16.   if (cacheCandidate != null && cacheResponse == null) {
  17.     closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
  18.   }
  19.   //如果我们被禁止使用网络,并且缓存不足,则失败。返回空相应(Util.EMPTY_RESPONSE)
  20.   if (networkRequest == null && cacheResponse == null) {
  21.     return new Response.Builder()
  22.         .request(chain.request())
  23.         .protocol(Protocol.HTTP_1_1)
  24.         .code(504)
  25.         .message("Unsatisfiable Request (only-if-cached)")
  26.         .body(Util.EMPTY_RESPONSE)
  27.           .sentRequestAtMillis(-1L)
  28.         .receivedResponseAtMillis(System.currentTimeMillis())
  29.         .build();
  30.   }
  31.   // 如果缓存有效,缓存 Response 可用则直接返回
  32.   if (networkRequest == null) {
  33.     return cacheResponse.newBuilder()
  34.         .cacheResponse(stripBody(cacheResponse))
  35.         .build();
  36.   }
  37.   //没有缓存或者缓存失败,则发送网络请求从服务器获取Response
  38.   Response networkResponse = null;
  39.   try {
  40.     //执行下一个拦截器,networkRequest
  41.     //发起网络请求
  42.     networkResponse = chain.proceed(networkRequest);
  43.   } finally {
  44.     //如果我们在I/O或其他方面崩溃,请不要泄漏cache body。
  45.     if (networkResponse == null && cacheCandidate != null) {
  46.       closeQuietly(cacheCandidate.body());
  47.     }
  48.   }
  49.   。。。
  50.   //通过网络获取最新的Response
  51.   Response response = networkResponse.newBuilder()
  52.       .cacheResponse(stripBody(cacheResponse))
  53.       .networkResponse(stripBody(networkResponse))
  54.       .build();
  55.   //如果开发人员有设置自定义cache,则将最新response缓存
  56.   if (cache != null) {
  57.     if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
  58.       // Offer this request to the cache.
  59.       CacheRequest cacheRequest = cache.put(response);
  60.       return cacheWritingResponse(cacheRequest, response);
  61.     }
  62.     //返回response(缓存或网络)
  63.     return response;
  64.   }

通过 Cache 实现缓存功能

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

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

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

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

  1.  /**
  2.    * Create a cache of at most {@code maxSize} bytes in {@code directory}.
  3.    */
  4.   public Cache(File directory, long maxSize) {
  5.     this(directory, maxSize, FileSystem.SYSTEM);
  6.   }
  7.   Cache(File directory, long maxSize, FileSystem fileSystem) {
  8.     this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
  9.   }

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

 

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

CallServerInterceptor 详解

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

CallServerInterceptor.intercept

  1.   @Override public Response intercept(Chain chain) throws IOException {
  2.     //获取RealInterceptorChain
  3.     RealInterceptorChain realChain = (RealInterceptorChain) chain;
  4.     //获取Exchange
  5.     Exchange exchange = realChain.exchange();
  6.     Request request = realChain.request();
  7.     long sentRequestMillis = System.currentTimeMillis();
  8.     exchange.writeRequestHeaders(request);
  9.     boolean responseHeadersStarted = false;
  10.     Response.Builder responseBuilder = null;
  11.     if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
  12.       if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
  13.         exchange.flushRequest();
  14.         responseHeadersStarted = true;
  15.         exchange.responseHeadersStart();
  16.         responseBuilder = exchange.readResponseHeaders(true);
  17.       }
  18.       if (responseBuilder == null) {
  19.         if (request.body().isDuplex()) {
  20.           exchange.flushRequest();
  21.           BufferedSink bufferedRequestBody = Okio.buffer(
  22.               exchange.createRequestBody(request, true));
  23.           request.body().writeTo(bufferedRequestBody);
  24.         } else {
  25.           // Write the request body if the "Expect: 100-continue" expectation was met.
  26.           BufferedSink bufferedRequestBody = Okio.buffer(
  27.               exchange.createRequestBody(request, false));
  28.           request.body().writeTo(bufferedRequestBody);
  29.           bufferedRequestBody.close();
  30.         }
  31.       } else {
  32.         exchange.noRequestBody();
  33.         if (!exchange.connection().isMultiplexed()) {
  34.           // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
  35.           // from being reused. Otherwise we're still obligated to transmit the request body to
  36.           // leave the connection in a consistent state.
  37.           exchange.noNewExchangesOnConnection();
  38.         }
  39.       }
  40.     } else {
  41.       exchange.noRequestBody();
  42.     }
  43.     if (request.body() == null || !request.body().isDuplex()) {
  44.       exchange.finishRequest();
  45.     }
  46.     上面是向服务器端发送请求数据
  47.     
  48.     -----强大的分割线----------
  49.     
  50.     下面是从服务端获取相应数据
  51.     并构建 Response 对象
  52.     
  53.     if (!responseHeadersStarted) {
  54.       exchange.responseHeadersStart();
  55.     }
  56.     if (responseBuilder == null) {
  57.       responseBuilder = exchange.readResponseHeaders(false);
  58.     }
  59.     Response response = responseBuilder
  60.         .request(request)
  61.         .handshake(exchange.connection().handshake())
  62.         .sentRequestAtMillis(sentRequestMillis)
  63.         .receivedResponseAtMillis(System.currentTimeMillis())
  64.         .build();
  65.     int code = response.code();
  66.     if (code == 100) {
  67.       // server sent a 100-continue even though we did not request one.
  68.       // try again to read the actual response
  69.       response = exchange.readResponseHeaders(false)
  70.           .request(request)
  71.           .handshake(exchange.connection().handshake())
  72.           .sentRequestAtMillis(sentRequestMillis)
  73.           .receivedResponseAtMillis(System.currentTimeMillis())
  74.           .build();
  75.       code = response.code();
  76.     }
  77.     exchange.responseHeadersEnd(response);
  78.     if (forWebSocket && code == 101) {
  79.       // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
  80.       response = response.newBuilder()
  81.           .body(Util.EMPTY_RESPONSE)
  82.           .build();
  83.     } else {
  84.       response = response.newBuilder()
  85.           .body(exchange.openResponseBody(response))
  86.           .build();
  87.     }
  88.     。。。
  89.     return response;
  90.   }

小结

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

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

往期回顾 

RecyclerView 绘制流程及Recycler缓存

Glide 缓存机制及源码(二)

Glide 的简单使用(一)

注:本文转载自blog.csdn.net的帅次的文章"https://shuaici.blog.csdn.net/article/details/120174041"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top