• okhttp-源码简析


    1 Request req = new Request.Builder()
    2     .cacheControl(new CacheControl.Builder().noCache().build())
    3     .url("http://publicobject.com/helloworld.txt")
    4     .build();//GET
    5 //同步执行
    6 client.newCall(req).execute();
    7 //异步执行
    8 client.newCall(req).enqueue(callback);

    不管同步请求还是异步请求,都需要通过newCall创建一个Call实例

    1 public Call newCall(Request request) {
    2     //client、originalRequest、forWebSocket
    3   return new RealCall(this, request, false /* for web socket */);
    4 }

     

    同步请求

     1 //RealCall的execute方法
     2 public Response execute() throws IOException {
     3   synchronized (this) {
     4       //RealCall只能执行一次
     5     if (executed) throw new IllegalStateException("Already Executed");
     6     executed = true;
     7   }
     8   captureCallStackTrace();
     9   try {
    10       //调用runningSyncCalls.add(call),runningSyncCalls是一个queue,
    11       //finally中的finished会调用runningSyncCalls.remove(call),
    12       //runningSyncCalls的意义在于client可以统一管理同步call(异步call同样有一个queue),
    13       //比如通过runningCalls()得到正在执行的http请求(包括同步、异步),还可以通过cancelAll()取消所有http请求(包括同步、异步)
    14     client.dispatcher().executed(this);
    15     //getResponseWithInterceptorChain是真正的执行逻辑,从方法名可以看出okhttp将执行逻辑分隔成了一个个拦截器,使用责任链来执行。
    16     Response result = getResponseWithInterceptorChain();
    17     if (result == null) throw new IOException("Canceled");
    18     return result;
    19   } finally {
    20       //runningSyncCalls.remove(call)
    21     client.dispatcher().finished(this);
    22   }
    23 }
     1 //getResponseWithInterceptorChain
     2 Response getResponseWithInterceptorChain() throws IOException {
     3   // Build a full stack of interceptors.
     4   List<Interceptor> interceptors = new ArrayList<>();
     5   //自定义的interceptor
     6   interceptors.addAll(client.interceptors());
     7   interceptors.add(retryAndFollowUpInterceptor);
     8   interceptors.add(new BridgeInterceptor(client.cookieJar()));
     9   interceptors.add(new CacheInterceptor(client.internalCache()));
    10   interceptors.add(new ConnectInterceptor(client));
    11   if (!forWebSocket) {
    12     interceptors.addAll(client.networkInterceptors());
    13   }
    14   //拦截链的最后一个拦截器,通常来说最后一个拦截器就是真正的执行逻辑,比如tomcat的filters中最后一个filter其实就是Servlet
    15   interceptors.add(new CallServerInterceptor(forWebSocket));
    16 
    17   Interceptor.Chain chain = new RealInterceptorChain(
    18       interceptors, null, null, null, 0, originalRequest);
    19   return chain.proceed(originalRequest);//执行拦截链
    20 }
    CallServerInterceptor的intercept方法
     1 //CallServerInterceptor的intercept,有删减
     2 public Response intercept(Chain chain) throws IOException {
     3   RealInterceptorChain realChain = (RealInterceptorChain) chain;
     4   HttpCodec httpCodec = realChain.httpStream();
     5   StreamAllocation streamAllocation = realChain.streamAllocation();
     6   RealConnection connection = (RealConnection) realChain.connection();
     7   Request request = realChain.request();
     8     //请求时间
     9   long sentRequestMillis = System.currentTimeMillis();
    10   //内部会调用writeRequest(request.headers(), requestLine);,writeRquest会先写请求行,再遍历请求头并写出。
    11   //这里的“写”操作,只是将字符串写入一个Buffer(后面会提到Buffer),socket还没有write。
    12   httpCodec.writeRequestHeaders(request);
    13      /** 什么是sink(okio.Sink接口)?sink翻译为水槽,可以理解为水的最终汇聚地,相对的有okio.Source,即水的发源地。
    14         * Receives a stream of bytes. Use this interface to write data wherever it's
    15         * needed: to the network, storage, or a buffer in memory. Sinks may be layered
    16         * to transform received data, such as to compress, encrypt, throttle, or add
    17         * protocol framing.
    18         */
    19   Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
    20   //将sink包装一层缓冲区
    21   BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    22   //将body字节序列全部写入bufferedRequestBody,注意是全部,这里就有可能出现OOM。
    23   //此时body只是写入了buffer,还未写入socket。
    24   request.body().writeTo(bufferedRequestBody);
    25   //close时会将buffer中的数据写入sink(即requestBodyOut),这里仍旧没有写入socket,
    26   //这里我不太理解,httpCodec.createRequestBody返回的sink其实已经是一个buffer了,为什么还要套一层?
    27   bufferedRequestBody.close();
    28   
    29   //这里才真正写出socket,socketOutputStream.write
    30     /** Flush the request to the underlying socket and signal no more bytes will be transmitted. */
    31   httpCodec.finishRequest();
    32   
    33     //开始构造response
    34     //读响应行、响应头
    35   Response.Builder responseBuilder = httpCodec.readResponseHeaders(false);
    36 
    37   Response response = responseBuilder
    38       .request(request)
    39       .handshake(streamAllocation.connection().handshake())
    40       .sentRequestAtMillis(sentRequestMillis)
    41       .receivedResponseAtMillis(System.currentTimeMillis())
    42       .build();
    43 
    44  /**
    45       * httpCodec.openResponseBody会返回一个输入流,类似socketInputStream,还未从流中读取数据。
    46       * 需要手动关闭
    47      * Response.close()
    48      * Response.body().close()
    49      * Response.body().source().close()
    50      * Response.body().charStream().close()
    51      * Response.body().byteString().close()
    52      * Response.body().bytes()
    53      * Response.body().string()
    54      */
    55   response = response.newBuilder()
    56       .body(httpCodec.openResponseBody(response))
    57       .build();
    58   return response;
    59 }

    异步请求

     1 //RealCall的enqueue方法
     2 public void enqueue(Callback responseCallback) {
     3   synchronized (this) {
     4     if (executed) throw new IllegalStateException("Already Executed");
     5     executed = true;
     6   }
     7   captureCallStackTrace();
     8   //入队
     9   client.dispatcher().enqueue(new AsyncCall(responseCallback));
    10 }

    dispatcher()会返回与该okHttpClient关联的Dispatcher实例,dispatcher的enqueue方法

     1 //Dispatcher的enqueue方法
     2 synchronized void enqueue(AsyncCall call) {
     3   if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
     4       //放入异步队列,与之前提到的同步队列意义相同
     5     runningAsyncCalls.add(call);
     6     //异步执行,通常的做法是放入executorService维护的任务队列中,然后由线程池竞争执行队列中的任务。
     7     //executorService的默认构造逻辑:executorService = new java.util.concurrent.ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, 
     8     //new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
     9     //参数对应:corePoolSize,maximumPoolSize,keepAliveTime,timeUnit,workQueue,threadFactory。
    10     //也可以自定义executorService:
    11     //OkHttpClient okHttpClient = new OkHttpClient.Builder().dispatcher(new Dispatcher(Executors.newSingleThreadExecutor())).build();
    12 
    13     executorService().execute(call);
    14   } else {
    15     readyAsyncCalls.add(call);
    16   }
    17 }

    AsynCall在executorService.execute中的执行逻辑是AsynCall的execute逻辑

     1 //AsyncCall的执行逻辑
     2 protected void execute() {
     3   boolean signalledCallback = false;
     4   try {
     5       //getResponseWithInterceptorChain与同步调用一样
     6     Response response = getResponseWithInterceptorChain();
     7     if (retryAndFollowUpInterceptor.isCanceled()) {
     8       signalledCallback = true;
     9       //onFailure回调
    10       responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
    11     } else {
    12       signalledCallback = true;
    13       //onResponse回调
    14       responseCallback.onResponse(RealCall.this, response);
    15     }
    16   } catch (IOException e) {
    17     if (signalledCallback) {
    18       // Do not signal the callback twice!
    19       Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
    20     } else {
    21       responseCallback.onFailure(RealCall.this, e);
    22     }
    23   } finally {
    24       //从异步队列中删除该call
    25     client.dispatcher().finished(this);
    26   }
    27 }

    Buffer

    okhttp并没有使用jdk的ByteBuffer,而是自己对byte[]进行了池化,这也使得okhttp没有机会使用DirectByteBuffer了,同时由于池化的byte[]被限定为64kb,okhttp也不适合大数据量应用。

     1 //Buffer即是sink,又是source
     2 public final class Buffer implements BufferedSource, BufferedSink, Cloneable {
     3     //双向链表头结点
     4     Segment head;
     5     //......
     6     
     7     public void write(Buffer source, long byteCount) {
     8         // Move bytes from the head of the source buffer to the tail of this buffer
     9         // while balancing two conflicting goals: don't waste CPU and don't waste
    10         // memory.
    11         //
    12         // Don't waste CPU (ie. don't copy data around).
    13         //
    14         // Copying large amounts of data is expensive. Instead, we prefer to
    15         // reassign entire segments from one buffer to the other.
    16         //内存间的复制操作是需要cpu参与的,为了避免复制,就将source中的Segment链表接到本Buffer的Segment链表尾部.
    17         //
    18         // Don't waste memory.
    19         //
    20         // As an invariant, adjacent pairs of segments in a buffer should be at
    21         // least 50% full, except for the head segment and the tail segment.
    22         //当Segment的利用率低于50%时,依旧需要使用System.arrayCopy将next的数据复制到pre中,也就是进行compact.
    23   }
    24   
    25   public Buffer writeUtf8(String string, int beginIndex, int endIndex) {
    26       //如果调用String.getBytes必然会在内部new byte[],这里的writeUtf8是通过String的getBytes编码string的吗?
    27       //writeUtf8并没有使用getBytes,而是自己实现了utf8的编码规则,并将字节编码序列存放在Buffer持有的data字节数组中。
    28   }
    29 }
     1 final class Segment {
     2   /** The size of all segments in bytes. */
     3   static final int SIZE = 8192;
     4 
     5   /** Segments will be shared when doing so avoids {@code arraycopy()} of this many bytes. */
     6   static final int SHARE_MINIMUM = 1024;
     7     //字节数组,真正存放数据的地方
     8   final byte[] data;
     9 
    10   /** The next byte of application data byte to read in this segment. */
    11   int pos;
    12 
    13   /** The first byte of available data ready to be written to. */
    14   int limit;
    15 
    16   /** True if other segments or byte strings use the same byte array. */
    17   boolean shared;
    18 
    19   /** True if this segment owns the byte array and can append to it, extending {@code limit}. */
    20   boolean owner;
    21 
    22   /** Next segment in a linked or circularly-linked list. */
    23   //segment可以组成一个单链表(SegmentPool中)或循环链表(Buffer中)
    24   Segment next;
    25 
    26   /** Previous segment in a circularly-linked list. */
    27   //双向链表的前指针
    28   Segment prev;
    29 
    30   Segment() {
    31     this.data = new byte[SIZE];
    32     this.owner = true;
    33     this.shared = false;
    34   }
    35 
    36   Segment(Segment shareFrom) {
    37     this(shareFrom.data, shareFrom.pos, shareFrom.limit);
    38     shareFrom.shared = true;
    39   }
    40 
    41   Segment(byte[] data, int pos, int limit) {
    42     this.data = data;
    43     this.pos = pos;
    44     this.limit = limit;
    45     this.owner = false;
    46     this.shared = true;
    47   }
    48   //......
    49 }
     1 /**
     2  * A collection of unused segments, necessary to avoid GC churn and zero-fill.
     3  * This pool is a thread-safe static singleton.
     4  */
     5 //SegmentPool是未使用的Segment的集合,即池化的byte[]
     6 final class SegmentPool {
     7   /** The maximum number of bytes to pool. */
     8   // TODO: Is 64 KiB a good maximum size? Do we ever have that many idle segments?
     9   //client最大持有64kb的缓冲池,超出的部分可以申请,但回收时不会被放回池中,看来okio.SegmentPool只适合小数据量应用
    10   static final long MAX_SIZE = 64 * 1024; // 64 KiB.
    11 
    12   /** Singly-linked list of segments. */
    13   //单链表
    14   static @Nullable Segment next;
    15   
    16   /** Total bytes in this pool. */
    17   static long byteCount;
    18 
    19   private SegmentPool() {
    20   }
    21     //从池中获取Segment
    22   static Segment take() {
    23     synchronized (SegmentPool.class) {
    24       if (next != null) {
    25         Segment result = next;
    26         next = result.next;
    27         result.next = null;
    28         byteCount -= Segment.SIZE;
    29         return result;
    30       }
    31     }
    32     return new Segment(); // Pool is empty. Don't zero-fill while holding a lock.
    33   }
    34     //回收Segment置池中
    35   static void recycle(Segment segment) {
    36     if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
    37     if (segment.shared) return; // This segment cannot be recycled.
    38     synchronized (SegmentPool.class) {
    39       if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full.
    40       byteCount += Segment.SIZE;
    41       segment.next = next;
    42       segment.pos = segment.limit = 0;
    43       next = segment;
    44     }
    45   }
    46 }

    Cache

    http缓存,首先server需要发送带缓存首部的响应(比如Cache-Control:max-age=3600),然后client需要开启缓存功能(浏览器自动会开启,chrome在debug时可以disable cache,okhttp需要在创建okHttpClient时设置cache)。

    1 int cacheSize = 10 * 1024 * 1024; // 10 MiB
    2 //最终会得到一个okhttp3.internal.cache.DiskLruCache
    3 Cache cache = new Cache(cacheDirectory, cacheSize);
    4 OkHttpClient client = new OkHttpClient.Builder()
    5     .cache(cache)
    6     .build();

    client缓存响应时会记录一个缓存时间(不是Date,是本地时间),响应头的max-age表明在max-age时间内,该response都是新鲜的,即不需要去server验证;响应头的no-cache表示client可以对response缓存,但client请求时必须先向server验证该缓存的新鲜度。

    请求头的max-age表示client只接收max-age之内的缓存,max-stale表示client能接受最大的缓存过期时间,no-cache也是要先向server验证新鲜度,if-only-cached表示只从缓存获取数据。

     1 Request request = new Request.Builder()
     2     //Cache-Control:no-cache
     3     //Forces caches to submit the request to the origin server for validation before releasing a cached copy.
     4     //准确的讲应该叫donot-serve-from-cache-without-revalidation
     5     .cacheControl(new CacheControl.Builder().noCache().build())
     6     .url("http://publicobject.com/helloworld.txt")
     7     .build();
     8 
     9 Request request = new Request.Builder()
    10     //Cache-Control:max-age=<seconds>
    11     //Specifies the maximum amount of time a resource will be considered fresh. 
    12     //Contrary to Expires, this directive is relative to the time of the request.
    13     .cacheControl(new CacheControl.Builder()
    14         .maxAge(0, TimeUnit.SECONDS)
    15         .build())
    16     .url("http://publicobject.com/helloworld.txt")
    17     .build();
    18 
    19 Request request = new Request.Builder()
    20     //Cache-Control:only-if-cached
    21     //Indicates to not retrieve new data. The client only wishes to obtain a cached response, 
    22     //and should not contact the origin-server to see if a newer copy exists.
    23     .cacheControl(new CacheControl.Builder()
    24         .onlyIfCached()
    25         .build())
    26     .url("http://publicobject.com/helloworld.txt")
    27     .build();
    28 Response forceCacheResponse = client.newCall(request).execute();
    29 if (forceCacheResponse.code() != 504) {
    30     // The resource was cached! Show it.
    31 } else {
    32     // The resource was not cached.
    33 }
    34 
    35 Request request = new Request.Builder()
    36     //Cache-Control:max-stale[=<seconds>]
    37     //Indicates that the client is willing to accept a response that has exceeded its expiration time. 
    38     //Optionally, you can assign a value in seconds, indicating the time the response must not be expired by.
    39     //okhttp中max-stale必须赋值?赋0表示max-stale无效?
    40     .cacheControl(new CacheControl.Builder()
    41         .maxStale(365, TimeUnit.DAYS)
    42         .build())
    43     .url("http://publicobject.com/helloworld.txt")
    44     .build();

    如下图,是否过期由响应头max-age决定,如果响应头没有ETag、Last-Modified则直接发送原始请求。

    参考:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control

      https://segmentfault.com/a/1190000006741200

  • 相关阅读:
    浏览器内核、webview内核
    移动端(h5)页面适配
    vue 开发多页面应用
    git 指令
    joomla多语言建站之默认前台语言设置
    初识node,原理与浏览器何其相似
    vue-cli下配置项目访问ip和服务器ip
    js中不容小觑的var声明
    vue中的事件监听之——v-on vs .$on
    用js的eval函数模拟Web API中的onclick事件
  • 原文地址:https://www.cnblogs.com/holoyong/p/7413403.html
Copyright © 2020-2023  润新知