• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • 安卓總結 之 OkHttp使用及源碼分析(三)

    本章主要介紹Okhttp的使用和和源碼分析

    1. 準備工作
    2. 常見用法
    3. OkHttp更好的封裝
    4. OkHttp的源碼分析

    一. 準備工作

    在gradle中添加依賴

        implementation 'com.squareup.okio:okio:1.7.0'
        implementation 'com.squareup.okhttp3:okhttp:3.2.0'
    

    添加網絡權限

       <uses-permission android:name="android.permission.INTERNET"/>
    

    二. 常見用法

    1. get異步請求
    private void getAsynHttp() {
    
            Request.Builder requestBuilder = new Request.Builder().url("http://www.baidu.com");
            requestBuilder.method("GET", null);
            Request request = requestBuilder.build();
            Call mcall = mOkHttpClient.newCall(request);
            mcall.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Toast.makeText(getApplicationContext(), "請求失敗", Toast.LENGTH_SHORT).show();
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    String str = response.body().string();
                    Log.i(TAG, str);
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(getApplicationContext(), "請求成功", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            });
        }
    
    1. post異步請求
        private void postAsynHttp() {
            RequestBody formBody = new FormBody.Builder()
                    .add("ip", "59.108.54.37")
                    .build();
            Request request = new Request.Builder()
                    .url("http://ip.taobao.com/service/getIpInfo.php")
                    .post(formBody)
                    .build();
            OkHttpClient mOkHttpClient = new OkHttpClient();
            Call call = mOkHttpClient.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Toast.makeText(getApplicationContext(), "請求失敗", Toast.LENGTH_SHORT).show();
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    String str = response.body().string();
                    Log.d(TAG, str);
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(getApplicationContext(), "請求成功", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
    
            });
        }
    

    只是添加了一個FormBody

    1. 異步上傳文件
        private void postAsynFile() {
            String filepath = "";
            if (Environment.getExternalStorageState().equals(
                    Environment.MEDIA_MOUNTED)) {
                filepath = Environment.getExternalStorageDirectory().getAbsolutePath();
            } else {
                filepath = getFilesDir().getAbsolutePath();
            }
            File file = new File(filepath, "wangshu.txt");
            Request request = new Request.Builder()
                    .url("https://api.github.com/markdown/raw")
                    .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
                    .build();
            mOkHttpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
    
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    Log.d(TAG, response.body().string());
                }
            });
        }
    

    定義文件類型再調用RequestBody方法就ok

    1. 異步下載文件
        private void downAsynFile() {
            String url = "https://img-my.csdn.net/uploads/201603/26/1458988468_5804.jpg";
            Request request = new Request.Builder().url(url).build();
            mOkHttpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    Toast.makeText(getApplicationContext(), "文件下載失敗", Toast.LENGTH_SHORT).show();
                }
    
                @Override
                public void onResponse(Call call, Response response) {
                    InputStream inputStream = response.body().byteStream();
                    FileOutputStream fileOutputStream = null;
                    String filepath = "";
                    try {
                        if (Environment.getExternalStorageState().equals(
                                Environment.MEDIA_MOUNTED)) {
                            filepath = Environment.getExternalStorageDirectory().getAbsolutePath();
                        } else {
                            filepath = getFilesDir().getAbsolutePath();
                        }
                        File file = new File(filepath, "wangshu.jpg");
                        if (null != file) {
                            fileOutputStream = new FileOutputStream(file);
                            byte[] buffer = new byte[2048];
                            int len = 0;
                            while ((len = inputStream.read(buffer)) != -1) {
                                fileOutputStream.write(buffer, 0, len);
                            }
                            fileOutputStream.flush();
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(getApplicationContext(), "文件存儲成功", Toast.LENGTH_SHORT).show();
                                }
                            });
                        } else {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(getApplicationContext(), "文件存儲失敗", Toast.LENGTH_SHORT).show();
                                }
                            });
                        }
                    } catch (IOException e) {
                        Log.e(TAG, "IOException");
                        e.printStackTrace();
                    }
                }
            });
        }
    

    調用response.body().byteStream();得到輸入流然后就是文件操作了

    1. 初始化okClient
        private void initOkHttpClient() {
            File sdcache = getExternalCacheDir();
            int cacheSize = 10 * 1024 * 1024;
            OkHttpClient.Builder builder = new OkHttpClient.Builder()
                    .connectTimeout(15, TimeUnit.SECONDS)
                    .writeTimeout(20, TimeUnit.SECONDS)
                    .readTimeout(20, TimeUnit.SECONDS)
                    .cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
            mOkHttpClient = builder.build();
        }
    

    在okHttp中,所有所有對于連接的初始化操作都在OkHttpClient.Builder進行,這里設置了超市時間和緩存。

    1. 取消請求

    當用戶離開應用程序或者跳轉到其他頁面的時候,我們可以取消任務節省網絡資源。

    簡單的方法就是在Request.Builder.tag中分配一個標簽,然后我們就能用OkHtppClient.cancel(Object tag)來取消任務

    三. OkHttp更好的封裝

    public class OkHttpEngine {
        private static volatile  OkHttpEngine mInstance;
        private OkHttpClient mOkHttpClient;
        private Handler mHandler;
    
        // 雙重檢驗鎖創建OkHttpEngine單例
        public static OkHttpEngine getInstance(Context context) {
            if (mInstance == null) {
                synchronized (OkHttpEngine.class) {
                    if (mInstance == null) {
                        mInstance = new OkHttpEngine(context);
                    }
                }
            }
            return mInstance;
        }
    
        // 在構造函數中初始化Client和handler
        private OkHttpEngine(Context context) {
            File sdcache = context.getExternalCacheDir();
            int cacheSize = 10 * 1024 * 1024;
            OkHttpClient.Builder builder = new OkHttpClient.Builder()
                    .connectTimeout(15, TimeUnit.SECONDS)
                    .writeTimeout(20, TimeUnit.SECONDS)
                    .readTimeout(20, TimeUnit.SECONDS)
                    .cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
             mOkHttpClient=builder.build();
             mHandler = new Handler();
        }
    
    
        /**
         * 異步get請求
         * @param url
         * @param callback
         */
        public void getAsynHttp(String url, ResultCallback callback) {
    
            final Request request = new Request.Builder()
                    .url(url)
                    .build();
            Call call = mOkHttpClient.newCall(request);
            dealResult(call, callback);
        }
    
    
        private void dealResult(Call call, final ResultCallback callback) {
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    sendFailedCallback(call.request(), e, callback);
                }
    
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    sendSuccessCallback(response.body().string(), callback);
                }
    
                private void sendSuccessCallback(final String str, final ResultCallback callback) {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            if (callback != null) {
                                try {
                                    callback.onResponse(str);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    });
                }
    
                private void sendFailedCallback(final Request request, final Exception e, final ResultCallback callback) {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            if (callback != null)
                                callback.onError(request, e);
                        }
                    });
                }
    
            });
        }
        
    
        public abstract class ResultCallback{
            public abstract void onError(Request request, Exception e);
            public abstract void onResponse(String str) throws IOException;
        }
    }
    

    請求網絡的時候是用Handler將請求結果回調給UI線程,所以我們想要請求網絡的時候只需要調用OkHttpEngine的getAsynHttp方法并寫一個ResultCallback回調就可以了。

    四. OkHttp的源碼分析

    我們從使用開始,一步步剖析OkClient的源碼實現

    //下面是一段Kotlin代碼
    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            val request = Request.Builder().url("https://www.baidu.com/").method("get", null).build()
            OkHttpClient().newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call?, e: IOException?) {
                    Log.d("onClient", "isBad")
                }
    
                override fun onResponse(call: Call?, response: Response?) {
                    Log.e("onClient", "isOk")
                }
            })
        }
    

    我們把重點關注在 OkHttpClient().newCall(request).enqueue() 這個方法中

    newCall方法:

      // OkHttpClint中的newCall方法
      @Override public Call newCall(Request request) {
        return new RealCall(this, request);
      }
    

    實例化一個RealCall類,RealCall實現了Call接口,看一下Call是干什么的
    ,這里保留了源碼的英文注釋

    // A call is a request that has been prepared for execution. 
    public interface Call {
        //  Invokes the request immediately, and blocks until the response can be processed or is in  error.
        Response execute() throws IOException;
        
        // Schedules the request to be executed at some point in the future.
        void enqueue(Callback responseCallback);
        
        void cancel();
        boolean isExecuted();
        boolean isCanceled();
        interface Factory {
        Call newCall(Request request);
      }
    }
    

    然后我們繼續看OkHttpClient().newCall(request).enqueue()
    的enqueue方法

      // RealCall中的enqueue方法
      void enqueue(Callback responseCallback, boolean forWebSocket) {
        synchronized (this) {
          if (executed) throw new IllegalStateException("Already Executed");
          executed = true;
        }
        client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
      }
    

    其中這個參數responseCallback就是源碼介紹開頭中OkHttpClient().newCall(request).enqueue()傳入的callback

    我們這里可以看到,真正處理這個Callback的并不是RealCall,而是dispatcher(),這個就是調度器,看看他的類聲明 public final class Dispatcher 是一個不變類,也就是說不能被重寫。

    我們看一下調度器Dispatcher中的enqueue方法:

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

    這個方法用了synchronized聲明。看到這么多全局的變量,感覺有點煩,沒事,其實很簡單,我們看一下Dispatcher的聲明:

     // Dispatcher類的成員變量
      private int maxRequests = 64;
      private int maxRequestsPerHost = 5;
    
      /** Executes calls. Created lazily. */
      private ExecutorService executorService;
    
      /** Ready async calls in the order they'll be run. */
      private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
    
      /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
      private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
    
      /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
      private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
    

    是不是感覺很清晰,很簡單

    我們回頭看這段代碼

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

    很簡單,如果可以這個符合可以運行的條件,就把它加到runningAsyncCalls隊列中,然后調用execute執行,否則加到readyAsyncCalls準備隊列中

    ok,我們把關注點放在這個方法中
    executorService().execute(call);

    // Dispatcher的executorService方法
    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;
      }
     
     // 對比一下newCachedThreadPool,簡直就是一毛一樣好吧
        public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    

    我們知道CachedThreadPool實際上就是一個無限容量,但是隊列中只有一個線程的線程池。這種線程池比較適合任務比較多,但是任務比較小的情況。

    execute(Runnable run)調度方法最后會調用調用這個runnable的run方法,因為這個call是AsyncCall類,我們看看這個類的run方法:

    final class AsyncCall extends NamedRunnable {
        private final Callback responseCallback;
        private final boolean forWebSocket;
        private AsyncCall(Callback responseCallback, boolean forWebSocket) {
          super("OkHttp %s", originalRequest.url().toString());
          this.responseCallback = responseCallback;
          this.forWebSocket = forWebSocket;
        }
        @Override protected void execute() {
          boolean signalledCallback = false;
          try {
            Response response = getResponseWithInterceptorChain(forWebSocket);
            if (canceled) {
              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!
              logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
            } else {
              responseCallback.onFailure(RealCall.this, e);
            }
          } finally {
            client.dispatcher().finished(this);
          }
        }
      }
    

    沒有run方法呀,怎么肥事?看看它繼承的類NamedRunnable搞了什么。

    public abstract class NamedRunnable implements Runnable {
      protected final String name;
    
      public NamedRunnable(String format, Object... args) {
        this.name = String.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的execute方法。ojbk,來看一下吧!

       // AsyncCall的execute方法
        @Override protected void execute() {
          boolean signalledCallback = false;
          try {
            Response response = getResponseWithInterceptorChain(forWebSocket);
            if (canceled) {
              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!
              logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
            } else {
              responseCallback.onFailure(RealCall.this, e);
            }
          } finally {
            client.dispatcher().finished(this);
          }
        }
    

    別急,我們一個個往下面看,先看這個方法:getResponseWithInterceptorChain

      private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
        Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
        return chain.proceed(originalRequest);
      }
    

    ApplicationInterceptorChain 從名字可以猜測到,他是一個攔截鏈,看一下吧!

     // ApplicationInterceptorChain類的proceed方法
     @Override public Response proceed(Request request) throws IOException {
          // If there's another interceptor in the chain, call that.
          if (index < client.interceptors().size()) {
            Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
            // 得到了當前攔截器
            Interceptor interceptor = client.interceptors().get(index);
            // 攔截操作,所有攔截器(除了最后一個)都會阻塞到這里
            Response interceptedResponse = interceptor.intercept(chain);
    
            if (interceptedResponse == null) {
              throw new NullPointerException("application interceptor " + interceptor
                  + " returned null");
            }
    
            return interceptedResponse;
          }
          return getResponse(request, forWebSocket);
        }
      }
    

    看到這段,有點發蒙

    Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
    

    為什么還要創建一個chain呢,原來index代表當前攔截器所在的位置,比如一共有五個攔截器,攔截器“G”在第二個,那你要做的是將“G”攔截器攔截在第一個攔截器之后。get到了嗎?

    Response interceptedResponse = interceptor.intercept(chain);
    這個方法為什么會攔住所有的攔截器,大家可能不明白,我就隨便創建一個攔截器(攔截發出的請求和響應的日志)出來,大家就知道了。

    class LoggingInterceptor implements Interceptor {
      @Override public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();
        
        long t1 = System.nanoTime();
        logger.info(String.format("Sending request %s on %s%n%s",
            request.url(), chain.connection(), request.headers()));
    
        Response response = chain.proceed(request);
    
        long t2 = System.nanoTime();
        logger.info(String.format("Received response for %s in %.1fms%n%s",
            response.request().url(), (t2 - t1) / 1e6d, response.headers()));
    
        return response;
      }
    }
    

    大家能夠明白嗎,這里我畫一張圖:

    攔截器.png

    這個有點類似Spirng中的攔截器,其實是一個道理。

    ok,我們討論完了攔截器。回到AsyncCall的execute方法。

       // AsyncCall的execute方法
        @Override protected void execute() {
          boolean signalledCallback = false;
          try {
            Response response = getResponseWithInterceptorChain(forWebSocket);
            if (canceled) {
              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!
              logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
            } else {
              responseCallback.onFailure(RealCall.this, e);
            }
          } finally {
            client.dispatcher().finished(this);
          }
        }
    
      responseCallback.onFailure(RealCall.this, responseCallback.onResponse(RealCall.this, response);
    

    這里正是我們重寫的Callback的兩個方法。

    好了,做了這么久的前戲,也該開始網絡請求了吧?看這個方法
    client.dispatcher().finished(this); 是調度器的finish方法。

       // Dispatcher類的finished方法
       synchronized void finished(AsyncCall call) {
        if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
        promoteCalls();
      }
    

    這里簡單地把call從運行隊列中移走了。看一下PromoteCalls方法:

     // Dispatcher類中的promoteCalls方法
     private void promoteCalls() {
        if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
        if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
    
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
          AsyncCall call = i.next();
    
          if (runningCallsForHost(call) < maxRequestsPerHost) {
            i.remove();
            runningAsyncCalls.add(call);
            executorService().execute(call);
          }
    
          if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
        }
      }
    

    很簡單,running的一個call被消化了,這個隊列也就有了空位置,這時候ready隊列有call乘機上位。等等,說好的網絡請求呢,在哪?是不是哪里疏忽了?

    我們分析到AsyncCall的execute方法的時候,還記得它做了什么嗎,它用一大堆攔截鏈攔截他,然后在最后 client.dispatcher().finished(this);對調度器Dispactcher的兩個隊列進行收尾對吧?

    于是我們大概分析到網絡請求操作在它們中間!果然在攔截器中間ApplicationInterceptorChain類的proceed方法找到啦!

     // ApplicationInterceptorChain類的proceed方法
     @Override public Response proceed(Request request) throws IOException {
          // If there's another interceptor in the chain, call that.
          if (index < client.interceptors().size()) {
            Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
            // 得到了當前攔截器
            Interceptor interceptor = client.interceptors().get(index);
            // 攔截操作,所有攔截器(除了最后一個)都會阻塞到這里
            Response interceptedResponse = interceptor.intercept(chain);
    
            if (interceptedResponse == null) {
              throw new NullPointerException("application interceptor " + interceptor
                  + " returned null");
            }
    
            return interceptedResponse;
          }
          return getResponse(request, forWebSocket);
        }
      }
    

    網絡請求在這里:getResponse(request, forWebSocket);

    這個方法又臭又長,這里我把重要的貼出來。

    Response getResponse(Request request, boolean forWebSocket) throws IOException {
        engine.sendRequest();
        engine.readResponse();
    }
    

    喵喵喵!就這么短

    一個個看啰!
    HttpEngine的sendRequest方法也很復雜,主要解決的是緩存問題,我們就不展開了,不然要要要講到明年!

    // HttpEngine的sendRequest方法
     public void sendRequest() throws RequestException, RouteException, IOException {
        if (cacheStrategy != null) return; 
        if (httpStream != null) throw new IllegalStateException();
    
        Request request = networkRequest(userRequest);
        
        //獲取Client中的Cache,同時Cache在初始化的時候讀取緩存目錄中曾經請求過的所有信息。
        InternalCache responseCache = Internal.instance.internalCache(client);
        Response cacheCandidate = responseCache != null
            ? responseCache.get(request)
            : null;
    
        long now = System.currentTimeMillis();
        cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
        //網絡請求
        networkRequest = cacheStrategy.networkRequest;
        //緩存的響應
        cacheResponse = cacheStrategy.cacheResponse;
    
        if (responseCache != null) {
            //記錄當前請求是網絡發起的還是緩存發起的
          responseCache.trackResponse(cacheStrategy);
        }
    
        
        if (cacheCandidate != null && cacheResponse == null) {
          closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
        }
    
        // 不進行網絡請求并且緩存不存在或者過期,返回504錯誤
        if (networkRequest == null && cacheResponse == null) {
          userResponse = new Response.Builder()
              .request(userRequest)
              .priorResponse(stripBody(priorResponse))
              .protocol(Protocol.HTTP_1_1)
              .code(504)
              .message("Unsatisfiable Request (only-if-cached)")
              .body(EMPTY_BODY)
              .build();
          return;
        }
        
        // 不進行網絡請求而且緩存可以使用,則直接返回緩存
        if (networkRequest == null) {
          userResponse = cacheResponse.newBuilder()
              .request(userRequest)
              .priorResponse(stripBody(priorResponse))
              .cacheResponse(stripBody(cacheResponse))
              .build();
          userResponse = unzip(userResponse);
          return;
        }
    
        // 需要訪問網絡時
        boolean success = false;
        try {
          httpStream = connect();
          httpStream.setHttpEngine(this);
    

    cacheCandidate是上次與服務器交互時緩存的Response,這里的緩存均基于Map。key是請求中url的md5,value是在文件中查到的緩存,頁面置換算法基于LRU。

    通過cacheStrategy我們可以得到networkRequest和cacheResponse,代表網絡請求和緩存是否存在。如果networkRequest和cacheResponse都為null的時候,返回504錯誤,當networkRequest為null時,也就是不進行網絡請求時,就可以直接返回緩存,其他情況就請求網絡。

    我們粗略看一下readResponse()方法:

     public void readResponse() throws IOException {
        Response networkResponse;
        if (forWebSocket) {
          httpStream.writeRequestHeaders(networkRequest);
          // 讀取網絡響應
          networkResponse = readNetworkResponse();
        } else if (!callerWritesRequestBody) {
         // 檢查緩存是否可用,如果可用則使用當前緩存的Response,關閉網絡連接,釋放連接
          networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);
        } else {
          // Emit the request body's buffer so that everything is in requestBodyOut.
          if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
            bufferedRequestBody.emit();
          }
    
          // Emit the request headers if we haven't yet. We might have just learned the Content-Length.
          if (sentRequestMillis == -1) {
            if (OkHeaders.contentLength(networkRequest) == -1
                && requestBodyOut instanceof RetryableSink) {
              long contentLength = ((RetryableSink) requestBodyOut).contentLength();
              networkRequest = networkRequest.newBuilder()
                  .header("Content-Length", Long.toString(contentLength))
                  .build();
            }
            httpStream.writeRequestHeaders(networkRequest);
          }
    
          // Write the request body to the socket.
          if (requestBodyOut != null) {
            if (bufferedRequestBody != null) {
              // This also closes the wrapped requestBodyOut.
              bufferedRequestBody.close();
            } else {
              requestBodyOut.close();
            }
            if (requestBodyOut instanceof RetryableSink) {
              httpStream.writeRequestBody((RetryableSink) requestBodyOut);
            }
          }
    
          networkResponse = readNetworkResponse();
        }
    
        receiveHeaders(networkResponse.headers());
    
        // If we have a cache response too, then we're doing a conditional get.
        if (cacheResponse != null) {
          if (validate(cacheResponse, networkResponse)) {
            userResponse = cacheResponse.newBuilder()
                .request(userRequest)
                .priorResponse(stripBody(priorResponse))
                .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();
            networkResponse.body().close();
            releaseStreamAllocation();
    
            // Update the cache after combining headers but before stripping the
            // Content-Encoding header (as performed by initContentStream()).
            InternalCache responseCache = Internal.instance.internalCache(client);
            responseCache.trackConditionalCacheHit();
            responseCache.update(cacheResponse, stripBody(userResponse));
            userResponse = unzip(userResponse);
            return;
          } else {
            closeQuietly(cacheResponse.body());
          }
        }
    
        userResponse = networkResponse.newBuilder()
            .request(userRequest)
            .priorResponse(stripBody(priorResponse))
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
    
        if (hasBody(userResponse)) {
          maybeCache();
          userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
        }
      }
    

    這個方法做的是解析HTTP響應報頭。如果有緩存而且可用,則用緩存的數據并更新緩存,否則則用網絡請求返回的數據。

    總結完了,大家應該會覺得很亂,這里粗略畫個圖幫助大家理解。

    在這里插入圖片描述

    版權聲明:本文為qq_33394088原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/qq_33394088/article/details/90718570

    智能推薦

    安卓四大組件總結之activity

    一、簡介     Android四大組件分別為activity、service、content provider、broadcast receiver。(活動、服務、內容提供器、廣播)。 1、activity     (1)一個Activity通常是一個單獨的窗口,是一個負責與用戶交互的組件。     (2)...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    猜你喜歡

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    統計學習方法 - 樸素貝葉斯

    引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...

    styled-components —— React 中的 CSS 最佳實踐

    https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...

    基于TCP/IP的網絡聊天室用Java來實現

    基于TCP/IP的網絡聊天室實現 開發工具:eclipse 開發環境:jdk1.8 發送端 接收端 工具類 運行截圖...

    精品国产乱码久久久久久蜜桃不卡