第五章 網絡 之 Android網絡知識&框架(二)

文章目錄

一.Android實現網絡請求的主流方法

HttpURLConnection和HttpClient:這兩種方式都支持HTTPS協議、以流的形式進行上傳和下載、配置超時時間、IPv6、以及連接池等功能。

1.1 HttpClient

(1)簡介

DefaultHttpClient和它的兄弟AndroidHttpClient都是HttpClient具體的實現類,它們都擁有衆多的API,而且實現比較穩定,bug數量也很少。
但同時也由於HttpClient的API數量過多,使得我們很難在不破壞兼容性的情況下對它進行升級和擴展,結構複雜,維護成本高。
Android SDK中包含了HttpClient,在Android6.0版本直接刪除了HttpClient類庫。

(2)實現

LoginHttpClientUtils.java

  public Boolean LoginGet(Context context,String username,String password){
    try{
        String path = "http://192.168.1.138:8080/chyWebTest/LoginServlet?username="+username+"&password="+password;
        Boolean isSuccess = false;
        //1.創建一個HttpClient對象
        HttpClient httpClient = new DefaultHttpClient();
        //2.設置請求方式
        HttpGet httpGet = new HttpGet(path);
        //3.執行一個Http請求(返回HttpResponse)
        HttpResponse httpResponse = httpClient.execute(httpGet);
        //4.獲取請求的狀態碼
        StatusLine statusLine = httpResponse.getStatusLine();
        int code = statusLine.getStatusCode();
        //5.判斷狀態嗎後獲取內容
        if(code==200){
            //獲取實體內容,中封裝有Http請求返回流信息
            HttpEntity entity = httpResponse.getEntity();
            InputStream inputStream = entity.getContent();
            String result = StreamUtil.StreamToString(inputStream);
            if(result.equals(("success"))){
                isSuccess = true;
            }
        }
        return isSuccess;
    }catch (Exception e){
        e.printStackTrace();
    }
    return null;
}

LoginHttpClientActivity.java

//主線程創建一個Handler對象
handler = new Handler(){
    //calledFromWrongThreadException從錯誤的線程調用(UI操作只能通過主線程,子線程不能更新UI空間)
    @Override
    public void handleMessage(Message msg) {
        //重寫handler的handleMessage方法,用來接受並處理子線程發來的消息,並可執行UI操作
        if(msg.obj.equals("success")) {
            Toast.makeText(LoginHttpClientActivity.this,"登錄成功",Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(LoginHttpClientActivity.this,"登錄失敗",Toast.LENGTH_SHORT).show();
        }
    }
};

//子線程創建一個Message對象,將獲取的數據綁定給Msg,通過主線程中handler對象將msg發送給主線程
    Message msg = Message.obtain();
    if(result)msg.obj = "success";
    else msg.obj = "fail";
    handler.sendMessage(msg);
}
                //創建一個新線程執行HttpClient網絡請求
                Thread getThread = new Thread(new LoginGetThread());
                getThread.start();

class LoginGetThread implements Runnable{
    public void run(){
        login(username,password,0);
    }
}

1.2 HttpURLConnection

(1)簡介

HttpURLConnection是一種多用途、輕量極的HTTP客戶端,使用它來進行HTTP操作可以適用於大多數的應用程序。雖然HttpURLConnection的API提供的比較簡單,但是同時這也使得我們可以更加容易地去使用和擴展它。
Android2.2前有個重大BUG:調用close()函數會影響連接池,導致鏈接複用失效。
比如說對一個可讀的InputStream調用close()方法時,就有可能會導致連接池失效了。那麼我們通常的解決辦法就是直接禁用掉連接池的功能:

private void disableConnectionReuseIfNecessary() {
      // 這是一個2.2版本之前的bug
      if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
            System.setProperty("http.keepAlive", "false");
      }
}

Android2.2後默認開啓了gzip壓縮、提高https性能(2.3)與緩衝機制(4.0)

(2)實現

1、創建一個UrlConnManager類,然後裏面提供getHttpURLConnection()方法用於配置默認的參數並返回HttpURLConnection:

public static HttpURLConnection getHttpURLConnection(String url){
     HttpURLConnection mHttpURLConnection=null;
     try {
         URL mUrl=new URL(url);
         mHttpURLConnection=(HttpURLConnection)mUrl.openConnection();
         //設置鏈接超時時間
         mHttpURLConnection.setConnectTimeout(15000);
         //設置讀取超時時間
         mHttpURLConnection.setReadTimeout(15000);
         //設置請求參數
         mHttpURLConnection.setRequestMethod("POST");
         //添加Header
         mHttpURLConnection.setRequestProperty("Connection","Keep-Alive");
         //接收輸入流
         mHttpURLConnection.setDoInput(true);
         //傳遞參數時需要開啓
         mHttpURLConnection.setDoOutput(true);
     } catch (IOException e) {
         e.printStackTrace();
     }
     return mHttpURLConnection ;
 }

發送POST請求,所以在UrlConnManager類中再寫一個postParams()方法用來組織一下請求參數並將請求參數寫入到輸出流中

public static void postParams(OutputStream output,List<NameValuePair>paramsList) throws IOException{
      StringBuilder mStringBuilder=new StringBuilder();
      for (NameValuePair pair:paramsList){
          if(!TextUtils.isEmpty(mStringBuilder)){
              mStringBuilder.append("&");
          }
          mStringBuilder.append(URLEncoder.encode(pair.getName(),"UTF-8"));
          mStringBuilder.append("=");
          mStringBuilder.append(URLEncoder.encode(pair.getValue(),"UTF-8"));
      }
      BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(output,"UTF-8"));
      writer.write(mStringBuilder.toString());
      writer.flush();
      writer.close();
  }

接下來我們添加請求參數,調用postParams()方法將請求的參數組織好傳給HttpURLConnection的輸出流,請求連接並處理返回的結果

private void useHttpUrlConnectionPost(String url) {
     InputStream mInputStream = null;
     HttpURLConnection mHttpURLConnection = UrlConnManager.getHttpURLConnection(url);
     try {
         List<NameValuePair> postParams = new ArrayList<>();
         //要傳遞的參數
         postParams.add(new BasicNameValuePair("username", "moon"));
         postParams.add(new BasicNameValuePair("password", "123"));
         UrlConnManager.postParams(mHttpURLConnection.getOutputStream(), postParams);
         mHttpURLConnection.connect();
         mInputStream = mHttpURLConnection.getInputStream();
         int code = mHttpURLConnection.getResponseCode();
         String respose = converStreamToString(mInputStream);
         Log.i("wangshu", "請求狀態碼:" + code + "\n請求結果:\n" + respose);
         mInputStream.close();
     } catch (IOException e) {
         e.printStackTrace();
     }
 }

最後開啓線程請求網絡

private void useHttpUrlConnectionGetThread() {
       new Thread(new Runnable() {
           @Override
           public void run() {
               useHttpUrlConnectionPost("http://www.baidu.com");
           }
       }).start();
   }

1.3 對比

Android2.2前不建議使用HttpURLConnection,Android4.4後,底層實現被OkHttp替換
Android5.0後HttpClient被官方棄用
所以,在Android 2.2版本以及之前的版本使用HttpClient是較好的選擇,而在Android 2.3版本及以後,HttpURLConnection則是最佳的選擇,它的API簡單,體積較小,因而非常適用於Android項目。壓縮和緩存機制可以有效地減少網絡訪問的流量,在提升速度和省電方面也起到了較大的作用。另外在Android 6.0版本中,HttpClient庫被移除了,HttpURLConnection則是以後我們唯一的選擇。

二.主流網絡請求庫

2.1 簡介

網絡請求開源庫是一個將 網絡請求+異步+數據處理 封裝好的類庫(網絡請求是Android網絡請求原生方法HttpClient或HttpURLConnection,異步包括多線程、線程池,數據處理包括序列化和反序列化)
使用網絡請求庫後,實現網絡請求的需求同時不需要考慮:異步請求、線程池、緩存等等;降低開發難度,縮短開發週期,使用方便

2.2 對比(Android-Async-Http、Volley、OkHttp、Retrofit)

在這裏插入圖片描述
在這裏插入圖片描述

三.okHttp

3.1 簡介

(1)支持http2/SPDY黑科技,共享同一個Socket來處理同一個服務器的所有請求(同一域名的所有請求stream共享同一個tcp連接),解決了HOL Blocking
SPDY(讀作“SPeeDY”)是Google開發的基於TCP的應用層協議,用以最小化網絡延遲,提升網絡速度,優化用戶的網絡使用體驗。SPDY是對HTTP協議的加強。新協議的功能包括數據流的多路複用、支持服務器推送技術、請求優先級、HTTP報頭壓縮以及強制使用SSL傳輸協議。
(2)socket自動選擇最好路線,並支持自動重連
(3)擁有自動維護的socket連接池,減少握手次數,減少請求延時
(4)擁有隊列線程池,輕鬆寫併發
(5)擁有Interceptors(攔截器)輕鬆處理請求與響應(透明GZIP壓縮,轉換從而減少數據流量)
(6)基於Headers的緩存策略減少重複的網絡請求

3.2 使用步驟

(1)添加okHttp和okIo

(Okio是一款輕量級IO框架,是著名網絡框架OkHttp的基石。Okio結合了java.io和java.nio,提供阻塞IO和非阻塞IO的功能,同時也對緩存等底層結構做了優化,能讓你更輕快的獲得、存儲和處理數據。)

(2)創建OkHttpClient對象

(3)get/post請求數據

(4)get/post調用

OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder().
        url("https://github.com/cozing").
        build();
Call call = client.newCall(request);
try {
    //1.同步請求調用的方法是call.execute(),內部採用的是線程阻塞(一直等待直到線程返回結果)方式直接將結果返回到Response
    Response response = call.execute();
    //2.異步請求調用的方法是call.enqueue(Callback callback),該方法需要傳入一個Callback等待結果回調的接口
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            Log.w("cozing", "請求失敗");
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            Log.w("cozing", "請求成功");
        }
    });
} catch (IOException e) {
    e.printStackTrace();
}

3.3 源碼解讀

(一)整體框架

在這裏插入圖片描述

(二)工作原理

2.1 OkHttpClient 構造

定義一個OkHttpClient,OkHttpClient構造函數及其配置如下。

mOkHttpClient = new OkHttpClient.Builder()
                            .addInterceptor(loggingInterceptor)
                            .retryOnConnectionFailure(true)
                            .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .readTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .build();

瞭解OkHttpClient屬性

final Dispatcher dispatcher;//調度器
    final @Nullable
    Proxy proxy;//代理
    final List<Protocol> protocols;//協議
    final List<ConnectionSpec> connectionSpecs;//傳輸層版本和連接協議
    final List<Interceptor> interceptors;//攔截器
    final List<Interceptor> networkInterceptors;//網絡攔截器
    final EventListener.Factory eventListenerFactory;
    final ProxySelector proxySelector;//代理選擇器
    final CookieJar cookieJar;//cookie
    final @Nullable
    Cache cache;//cache 緩存
    final @Nullable
    InternalCache internalCache;//內部緩存
    final SocketFactory socketFactory;//socket 工廠
    final @Nullable
    SSLSocketFactory sslSocketFactory;//安全套層socket工廠 用於https
    final @Nullable
    CertificateChainCleaner certificateChainCleaner;//驗證確認響應書,適用HTTPS 請求連接的主機名
    final HostnameVerifier hostnameVerifier;//主機名字確認
    final CertificatePinner certificatePinner;//證書鏈
    final Authenticator proxyAuthenticator;//代理身份驗證
    final Authenticator authenticator;//本地省份驗證
    final ConnectionPool connectionPool;//鏈接池 複用連接
    final Dns dns; //域名
    final boolean followSslRedirects;//安全套接層重定向
    final boolean followRedirects;//本地重定向
    final boolean retryOnConnectionFailure;//重試連接失敗
    final int connectTimeout;//連接超時
    final int readTimeout;//讀取超時
    final int writeTimeout;//寫入超時

這裏OkHttpClient的構造採用了建造者模式。

2.2 OkHttpClient 請求網絡

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

定義一個請求Request,Request中包含客戶請求的參數:url、method、headers、requestBody和tag,也採用了建造者模式。
實際請求網絡的是Call接口,OkHttp實現了Call.Factory接口,其真正的實現類是RealCall,OkHttp將真正的請求交給了RealCall,RealCall實現了Call中方法完成請求。這裏採用了簡單工廠模式。

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

RealCall中主要方法有

  • 同步請求:client.newCall(request).execute
  • 異步請求:client.newCall(request).enqueue(常用)

異步請求 RealCall.enqueue()

RealCall.java

 @Override public void enqueue(Callback responseCallback) {
    //TODO 不能重複執行
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    //TODO 交給 dispatcher調度器 進行調度
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
  1. synchronized (this) 確保每個call只能被執行一次不能重複執行
  2. Dispatcher 調度器 將 Call 加入隊列,並通過線程池執行 Call
    利用dispatcher調度器,來進行實際的執行client.dispatcher().enqueue(new AsyncCall(responseCallback));,在上面的OkHttpClient.Builder可以看出 已經初始化了Dispatcher。
    Dispatcher的屬性和方法
  //TODO 同時能進行的最大請求數
    private int maxRequests = 64;
    //TODO 同時請求的相同HOST的最大個數 SCHEME :// HOST [ ":" PORT ] [ PATH [ "?" QUERY ]]
    //TODO 如 https://restapi.amap.com  restapi.amap.com - host
    private int maxRequestsPerHost = 5;
    /**
     * Ready async calls in the order they'll be run.
     * TODO 雙端隊列,支持首尾兩端 雙向開口可進可出,方便移除
     * 異步等待隊列
     *
     */
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

    /**
     * Running asynchronous calls. Includes canceled calls that haven't finished yet.
     * TODO 正在進行的異步隊列
     */
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

Dispatcher管理兩個異步請求隊列,可對多個併發網絡請求進行處理。
Dispatcher.enqueue方法用於執行異步請求,實現如下

//TODO 執行異步請求
    synchronized void enqueue(AsyncCall call) {
        //TODO 同時請求不能超過併發數(64,可配置調度器調整)
        //TODO okhttp會使用共享主機即 地址相同的會共享socket
        //TODO 同一個host最多允許5條線程通知執行請求
        if (runningAsyncCalls.size() < maxRequests &&
                runningCallsForHost(call) < maxRequestsPerHost) {
            //TODO 加入運行隊列 並交給線程池執行
            runningAsyncCalls.add(call);
            //TODO AsyncCall 是一個runnable,放到線程池中去執行,查看其execute實現
            executorService().execute(call);
        } else {
            //TODO 加入等候隊列
            readyAsyncCalls.add(call);
        }
    }

可見Dispatcher將Call加入隊列中(若同時請求數未超過最大值,則加入運行隊列,放到線程池中執行;否則加入等待隊列),然後通過線程池執行call。
executorService() 本質上是一個線程池執行方法,用於創建一個線程池

 public synchronized ExecutorService executorService() {
        if (executorService == null) {
            //TODO 線程池的相關概念 需要理解
            //TODO 核心線程 最大線程 非核心線程閒置60秒回收 任務隊列
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher",
                    false));
        }
        return executorService;
    }
  1. 在 ConnectionPool 線程池中執行請求 AsyncCall
    異步請求中Call實際上爲AsyncCall,繼承自NamedRunnable
final class AsyncCall extends NamedRunnable

NamedRunnable

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();
}

因此 AsyncCall 其實就是一個 Runnable,線程池實際上就是執行了execute()。
AsyncCall的execute()

 final class AsyncCall extends NamedRunnable {
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        //TODO 責任鏈模式
        //TODO 攔截器鏈  執行請求
        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 {
        //TODO 移除隊列
        client.dispatcher().finished(this);
      }
    }
  }

從上述代碼可以看出真正執行請求的是getResponseWithInterceptorChain(); 然後通過回調將Response返回給用戶。

  1. 通過攔截器鏈 RealInterceptorChain 通過 責任鏈模式 真正執行網絡請求
    真正的執行網絡請求和返回響應結果:getResponseWithInterceptorChain()
//TODO 核心代碼 開始真正的執行網絡請求
  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //TODO 責任鏈
    List<Interceptor> interceptors = new ArrayList<>();
    //TODO 在配置okhttpClient 時設置的intercept 由用戶自己設置
    interceptors.addAll(client.interceptors());
    //TODO 負責處理失敗後的重試與重定向
    interceptors.add(retryAndFollowUpInterceptor);
    //TODO 負責把用戶構造的請求轉換爲發送到服務器的請求 、把服務器返回的響應轉換爲用戶友好的響應 處理 配置請求頭等信息
    //TODO 從應用程序代碼到網絡代碼的橋樑。首先,它根據用戶請求構建網絡請求。然後它繼續呼叫網絡。最後,它根據網絡響應構建用戶響應。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //TODO 處理 緩存配置 根據條件(存在響應緩存並被設置爲不變的或者響應在有效期內)返回緩存響應
    //TODO 設置請求頭(If-None-Match、If-Modified-Since等) 服務器可能返回304(未修改)
    //TODO 可配置用戶自己設置的緩存攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //TODO 連接服務器 負責和服務器建立連接 這裏纔是真正的請求網絡
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //TODO 配置okhttpClient 時設置的networkInterceptors
      //TODO 返回觀察單個網絡請求和響應的不可變攔截器列表。
      interceptors.addAll(client.networkInterceptors());
    }
    //TODO 執行流操作(寫出請求體、獲得響應數據) 負責向服務器發送請求數據、從服務器讀取響應數據
    //TODO 進行http請求報文的封裝與請求報文的解析
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //TODO 創建責任鏈
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    //TODO 執行責任鏈
    return chain.proceed(originalRequest);
  }

一共執行6種攔截器,一種是用戶自定義攔截器client.interceptors(),另外五種是OkHttp自帶攔截器:

  • RetryAndFollowUpInterceptor,重試那些失敗或者redirect的請求。
  • BridgeInterceptor,請求之前對響應頭做了一些檢查,並添加一些頭,然後在請求之後對響應做一些處理(gzip解壓or設置cookie)。
  • CacheInterceptor,根據用戶是否有設置cache,如果有的話,則從用戶的cache中獲取當前請求的緩存。
  • ConnectInterceptor,複用連接池中的連接,如果沒有就與服務器建立新的socket連接。
  • CallServerInterceptor,負責發送請求和獲取響應。

從上述代碼中,可以看出都實現了Interceptor接口,這是okhttp最核心的部分,採用責任鏈的模式來使每個功能分開,每個Interceptor自行完成自己的任務,並且將不屬於自己的任務交給下一個,簡化了各自的責任和邏輯。

責任鏈模式是OkHttp中最核心的設計模式。

我們着重分析一下,okhttp的設計實現,如何通過責任鏈來進行傳遞返回數據的。上述代碼中可以看出interceptors,是傳遞到了RealInterceptorChain該類實現了Interceptor.Chain,並且執行了chain.proceed(originalRequest)。核心代碼就是chain.proceed() 通過該方法進行責任鏈的執行。

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    calls++;
    //TODO 獲取下一個攔截鏈,即鏈中的攔截器集合index+1
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    //TODO 執行當前的攔截器-如果在配置okhttpClient,時沒有設置intercept默認是先執行:retryAndFollowUpInterceptor 攔截器
    Interceptor interceptor = interceptors.get(index);
    //TODO 執行下一個攔截器
    Response response = interceptor.intercept(next);
    return response;
  }

從上述代碼,我們可以知道,獲取下一個(index+1)RealInterceptorChain 責任鏈,然後 執行當前(index)責任鏈interceptors.get(index),最後返回下一個責任鏈的Response。
其實就是按責任鏈順序遞歸執行了攔截器
這樣設計的一個好處就是,責任鏈中每個攔截器都會執行chain.proceed()方法之前的代碼,等責任鏈最後一個攔截器執行完畢後會返回最終的響應數據,而chain.proceed() 方法會得到最終的響應數據,這時就會執行每個攔截器的chain.proceed()方法之後的代碼,其實就是對響應數據的一些操作。執行過程如下圖:
在這裏插入圖片描述
CacheInterceptor 緩存攔截器就是很好的證明,我們來通過CacheInterceptor 緩存攔截器來進行分析,大家就會明白了。
CacheInterceptor 的實現如下:
首先我們先分析上部分代碼當沒有網絡的情況下是如何處理獲取緩存的。

  @Override public Response intercept(Chain chain) throws IOException
  {
//TODO 獲取request對應緩存的Response 如果用戶沒有配置緩存攔截器 cacheCandidate == null
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    //TODO 執行響應緩存策略
    long now = System.currentTimeMillis();
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //TODO 如果networkRequest == null 則說明不使用網絡請求
    Request networkRequest = strategy.networkRequest;
    //TODO 獲取緩存中(CacheStrategy)的Response
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      cache.trackResponse(strategy);
    }
    //TODO 緩存無效 關閉資源
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    //TODO networkRequest == null 不實用網路請求 且沒有緩存 cacheResponse == null  返回失敗
    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();
    }

    //TODO 不使用網絡請求 且存在緩存 直接返回響應
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    }

上述的代碼,主要做了幾件事:
如果用戶自己配置了緩存攔截器,cacheCandidate = cache.Response 獲取用戶自己存儲的Response,否則 cacheCandidate = null;同時從CacheStrategy 獲取cacheResponse 和 networkRequest
如果cacheCandidate != null 而 cacheResponse == null 說明緩存無效清除cacheCandidate緩存。
如果networkRequest == null 說明沒有網絡,cacheResponse == null 沒有緩存,返回失敗的信息,責任鏈此時也就終止,不會在往下繼續執行。
如果networkRequest == null 說明沒有網絡,cacheResponse != null 有緩存,返回緩存的信息,責任鏈此時也就終止,不會在往下繼續執行。
上部分代碼,其實就是沒有網絡的時候的處理。
那麼下部分代碼肯定是,有網絡的時候處理

    //TODO 執行下一個攔截器
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    //TODO 網絡請求 回來 更新緩存
    // If we have a cache response too, then we're doing a conditional get.
    //TODO 如果存在緩存 更新
    if (cacheResponse != null) {
      //TODO 304響應碼 自從上次請求後,請求需要響應的內容未發生改變
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
    //TODO 緩存Response
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    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);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

下部分代碼主要做了這幾件事:
執行下一個攔截器,也就是請求網絡
責任鏈執行完畢後,會返回最終響應數據,如果緩存存在更新緩存,如果緩存不存在加入到緩存中去。
這樣就體現出了,責任鏈這樣實現的好處了,當責任鏈執行完畢,如果攔截器想要拿到最終的數據做其他的邏輯處理等,這樣就不用在做其他的調用方法邏輯了,直接在當前的攔截器就可以拿到最終的數據。
這也是okhttp設計的最優雅最核心的功能。

  1. 執行調度器完成方法,移除隊列
    AsyncCall的execute()中(見第3步)中finally 執行了client.dispatcher().finished(this); 通過調度器移除隊列,並且判斷是否存在等待隊列,如果存在,檢查執行隊列是否達到最大值,如果沒有將等待隊列變爲執行隊列。這樣也就確保了等待隊列被執行。
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
        int runningCallsCount;
        Runnable idleCallback;
        synchronized (this) {
            //TODO calls 移除隊列
            if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
            //TODO 檢查是否爲異步請求,檢查等候的隊列 readyAsyncCalls,如果存在等候隊列,則將等候隊列加入執行隊列
            if (promoteCalls) promoteCalls();
            //TODO 運行隊列的數量
            runningCallsCount = runningCallsCount();
            idleCallback = this.idleCallback;
        }
        //閒置調用
        if (runningCallsCount == 0 && idleCallback != null) {
            idleCallback.run();
        }
    }
    
    private void promoteCalls() {
        //TODO 檢查 運行隊列 與 等待隊列
        if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
        if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

        //TODO 將等待隊列加入到運行隊列中
        for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall call = i.next();
            //TODO  相同host的請求沒有達到最大,加入運行隊列
            if (runningCallsForHost(call) < maxRequestsPerHost) {
                i.remove();
                runningAsyncCalls.add(call);
                executorService().execute(call);
            }

            if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
        }
    }

同步請求 RealCall.excute()

//TODO 同步執行請求 直接返回一個請求的結果
  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    //TODO 調用監聽的開始方法
    eventListener.callStart(this);
    try {
      //TODO 交給調度器去執行
      client.dispatcher().executed(this);
      //TODO 獲取請求的返回數據
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      //TODO 執行調度器的完成方法 移除隊列
      client.dispatcher().finished(this);
    }
  }
  1. synchronized (this) 避免重複執行
  2. Dispatcher 調度器 將 Call 加入同步執行隊列
//TODO 調度器執行同步請求
    synchronized void executed(RealCall call) {
        runningSyncCalls.add(call);
    }
  1. 通過攔截器鏈 RealInterceptorChain 通過 責任鏈模式 真正執行網絡請求
    getResponseWithInterceptorChain()最核心的代碼,請求網絡得到響應數據,返回給用戶。
  2. 執行調度器完成方法,移除隊列
    client.dispatcher().finished(this); 執行調度器的完成方法 移除隊列

2.3 OkHttp 執行流程總結

  1. OkhttpClient 實現了Call.Fctory,負責爲Request 創建 Call;
  2. RealCall 爲Call的具體實現,其enqueue() 異步請求接口通過Dispatcher()調度器利用ExcutorService實現,而最終進行網絡請求時和同步的execute()接口一致,都是通過 getResponseWithInterceptorChain() 函數實現
  3. getResponseWithInterceptorChain() 中利用 Interceptor 鏈條,責任鏈模式 分層實現緩存、透明壓縮、網絡 IO 等功能;最終將響應數據返回給用戶。

2.4 OkHttp 設計模式總結

  1. 建造者模式
    創建者模式又叫建造者模式,是將一個複雜的對象的構建與它的表示分離,使
    得同樣的構建過程可以創建不同的表示。創建者模式隱藏了複雜對象的創建過程,它把複雜對象的創建過程加以抽象,通過子類繼承或者重載的方式,動態的創建具有複合屬性的對象。
    OkHttp中HttpClient、Request構造便是通過建造者模式
    OkHttpClient
mOkHttpClient = new OkHttpClient.Builder()
                            .addInterceptor(loggingInterceptor)
                            .retryOnConnectionFailure(true)
                            .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .readTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .build();

Request

 Request request = new Request.Builder()
      .url(url)
      .build();
  1. 簡單工廠模式
    okhttp 實現了Call.Factory接口
interface Factory {
    Call newCall(Request request);
  }

我們看一下okhttpClient 如何實現的Call接口,代碼如下

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

可以看出 真正的請求交給了 RealCall 類,並且RealCall 實現了Call方法,RealCall是真正的核心代碼。

  1. 責任鏈模式
    責任鏈模式(Chain of Responsibility Pattern)爲請求創建了一個接收者對象的鏈。這種模式給予請求的類型,對請求的發送者和接收者進行解耦。這種類型的設計模式屬於行爲型模式。
    在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。
    OkHttp責任鏈驗證
    (1)模擬攔截器接口
public interface Interceptor {
    String interceptor(Chain chain);

    interface Chain {
        String request();

        String proceed(String request);
    }
}

(2)實現具體攔截器

public class BridgeInterceptor implements Interceptor {
    @Override
    public String interceptor(Chain chain) {
        System.out.println("執行 BridgeInterceptor 攔截器之前代碼");
        String proceed = chain.proceed(chain.request());
        System.out.println("執行 BridgeInterceptor 攔截器之後代碼 得到最終數據:"+proceed);
        return proceed;
    }
}

public class RetryAndFollowInterceptor implements Interceptor {
    @Override
    public String interceptor(Chain chain) {
        System.out.println("執行 RetryAndFollowInterceptor 攔截器之前代碼");
        String proceed = chain.proceed(chain.request());
        System.out.println("執行 RetryAndFollowInterceptor 攔截器之後代碼 得到最終數據:" + proceed);
        return proceed;
    }
}

public class CacheInterceptor implements Interceptor {
    @Override
    public String interceptor(Chain chain) {
        System.out.println("執行 CacheInterceptor 最後一個攔截器 返回最終數據");
        return "success";
    }
}

(3)實現攔截器鏈接口

public class RealInterceptorChain implements Interceptor.Chain {

    private List<Interceptor> interceptors;

    private int index;

    private String request;

    public RealInterceptorChain(List<Interceptor> interceptors, int index, String request) {
        this.interceptors = interceptors;
        this.index = index;
        this.request = request;
    }

    @Override
    public String request() {
        return request;
    }

    @Override
    public String proceed(String request) {
        if (index >= interceptors.size()) return null;

        //獲取下一個責任鏈
        RealInterceptorChain next = new RealInterceptorChain(interceptors, index+1, request);
        // 執行當前的攔截器
        Interceptor interceptor = interceptors.get(index);

        return interceptor.interceptor(next);
    }
}

(4)測試及結果

List<Interceptor> interceptors = new ArrayList<>();
        interceptors.add(new BridgeInterceptor());
        interceptors.add(new RetryAndFollowInterceptor());
        interceptors.add(new CacheInterceptor());

        RealInterceptorChain request = new RealInterceptorChain(interceptors, 0, "request");

        request.proceed("request");

打印出的log日誌

執行 BridgeInterceptor 攔截器之前代碼
執行 RetryAndFollowInterceptor 攔截器之前代碼
執行 CacheInterceptor 最後一個攔截器 返回最終數據
執行 RetryAndFollowInterceptor 攔截器之後代碼 得到最終數據:success
執行 BridgeInterceptor 攔截器之後代碼 得到最終數據:success

這也是OkHttp的核心設計思想

(三)源碼解讀

3.1 重要的類

(1)Route路由:對地址Adress的一個封裝類
RouteSelector路由選擇器:在OKhttp中其實其作用也就是返回一個可用的Route對象
(2)Platform平臺:用於針對不同平臺適應性
(3)RealConnection連接:Okhttp正式發起網絡請求所使用的對象
ConnectionPool連接池:管理HTTP和SPDY連接的重用,減少網絡延遲。連接池是將已經創建好的連接保存在一個緩衝池中,當有請求來時,直接使用已經創建好的連接。
每次請求生成StreamAllocation對象請求鏈接時,首先要做的不是new 一個新的RealConnection對象,而是從鏈接池中獲取已經存在的並且可以複用的RealConnection,如果找不到可用的鏈接,則才new 一個新的RealConnection,並將新的連接放入連接池,等待其他請求複用
(4)Call請求(Request\Response):代表實際的http請求,它是連接Request和response的橋樑。由於重寫,重定向,跟進和重試,你簡單的請求Call可能產生多個請求Request和響應Response。OkHttp會使用Call來模化滿足請求的任務,然而中間的請求和響應是必要的(重定向處理和IP出錯)
Call執行有兩種方式:
Synchronous:線程會阻塞直到響應可讀。
Asynchronous:在一個線程中入隊請求,當你的響應可讀時在另外一個線程獲取回調。
線程中的請求取消、失敗、未完成,寫請求主體和讀響應主體代碼會遇到IOException
(5)Dispatchar調度器:Dispatcher是okhttp3的任務調度核心類,負責管理同步和異步的請求,管理每一個請求任務的請求狀態,並且其內部維護了一個線程池用於執行相應的請求,Dispatcher實現框架:
在這裏插入圖片描述
Dispatcher維護了三個隊列

 private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();//等待執行的異步隊列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();//正在執行的異步隊列
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();//同步隊列
一個線程池
  executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));

5.1)Deque是一個雙向隊列接口,Deque接口具有豐富的抽象數據形式,它支持從隊列兩端點檢索和插入元素
5.2)當需要執行的線程大於所能承受的最大範圍時,就把未能及時執行的任務保存在readyAsyncCalls隊列中。當線程池有空餘線程可以執行時,會調用promoteCall()方法把等待隊列readyAsyncCalls中的任務放到線程池執行,並把任務轉移到runningAsyncCalls隊列中

(6)Interceptor攔截器:攔截器是一個強大的機制,它可以監控,重寫和重試Calls,OkHttp自帶攔截器有5種:
1、RetryAndFollowUpInterceptor,重試那些失敗或者redirect的請求。
2、BridgeInterceptor,請求之前對響應頭做了一些檢查,並添加一些頭,然後在請求之後對響應做一些處理(gzip解壓or設置cookie)。
3、CacheInterceptor,根據用戶是否有設置cache,如果有的話,則從用戶的cache中獲取當前請求的緩存。
4、ConnectInterceptor,複用連接池中的連接,如果沒有就與服務器建立新的socket連接。
5、CallServerInterceptor,負責發送請求和獲取響應。
(7)Cache緩存:響應緩存
(8)OkHttpClient客戶端:OkHttpClient是okhttp3框架的客戶端,用於發送http請求(Requests)和讀取讀取交易返回數據(Responses)

3.2 請求流程

(1)創建OkHttpClient

創建OkHttpClient對象,OkHttpClient是okhttp3框架的客戶端,用於發送http請求(Requests)和讀取讀取交易返回數據(Responses)。
官方建議使用單例創建OkHttpClient,即一個進程中只創建一次即可,以後的每次交易都使用該實例發送交易。這是因爲OkHttpClient擁有自己的連接池和線程池,這些連接池和線程池可以重複使用,這樣做利於減少延遲和節省內存。

//採用構建者模式
OkHttpClient client = new OkHttpClient.Builder().build();

自定義Builder內部的每一個參數屬性

(2)創建Call對象,併發起同步/異步請求

一個Call對象表示一次請求,Call其實是一個接口對象,它的具體實現類是RealCall

//採用構建者模式
OkHttpClient client = new OkHttpClient.Builder().build();
Request request = new Request.Builder().
        url("https://github.com/cozing").
        build();
Call call = client.newCall(request);

2.1)傳入Request對象,表示客戶請求的參數

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Object tag;

2.2)Call創建,實際創建RealCall對象

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

RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
  final EventListener.Factory eventListenerFactory = client.eventListenerFactory();

  this.client = client;
  this.originalRequest = originalRequest;
  this.forWebSocket = forWebSocket;
  this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);

  // TODO(jwilson): this is unsafe publication and not threadsafe.
  this.eventListener = eventListenerFactory.create(this);
}

okhttp3中提供了兩種交易方式:一種是同步請求,第二種是異步請求。同步請求調用call.execute()方法,異步請求調用call.enqueue(Callback callback)方法

(3)Dispatcher 調度器對同步/異步請求進行處理

3.1)同步請求
call.execute()【RealCall.java】

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  try {
//1.調用調度器dispatcher的executed方法
    client.dispatcher().executed(this);
//2.通過攔截器對請求數據與攔截數據進行處理
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
//3.調用調度器dispatcher的finish方法
    client.dispatcher().finished(this);
  }
}

1.調用調度器dispatcher的executed方法,將請求的RealCall放入runningSyncCalls隊列中【Dispatcher.java】

public final class Dispatcher {
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
synchronized void executed(RealCall call) {
  runningSyncCalls.add(call);
}

3.2)異步請求
call.enqueue(Callback callback)實現方式【RealCall.java】

@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
//調用調度器dispatcher的enqueue方法
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

調度器dispatcher的equeue(new AsyncCall(responseCallback))方法【dispatcher.java】

private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    //如果當前運行中的請求數小於設定的最大請求數64,且當前運行中同一主機的請求數小於設定的最大請求數5
    runningAsyncCalls.add(call); //將本次請求call加入正在執行異步隊列
    executorService().execute(call); //執行請求call
  } else {
    readyAsyncCalls.add(call); //將本次請求call加入等待執行異步隊列,等到有空閒的請求線程時,會從該隊列中取出請求並執行
  }
}

執行請求executorService().execute(call)【dispatcher.java】
調度器Dispatcher內部維護了一個ThreadPoolExecutor線程池executorService(),並直接將call對象傳入線程池執行。這裏的Call對象的實現對象是AsyncCall是Call的內部類,繼承自NamedRunnable,用來開啓一個線程。
當網絡請求線程池執行該線程的run()方法時,會調用AsyncCall的execute()的方法,最後在execute()方法內部調用了和同步請求方法一樣的getResponseWithInterceptorChain()。

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;
}

AsyncCall

final class AsyncCall extends NamedRunnable {
  private final Callback responseCallback;

  AsyncCall(Callback responseCallback) {
    super("OkHttp %s", redactedUrl());
    this.responseCallback = responseCallback;
  }

  String host() {
    return originalRequest.url().host();
  }

  Request request() {
    return originalRequest;
  }

  RealCall get() {
    return RealCall.this;
  }

  @Override 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 {
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
      client.dispatcher().finished(this);
    }
  }
}
(4)getResponseWithInterceptorChain()/攔截器鏈

【okhttp3中的精髓設計之一】
這個方法是通過攔截器鏈對請求數據和返回數據進行處理,內部採用責任鏈模式,將每一個攔截器對應負責的處理任務進行嚴格分配,最後將交易結果返回並回調到暴露給調用者的接口上。
在這裏插入圖片描述
在這裏插入圖片描述
依次添加了用戶自定義的interceptor、retryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、 networkInterceptors、CallServerInterceptor,並將這些攔截器傳遞給了這個RealInterceptorChain。攔截器之所以可以依次調用,並最終再從後先前返回Response。
getResponseWithInterceptorChain()【RealCall.java】

Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());//用戶自定義攔截器
  interceptors.add(retryAndFollowUpInterceptor); //重試和重定向攔截器
  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, null, null, null, 0, originalRequest);
  return chain.proceed(originalRequest);
}

調用攔截器具體處理類RealInterceptorChain的proceed()方法來實現攔截:
執行當前攔截器的Intercept方法,並調用下一個(index+1)攔截器。下一個(index+1)攔截器的調用依賴於當前攔截器的Intercept方法中,對RealInterceptorChain的proceed方法的調用
【XXInterceptor】

return realChain.proceed(request, streamAllocation, httpCodec, connection);

當前攔截器的Response依賴於下一個攔截器的Intercept的Response。因此,就會沿着這條攔截器鏈依次調用每一個攔截器,當執行到最後一個攔截器之後,就會沿着相反的方向依次返回Response,最終得到我們需要的“終極版”Response。
【RealInterceptorChain】

@Override public Response proceed(Request request) throws IOException {
  return proceed(request, streamAllocation, httpCodec, connection);
}

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
    RealConnection connection) throws IOException {
  if (index >= interceptors.size()) throw new AssertionError();

  calls++;

  // If we already have a stream, confirm that the incoming request will use it.
  if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
    throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
        + " must retain the same host and port");
  }

  // If we already have a stream, confirm that this is the only call to chain.proceed().
  if (this.httpCodec != null && calls > 1) {
    throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
        + " must call proceed() exactly once");
  }

  // Call the next interceptor in the chain.
  RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
      connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
      writeTimeout);
  Interceptor interceptor = interceptors.get(index);
  Response response = interceptor.intercept(next);

  // Confirm that the next interceptor made its required call to chain.proceed().
  if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
    throw new IllegalStateException("network interceptor " + interceptor
        + " must call proceed() exactly once");
  }

  // Confirm that the intercepted response isn't null.
  if (response == null) {
    throw new NullPointerException("interceptor " + interceptor + " returned null");
  }

  if (response.body() == null) {
    throw new IllegalStateException(
        "interceptor " + interceptor + " returned a response with no body");
  }

  return response;
}
(5)client.dispatcher().finished(this);

將本次請求從隊列中移除。若是異步請求,則在一個異步請求結束後,將一個符合條件的異步請求從等待運行隊列中放入運行隊列中,並開始執行

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
  int runningCallsCount;
  Runnable idleCallback;
  synchronized (this) {
    if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
    if (promoteCalls) promoteCalls();
    runningCallsCount = runningCallsCount();
    idleCallback = this.idleCallback;
  }

  if (runningCallsCount == 0 && idleCallback != null) {
    idleCallback.run();
  }
}

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.
  }
}

3.3 同步與異步(線程池)的實現

3.4 攔截器

(一)處理網絡響應的攔截器機制

okHttp處理網絡響應時採用的是攔截器機制,對interceptors依此調用。
okHttp自帶的攔截器包括重連、組裝請求頭部、讀/寫緩存、建立socket連接、向服務器發送請求/接收響應攔截器。
用戶可添加自定義Interceptor,包括應用攔截器和網絡攔截器

  1. 調用OkHttpClient.Builder的addInterceptor()可以添加應用攔截器,只會被調用一次,可以處理網絡請求回來的最終Response
  2. 調用addNetworkInterceptor()可以添加network攔截器,處理所有的網絡響應(一次請求如果發生了redirect ,那麼這個攔截器的邏輯可能會被調用兩次)
public class OkHttpClient implements Cloneable, Call.Factory {
  final List<Interceptor> interceptors;
  final List<Interceptor> networkInterceptors;  ......  }
(二)Interceptor解析

攔截器鏈通過責任鏈的模式,將網絡請求過程中的職責功能都分割開,分別用不同的攔截器來完成失敗重連、緩存處理、網絡連接等問題。而且用戶還可以添加自定義的攔截器,非常靈活,滿足面向對象的開閉原則。

(1)RetryAndFollowUpInterceptor

該攔截器主要的作用負責失敗自動重連和必要的重定向。當一個請求由於各種原因失敗了,如果是路由或者連接異常,則嘗試恢復,否則,根據響應碼(ResponseCode),重定向方法會對Request進行再處理以得到新的Request,然後沿着攔截器鏈繼續新的Request。當然,如果responseCode是200的話,這些過程就結束了。

(2)BridgeInterceptor

BridgeInterceptor的主要作用就是爲請求(request before)添加請求頭,爲響應(Response Before)添加響應頭。
負責將用戶的Request轉換成一個實際的網絡請求Request,再調用下一個攔截器獲取Response,然後將Response轉換成用戶的Response。

(3)CacheInterceptor

負責控制緩存,緩存的邏輯就在這裏面
okHttp緩衝機制:
(3.1)HTTP緩衝機制
HTTP緩存有多種規則,根據是否需要重新向服務器發起請求來分類,我將其分爲兩大類(強制緩存,對比緩存)
(3.1.1)強制緩存
在這裏插入圖片描述
從上文我們得知,強制緩存,在緩存數據未失效的情況下,可以直接使用緩存數據,那麼瀏覽器是如何判斷緩存數據是否失效呢?
我們知道,在沒有緩存數據的時候,瀏覽器向服務器請求數據時,服務器會將數據和緩存規則一併返回,緩存規則信息包含在響應header中。
對於強制緩存來說,響應header中會有兩個字段來標明失效規則(Expires/Cache-Control)

  • Expires(失效)
    Expires的值爲服務端返回的到期時間,即下一次請求時,請求時間小於服務端返回的到期時間,直接使用緩存數據。
    不過Expires 是HTTP 1.0的東西,現在默認瀏覽器均默認使用HTTP 1.1,所以它的作用基本忽略。另一個問題是,到期時間是由服務端生成的,但是客戶端時間可能跟服務端時間有誤差,這就會導致緩存命中的誤差。所以HTTP 1.1 的版本,使用Cache-Control替代。
  • Cache-Control
    Cache-Control 是最重要的規則。常見的取值有private、public、no-cache、max-age,no-store,默認爲private。
    private:客戶端可以緩存
    public:客戶端和代理服務器都可緩存(前端的同學,可以認爲public和private是一樣的)
    max-age=xxx:緩存的內容將在 xxx 秒後失效(若在xxx秒內再次請求這條數據,都會直接獲取緩存數據庫中的數據,直接使用。)
    no-cache:需要使用對比緩存來驗證緩存數據
    no-store:所有內容都不會緩存,強制緩存,對比緩存都不會觸發

(3.1.2)對比緩存
在這裏插入圖片描述
對比緩存,顧名思義,需要進行比較判斷是否可以使用緩存。
瀏覽器第一次請求數據時,服務器會將緩存標識與數據一起返回給客戶端,客戶端將二者備份至緩存數據庫中。再次請求數據時,客戶端將備份的緩存標識發送給服務器,服務器根據緩存標識進行判斷,判斷成功後,返回304狀態碼,通知客戶端比較成功,可以使用緩存數據。
在對比緩存生效時,狀態碼爲304,並且報文大小和請求時間大大減少。
原因是,服務端在進行標識比較後,只返回header部分,通過狀態碼通知客戶端使用緩存,不再需要將報文主體部分返回給客戶端。對於對比緩存來說,緩存標識的傳遞是我們着重需要理解的,它在請求header和響應header間進行傳遞,一共分爲兩種標識傳遞:

  • Last-Modified / If-Modified-Since
  • Last-Modified:
    服務器在響應請求時,告訴瀏覽器資源的最後修改時間。
    在這裏插入圖片描述
  • If-Modified-Since:
    再次請求服務器時,通過此字段通知服務器上次請求時,服務器返回的資源最後修改時間。
    服務器收到請求後發現有頭If-Modified-Since 則與被請求資源的最後修改時間進行比對。
    若資源的最後修改時間大於If-Modified-Since,說明資源又被改動過,則響應整片資源內容,返回狀態碼200;
    若資源的最後修改時間小於或等於If-Modified-Since,說明資源無新修改,則響應HTTP 304,告知瀏覽器繼續使用所保存的cache。
    在這裏插入圖片描述
  • Etag / If-None-Match(優先級高於Last-Modified / If-Modified-Since)
  • Etag:
    服務器響應請求時,告訴瀏覽器當前資源在服務器的唯一標識(生成規則由服務器決定)。
    在這裏插入圖片描述
  • If-None-Match:
    再次請求服務器時,通過此字段通知服務器客戶段緩存數據的唯一標識。
    服務器收到請求後發現有頭If-None-Match 則與被請求資源的唯一標識進行比對,
    不同,說明資源又被改動過,則響應整片資源內容,返回狀態碼200;
    相同,說明資源無新修改,則響應HTTP 304,告知瀏覽器繼續使用所保存的cache。
    在這裏插入圖片描述
    對於強制緩存,服務器通知瀏覽器一個緩存時間,在緩存時間內,下次請求,直接用緩存,不在時間內,執行比較緩存策略。
    對於比較緩存,將緩存信息中的Etag和Last-Modified通過請求發送給服務器,由服務器校驗,返回304狀態碼時,瀏覽器直接使用緩存。
    在這裏插入圖片描述
    在這裏插入圖片描述
    強制緩存如果生效,不需要再和服務器發生交互,而對比緩存不管是否生效,都需要與服務端發生交互。
    兩類緩存規則可以同時存在,強制緩存優先級高於對比緩存,也就是說,當執行強制緩存的規則時,如果緩存生效,直接使用緩存,不再執行對比緩存規則。

(3.2)重要的類
1、緩存Cache
Cache來自OkHttpClient
Cache中採用了DiskLruCache,以Request的URL的md5爲key,相應Response爲value。此外Cache中還通過外觀模式對外提供了InternalCache接口變量,用於調用Cache中的方法,也滿足面向對象的接口隔離原則和依賴倒置原則等。
DiskLruCache和LruCache內部都是使用了LinkedHashMap去實現緩存算法的,只不過前者針對的是將緩存存在硬盤(/sdcard/Android/data//cache),而後者是直接將緩存存在內存;
2、緩存策略CacheStrategy
CacheStrategy的內部工廠類Factory中有一個getCandidate方法,會根據實際的請求生成對應的CacheStrategy類返回,是個典型的簡單工廠模式。其內部維護一個request和response,通過指定request和response來告訴CacheInterceptor是使用緩存還是使用網絡請求,亦或兩者同時使用。
(3.2)緩存框架
(3.3)源碼分析

@Override public Response intercept(Chain chain) throws IOException {
  // 1.如果設置緩存並且當前request有緩存,則從緩存Cache中獲取當前請求request的緩存response
  Response cacheCandidate = cache != null
      ? cache.get(chain.request())
      : null;

  long now = System.currentTimeMillis();
  // 2.傳入的請求request和獲取的緩存response通過緩存策略對象CacheStragy的工廠類get方法根據一些規則獲取緩存策略CacheStrategy(這裏的規則根據請求的request和緩存的Response的header頭部信息生成的,比如是否有noCache標誌位,是否是immutable不可變,緩存是否過期等等)
  CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
  // 3.生成的CacheStrategy有2個變量,networkRequest和cacheRequest,如果networkRequest爲Null表示不進行網絡請求,如果cacheResponse爲null,則表示沒有有效緩存 
  Request networkRequest = strategy.networkRequest;
  Response cacheResponse = strategy.cacheResponse;
  // 4.緩存不可用,關閉
  if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
  }
  // 5.如果networkRequest和cacheResponse都爲Null,則表示不請求網絡且緩存爲null,返回504,請求失敗
  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();
  }
  // 6.如果不請求網絡,但存在緩存,則不請求網絡,直接返回緩存,結束,不執行下一個攔截器
  if (networkRequest == null) {
    return cacheResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .build();
  }
  // 7.否則,請求網絡,並調用下一個攔截器鏈,將請求轉發到下一個攔截器
  Response networkResponse = null;
  try {
    networkResponse = chain.proceed(networkRequest);
  } finally {
    // If we're crashing on I/O or otherwise, don't leak the cache body.
    if (networkResponse == null && cacheCandidate != null) {
      closeQuietly(cacheCandidate.body());
    }
  }
//8.請求網絡,並且網絡請求返回HTTP_NOT_MODIFIED,說明緩存有效,則合併網絡響應和緩存結果,同時更新緩存
  if (cacheResponse != null) {
    if (networkResponse.code() == HTTP_NOT_MODIFIED) {
      Response response = cacheResponse.newBuilder()
          .headers(combine(cacheResponse.headers(), networkResponse.headers()))
          .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
          .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
          .cacheResponse(stripBody(cacheResponse))
          .networkResponse(stripBody(networkResponse))
          .build();
      networkResponse.body().close();
      // Update the cache after combining headers but before stripping the
      // Content-Encoding header (as performed by initContentStream()).
      cache.trackConditionalCacheHit();
      cache.update(cacheResponse, response);
      return response;
    } else {
      closeQuietly(cacheResponse.body());
    }
  }
  //9.若沒有緩存,則寫入緩存
  Response response = networkResponse.newBuilder()
      .cacheResponse(stripBody(cacheResponse))
      .networkResponse(stripBody(networkResponse))
      .build();
  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);
    }
  }
  return response;
}

(3.4)流程

  1. 如果本地沒有緩存,直接發送網絡請求;
    cacheResponse == null
  2. 如果當前請求是Https,而緩存沒有TLS握手,則重新發起網絡請求;
    request.isHttps() && cacheResponse.handshake() == null
  3. 如果當前的緩存策略是不可緩存,直接發送網絡請求;
    !isCacheable(cacheResponse, request)
  4. 請求頭no-cache或者請求頭包含If-Modified-Since或者If-None-Match,則需要服務器驗證本地緩存是不是還能繼續使用,直接網絡請求;
    requestCaching.noCache() || hasConditions(request)
  5. 可緩存,並且ageMillis + minFreshMillis < freshMillis + maxStaleMillis(意味着雖過期,但可用,只是會在響應頭添加warning),則使用緩存;
  6. 緩存已經過期,添加請求頭:If-Modified-Since或者If-None-Match,進行網絡請求;
(4)ConnectInterceptor(核心,連接池)

okhttp的一大特點就是通過連接池來減小響應延遲。如果連接池中沒有可用的連接,則會與服務器建立連接,並將socket的io封裝到HttpStream(發送請求和接收response)中
(4.1)重要的類
HttpCodec(Stream):數據交換的流,對請求的編碼以及對響應數據的解碼
(Stream:基於Connection的邏輯Http請求/響應對)
RealConnecton(Collection):Connection實現類,主要實現連接的建立等工作;
Http中Stream和Collection關係:
Http1(Http1.0)1:1一個連接只能被一個請求流使用
Http2(Http1.1)1:n一個連接可被多個請求流同時使用,且keep-alive機制保證連接使用完不關閉,當下一次請求與連接的Host相同時,連接可以直接使用,不用再次創建
StreamAllocation(流分配):會通過ConnectPool獲取或者創建一個RealConnection來得到一個連接到Server的Connection連接,同時會生成一個HttpCodec用於下一個CallServerInterceptor,以完成最終的請求;
RouteDataBase:這是一個關於路由信息的白名單和黑名單類,處於黑名單的路由信息會被避免不必要的嘗試;
ConnectionPool:連接池,實現連接的複用;
(4.2)連接流程框架
在這裏插入圖片描述
(4.3)連接流程源碼
創建一個StreamAllocation並且分配一個Connection和HttpCodec,爲最終的請求做準備

@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Request request = realChain.request();
  StreamAllocation streamAllocation = realChain.streamAllocation();
  // We need the network to satisfy this request. Possibly for validating a conditional GET.
  boolean doExtensiveHealthChecks = !request.method().equals("GET");
  HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
  RealConnection connection = streamAllocation.connection();
  return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

1、 HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
newStream.java

public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
}

findHealthyConnection.java找到一個可用連接

/**
 * 找可用連接,如果連接不可用,便會一直持續查找
 */
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
    int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws IOException {
  while (true) {
    RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
        connectionRetryEnabled);
    // 如果是新的連接,則直接返回.
    synchronized (connectionPool) {
      if (candidate.successCount == 0) {
        return candidate;
      }
    }
    //判斷連接池中連接是否可用,如果不可用,則釋放該連接並從連接池中移除,並繼續尋找可用連接
    if (!candidate.isHealthy(doExtensiveHealthChecks)) {
      noNewStreams();
      continue;
    }
    return candidate;
  }
}

findConnection.java

/**
 * 返回連接到Host的新流.首選已存在的流,再選連接池的流,最後創建一個新流 
 */
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled) throws IOException {
  Route selectedRoute;
  synchronized (connectionPool) {
    // 1.首選已存在的流
    RealConnection allocatedConnection = this.connection;
// 2.從連接池中取得連接(以URL爲Key)
    Internal.instance.get(connectionPool, address, this, null);
    selectedRoute = route;
  }
  // 3.如果當前路由爲空,選擇下一條路由.
  if (selectedRoute == null) {
    selectedRoute = routeSelector.next();
  }
  synchronized (connectionPool) {
    //4.根據IP地址和Route從連接池進行第二次查找
    Internal.instance.get(connectionPool, address, this, selectedRoute);
    //5.根據route創建一個RealConnection
    route = selectedRoute; 
    result = new RealConnection(connectionPool, selectedRoute);
    acquire(result);
  }
  // 6.建立Socket連接
  result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
  routeDatabase().connected(result.route());
  synchronized (connectionPool) {
    // 6.將生成的connection放入連接池
    Internal.instance.put(connectionPool, result);
    // 7.多路複用
    if (result.isMultiplexed()) {
      socket = Internal.instance.deduplicate(connectionPool, address, this);
      result = connection;
    }

private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
    Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    source = Okio.buffer(Okio.source(rawSocket));
    sink = Okio.buffer(Okio.sink(rawSocket));
}

2、RealConnection connection = streamAllocation.connection();
(4.4)連接流程步驟
1.框架使用URL和配置好的OkHttpClient創建一個address。此地址指定我們將如何連接到網絡服務器。
2.框架通過address從連接池中取回一個連接。
3.如果沒有在池中找到連接,ok會選擇一個route嘗試連接。這通常意味着使用一個DNS請求, 以獲取服務器的IP地址。如果需要,ok還會選擇一個TLS版本和代理服務器。
4.如果獲取到一個新的route,它會與服務器建立一個直接的socket連接、使用TLS安全通道(基於HTTP代理的HTTPS),或直接TLS連接。它的TLS握手是必要的。
5.開始發送HTTP請求並讀取響應。
如果有連接出現問題,OkHttp將選擇另一條route,然後再試一次。這樣的好處是當服務器地址的一個子集不可達時,OkHttp能夠自動恢復。而且當連接池過期或者TLS版本不受支持時,這種方式非常有用。
一旦響應已經被接收到,該連接將被返回到池中,以便它可以在將來的請求中被重用。連接在池中閒置一段時間後,它會被趕出。

(5)CallServerInterceptor

CallServerInterceptor的intercept()方法裏 負責發送請求和獲取響應,實際上都是由HttpStream類去完成具體的工作。
一個socket連接用來發送HTTP/1.1消息,這個類嚴格按照以下生命週期:
1、 writeRequestHeaders()發送request header
httpCodec.writeRequestHeaders(request);
2、打開一個sink來寫request body,然後關閉sink

Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());

3、readResponseHeaders()讀取response頭部

responseBuilder = httpCodec.readResponseHeaders(true);

4、打開一個source來讀取response body,然後關閉source

3.5 緩存策略

3.6 連接池

3.7 重連機制

3.8 Gzip的使用方式

3.9 安全性

3.10 平臺適應性

四.Volley

4.1 簡介

Volley的意思是"羣發"、“併發”。
Volley是Google在2013年發佈的一個Android網絡通信庫,使網絡通信更快、更簡單、更健壯。適用於數據不大但頻繁的網絡通信。

在開發Android應用的時候不可避免地都需要用到網絡技術,而多數情況下應用程序都會使用HTTP協議來發送和接收網絡數據。Android系統中主要提供了兩種方式來進行HTTP通信,HttpURLConnection和HttpClient,不過HttpURLConnection和HttpClient的用法還是稍微有些複雜的,如果不進行適當封裝的話,很容易就會寫出不少重複代碼。於是,一些Android網絡通信框架也就應運而生,比如說AsyncHttpClient,它把HTTP所有的通信細節全部封裝在了內部,我們只需要簡單調用幾行代碼就可以完成通信操作了。再比如Universal-Image-Loader,它使得在界面上顯示網絡圖片的操作變得極度簡單,開發者不用關心如何從網絡上獲取圖片,也不用關心開啓線程、回收圖片資源等細節,Universal-Image-Loader已經把一切都做好了。
Android開發團隊也是意識到了有必要將HTTP的通信操作再進行簡單化,於是在2013年Google
I/O大會上推出了一個新的網絡通信框架——Volley。Volley可是說是把AsyncHttpClient和Universal-Image-Loader的優點集於了一身,既可以像AsyncHttpClient一樣非常簡單地進行HTTP通信,也可以像Universal-Image-Loader一樣輕鬆加載網絡上的圖片。除了簡單易用之外,Volley在性能方面也進行了大幅度的調整,它的設計目標就是非常適合去進行數據量不大,但通信頻繁的網絡操作,而對於大數據量的網絡操作,比如說下載文件等,Volley的表現就會非常糟糕。

4.2 特點

優點

  1. 通信更快,更簡單
  2. Get、Post網絡請求及網絡圖像的高效率異步處理請求
  3. 對網絡請求進行排序與優先級處理
  4. 網絡請求的緩存
  5. 多級別取消請求(對於多個網絡請求可進行取消)
  6. 和Activity生命週期的聯動(Activity生命週期結束時撤銷後臺所有網絡請求)

缺點

  1. 不適合進行較大文件上傳和下載
    Volley的網絡請求線程池默認大小爲4。意味着可以併發進行4個請求,大於4個,會排在隊列中。如果所有的網絡線程都被上傳文件的任務佔滿了,你的網絡請求只有在文件上傳完畢後才能得到執行。就會使其他網絡請求很慢。
    Request#getBody() 方法返回byte[]類型,作爲 Http.POST 和 Http.PUT body 中的數據。這就意味着需要把用 http 傳輸的數據一股腦讀取到內存中。如果文件過大,內存佔用就很高,很容易oom。
  2. 只支持HTTP請求

4.3 使用

4.3.1 Volley的get和post請求方式的使用

本質上是對Android原生Get和Post請求進行二次封裝,並進行性能、效率的優化,在使用Get與Post請求方法前,我們需要確定請求接口數據類型(所請求數據返回的類型),Volley自帶了三種返回類型:

  • StringRequest:請求數據類型不確定(涵蓋後兩種)
  • JsonObjectRequest:請求數據類型爲JsonObject
  • JsonArrayRequest:請求數據類型爲JsonArray

(1)網絡請求之Get方式請求數據

1、使用Get方式請求數據返回StringRequest對象
當想返回String類型的請求結果數據或者不清楚返回什麼類型時可以用StringRequest對象。
下面使用Get請求方式返回一個String類型的手機歸屬地信息。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        volleyGet();
    }

    /**
     *  new StringRequest(int method,String url,Listener listener,ErrorListener errorListener)
     *  method:請求方式,Get請求爲Method.GET,Post請求爲Method.POST
     *  url:請求地址
     *  listener:請求成功後的回調
     *  errorListener:請求失敗的回調
     */
    private void volleyGet() {
        String url = "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=15850781443";
        StringRequest request = new StringRequest(Method.GET, url,
                new Listener<String>() {
                    @Override
                    public void onResponse(String s) {//s爲請求返回的字符串數據
                        Toast.makeText(MainActivity.this,s,Toast.LENGTH_LONG).show();
                    }
                },
                new ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError volleyError) {
                        Toast.makeText(MainActivity.this,volleyError.toString(),Toast.LENGTH_LONG).show();
                    }
                });
        //設置請求的Tag標籤,可以在全局請求隊列中通過Tag標籤進行請求的查找
        request.setTag("testGet");
        //將請求加入全局隊列中
        MyApplication.getHttpQueues().add(request);
    }
}

2、使用Get方式請求數據返回JsonObjectRequest對象
由於我們知道這個請求返回的是Json格式的數據,所以我們可以直接使用JsonObjectRequest作爲請求返回結果對象。
下面使用Get請求方式返回一個Json格式的IP地址信息

/**
 *  new JsonObjectRequest(int method,String url,JsonObject jsonObject,Listener listener,ErrorListener errorListener)
 *  method:請求方式,Get請求爲Method.GET,Post請求爲Method.POST
 *  url:請求地址
 *  JsonObject:Json格式的請求參數。如果使用的是Get請求方式,請求參數已經包含在url中,所以可以將此參數置爲null
 *  listener:請求成功後的回調
 *  errorListener:請求失敗的回調
 */
private void volleyGet() {
    String url = "http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=218.4.255.255";
    JsonObjectRequest request = new JsonObjectRequest(Method.GET, url, null,
            new Listener<JSONObject>() {
                @Override
                public void onResponse(JSONObject jsonObject) {//jsonObject爲請求返回的Json格式數據
                    Toast.makeText(MainActivity.this,jsonObject.toString(),Toast.LENGTH_LONG).show();
                }
            },
            new ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError volleyError) {
                    Toast.makeText(MainActivity.this,volleyError.toString(),Toast.LENGTH_LONG).show();
                }
            });

    //設置請求的Tag標籤,可以在全局請求隊列中通過Tag標籤進行請求的查找
    request.setTag("testGet");
    //將請求加入全局隊列中
    MyApplication.getHttpQueues().add(request);
}

返回JsonArrayRequest對象的方法與JsonObjectRequest比較類似。
運行結果
在這裏插入圖片描述

(2)網絡請求之Post方式請求數據

1、使用Post方式請求數據返回StringRequest對象
使用Post方式需要手動傳遞請求參數,可以重寫Request類的getParams()方法將請求參數名和參數值放入Map中進行傳遞。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        volleyPost();
    }

    /**
     * 使用Post方式返回String類型的請求結果數據
     * 
     *  new StringRequest(int method,String url,Listener listener,ErrorListener errorListener)
     *  method:請求方式,Get請求爲Method.GET,Post請求爲Method.POST
     *  url:請求地址
     *  listener:請求成功後的回調
     *  errorListener:請求失敗的回調
     */
    private void volleyPost() {
        String url = "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm";
        StringRequest request = new StringRequest(Method.POST, url,
                new Listener<String>() {
                    @Override
                    public void onResponse(String s) {//s爲請求返回的字符串數據
                        Toast.makeText(MainActivity.this,s,Toast.LENGTH_LONG).show();
                    }
                },
                new ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError volleyError) {
                        Toast.makeText(MainActivity.this,volleyError.toString(),Toast.LENGTH_LONG).show();
                    }
                }){
                    @Override
                    protected Map<String, String> getParams() throws AuthFailureError {
                        Map<String,String> map = new HashMap<>();
                        //將請求參數名與參數值放入map中
                        map.put("tel","15850781443");
                        return map;
                    }
                }
                ;
        //設置請求的Tag標籤,可以在全局請求隊列中通過Tag標籤進行請求的查找
        request.setTag("testPost");
        //將請求加入全局隊列中
        MyApplication.getHttpQueues().add(request);
    }
}

2、使用Post方式請求數據返回JsonObject對象

/**
 *  使用Post方式返回JsonObject類型的請求結果數據
 *
 *  new JsonObjectRequest(int method,String url,JsonObject jsonObject,Listener listener,ErrorListener errorListener)
 *  method:請求方式,Get請求爲Method.GET,Post請求爲Method.POST
 *  url:請求地址
 *  JsonObject:Json格式的請求參數。如果使用的是Get請求方式,請求參數已經包含在url中,所以可以將此參數置爲null;如果用Post方式,則可以將請求參數直接放在JsonObject進行傳遞
 *  listener:請求成功後的回調
 *  errorListener:請求失敗的回調
 */
private void volleyPost() {
    String url = "http://www.kuaidi100.com/query";
    Map<String,String> map = new HashMap<>();
    map.put("type","yuantong");
    map.put("postid","229728279823");
    //將map轉化爲JSONObject對象
    JSONObject jsonObject = new JSONObject(map);

    JsonObjectRequest request = new JsonObjectRequest(Method.POST, url, jsonObject,
            new Listener<JSONObject>() {
                @Override
                public void onResponse(JSONObject jsonObject) {//jsonObject爲請求返回的Json格式數據
                    Toast.makeText(MainActivity.this,jsonObject.toString(),Toast.LENGTH_LONG).show();
                }
            },
            new ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError volleyError) {
                    Toast.makeText(MainActivity.this,volleyError.toString(),Toast.LENGTH_LONG).show();
                }
            });
    //設置請求的Tag標籤,可以在全局請求隊列中通過Tag標籤進行請求的查找
    request.setTag("testPost");
    //將請求加入全局隊列中
    MyApplication.getHttpQueues().add(request);
}

4.3.2 Volley的網絡請求隊列建立和取消隊列請求

使用Volley需要建立一個全局的請求隊列,這樣我們就可以將一個請求加入到這個全局隊列中,並可以管理整個APP的所有請求,包括取消一個或所有的請求。

(1)建立請求隊列(使用Singleton模式)

如果您的應用程序不斷使用網絡,可能最有效的方法是設置一個可以延長應用程序生命週期的RequestQueue實例。
要達到這種目標,有各種方式可以實現。 推薦的方法是實現封裝RequestQueue和其他Volley功能的單例類。 另一種方法是在Application.onCreate()中繼承Application並設置RequestQueue, 但這種做法是不鼓勵的, 靜態單例可以以更模塊化的方式提供相同的功能。
一個關鍵的概念是RequestQueue必須用Application context而不是Activity context來實例化。 這樣可以確保RequestQueue在您的應用程序生命週期中持續存在,而不是每次重新創建Activity時重新創建(例如,當用戶旋轉設備時)。
提供RequestQueue和ImageLoader功能的單例類的例子:

public class MySingleton {
    private static MySingleton mInstance;
    private RequestQueue mRequestQueue;//全局請求隊列
    private ImageLoader mImageLoader;//全局圖片加載器
    private static Context mCtx;//這裏的上下文爲Application上下文,確保請求隊列在應用程序生命週期持續存在

    private MySingleton(Context context) {
        mCtx = context;
        mRequestQueue = getRequestQueue();

        mImageLoader = new ImageLoader(mRequestQueue,
                new ImageLoader.ImageCache() {
                    private final LruCache<String, Bitmap>
                            cache = new LruCache<String, Bitmap>(20);

                    @Override
                    public Bitmap getBitmap(String url) {
                        return cache.get(url);
                    }

                    @Override
                    public void putBitmap(String url, Bitmap bitmap) {
                        cache.put(url, bitmap);
                    }
                });
    }
//獲取請求隊列實例
    public static synchronized MySingleton getInstance(Context context) {
        if (mInstance == null) {
            mInstance = new MySingleton(context);
        }
        return mInstance;
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            // getApplicationContext() is key, it keeps you from leaking the Activity or BroadcastReceiver if someone passes one in.防止內存泄露
            mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
        }
        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req) {
        getRequestQueue().add(req);
    }

    public ImageLoader getImageLoader() {
        return mImageLoader;
    }
}

使用RequestQueue

// Get a RequestQueue
RequestQueue queue = MySingleton.getInstance(this.getApplicationContext()).
    getRequestQueue();

// ...

// Add a request (in this example, called stringRequest) to your RequestQueue.
MySingleton.getInstance(this).addToRequestQueue(stringRequest);

(2)取消網絡請求

2.1 取消某個request操作

@Override  
   public void onStop() {  
       for (Request <?> req : mRequestQueue) {  
           req.cancel();  
       }  
   } 

2.2 取消請求隊列中所有請求

    @Override  
    protected void onStop() {  
        // TODO Auto-generated method stub  
        super.onStop();  
        mRequestQueue.cancelAll(this);  
    }  

2.3 根據請求標記的Tag終止某個請求
1、給請求添加標籤tag

request.setTag("My Tag");
mRequestQueue.add(request);

2、取消所有指定標籤tag的請求,這樣只會取消指定tag的請求,而其他請求不受影響

mRequestQueue.cancelAll("My Tag");

用於Volley與Activity生命週期的聯動。

4.3.3 Volley與Activity生命週期的聯動

Activity裏面啓動了網絡請求,而在這個網絡請求還沒返回結果的時候,Activity被結束了,此時如果繼續使用其中的Context等,除了無辜的浪費CPU,電池,網絡等資源,有可能還會導致程序crash,所以,我們需要處理這種一場情況。使用Volley的話,我們可以在Activity停止的時候,同時取消所有或部分未完成的網絡請求。Volley裏所有的請求結果會返回給主進程,如果在主進程裏取消了某些請求,則這些請求將不會被返回給主線程。Volley支持多種request取消方式。
簡單來說就是Volley中的請求是與Activity的生命週期進行關聯。這樣可以在Android銷燬時關閉Volley的請求,防止請求在後臺運行造成內存溢出等情況發生。與Activity生命週期進行聯動時需要設置Tag標籤,因爲取消請求需要在請求隊列中通過Tag標籤進行查找,在Activity的onStop中執行取消請求的操作。
(1)爲request請求設置標籤(一般爲activity標籤)

		String TAG = "VOLLEY_ACTIVITY"
		...
        //設置請求的Tag標籤,可以在全局請求隊列中通過Tag標籤進行請求的查找
        request.setTag(TAG);
        //將請求加入全局隊列中
        MyApplication.getHttpQueues().add(request);

(2)在Activity關閉時(onStop)取消請求隊列中的請求

@Override
protected void onStop() {
    super.onStop();
    //通過Tag標籤取消請求隊列中對應的全部請求
    MyApplication.getHttpQueues().cancelAll("abcGet");
}

4.3.4 Volley加載網絡圖片

(1)加載圖片緩存功能:LruCache、ImageCache
(2)加載網絡圖片及監聽:ImageRequest、ImageLoader、NetWorkImageView

(1)使用ImageRequest加載網絡圖片

public class MainActivity extends AppCompatActivity {
    private ImageView image;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        image = (ImageView) findViewById(R.id.image);
        loadImageByVolley();
    }

    /**
     *  通過Volley加載網絡圖片
     *
     *  new ImageRequest(String url,Listener listener,int maxWidth,int maxHeight,Config decodeConfig,ErrorListener errorListener)
     *  url:請求地址
     *  listener:請求成功後的回調
     *  maxWidth、maxHeight:設置圖片的最大寬高,如果均設爲0則表示按原尺寸顯示
     *  decodeConfig:圖片像素的儲存方式。Config.RGB_565表示每個像素佔2個字節,Config.ARGB_8888表示每個像素佔4個字節等。
     *  errorListener:請求失敗的回調
     */
    private void loadImageByVolley() {
        String url = "http://pic20.nipic.com/20120409/9188247_091601398179_2.jpg";
        ImageRequest request = new ImageRequest(
                            url,
                            new Listener<Bitmap>() {
                                @Override
                                public void onResponse(Bitmap bitmap) {
                                    image.setImageBitmap(bitmap);
                                }
                            },
                            0, 0, Config.RGB_565,
                            new ErrorListener() {
                                @Override
                                public void onErrorResponse(VolleyError volleyError) {
                                    image.setImageResource(R.mipmap.ic_launcher);
                                }
                            });

        //設置請求的Tag標籤,可以在全局請求隊列中通過Tag標籤進行請求的查找
        request.setTag("loadImage");
        //通過Tag標籤取消請求隊列中對應的全部請求
        MyApplication.getHttpQueues().add(request);
    }

}

(2)使用ImageLoader加載及緩存網絡圖片

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private ImageView image;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        image = (ImageView) findViewById(R.id.image);
        loadImageWithCache();
    }
/**
     *  通過ImageLoader加載及緩存網絡圖片
   *
     *  new ImageLoader(RequestQueue queue,ImageCache imageCache)
     *  queue:請求隊列
     *  imageCache:一個用於圖片緩存的接口,一般需要傳入它的實現類
     *
     *  getImageListener(ImageView view, int defaultImageResId, int errorImageResId)
     *  view:ImageView對象
     *  defaultImageResId:默認的圖片的資源Id
     *  errorImageResId:網絡圖片加載失敗時顯示的圖片的資源Id
     */
    private void loadImageWithCache() {
        String url = "http://pic20.nipic.com/20120409/9188247_091601398179_2.jpg";
        ImageLoader loader = new ImageLoader(MyApplication.getHttpQueues(), new BitmapCache());
        ImageListener listener = loader.getImageListener(image,R.mipmap.ic_launcher,R.mipmap.ic_launcher);
        //加載及緩存網絡圖片
        loader.get(url,listener);
    }
}

BitmapCache.java

public class BitmapCache implements ImageLoader.ImageCache{
    //LruCache是基於內存的緩存類
    private LruCache<String,Bitmap> lruCache;
    //LruCache的最大緩存大小
    private int max = 10 * 1024 * 1024;

    public BitmapCache() {
        lruCache = new LruCache<String, Bitmap>(max){
            @Override
            //緩存圖片的大小
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight();
            }
        };
    }

    @Override
    public Bitmap getBitmap(String s) {
        return lruCache.get(s);
    }

    @Override
    public void putBitmap(String s, Bitmap bitmap) {
        lruCache.put(s,bitmap);
    }
}

4.3.5 Volley的二次回調封裝

自定義二次回調方法,方便全局使用統一的接口(而不需要在每次回調重新複寫)。可控、可自定義定製需求;且方便,靈活。
1、自定義抽象接口,封裝請求成功回調方法與請求失敗回調方法

public abstract class VolleyInterface {
    //上下文
    public static Context context;
    //請求成功
    public static Response.Listener<String> listener;
    //請求失敗
    public static Response.ErrorListener errorListener;

    public VolleyInterface(Context mContext, Response.Listener<String> mListener, Response.ErrorListener mErrorListener){
        this.context = mContext;
        this.listener = mListener;
        this.errorListener = mErrorListener;
    }

    public abstract void onMySuccess(String result);
    public abstract void onMyError(VolleyError error);

    //請求成功的監聽方法
    public Response.Listener<String> loadingListener(){
        //請求成功實例化
        listener = new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
                //這裏可以添加成功提示
                onMySuccess(s);
            }
        };
        return listener;
    }
    //請求失敗的監聽方法
    public Response.ErrorListener errorListener(){
        //請求失敗實例化
        errorListener = new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                onMyError(volleyError);
                //這裏可以添加成功提示
            }
        };
        return errorListener;
    }
}

2、創建一個網絡請求管理類VolleyRequest

public class VolleyRequest {
    public static StringRequest stringRequest;
    public static Context context;


    public static void RequestGet(
        Context mContext,//上下文
        String url,
        String tag,
        VolleyInterface vif)//請求回調的接口,請求成功和失敗
        {
        //防止重複請求,刪除所有相同標籤的請求
        MyApplication.getHttpQueue().cancelAll(tag);
        stringRequest = new StringRequest(Request.Method.GET,url,
                vif.loadingListener(),vif.errorListener());
        stringRequest.setTag(tag);
        //請求添加到隊列中
        MyApplication.getHttpQueue().add(stringRequest);
        MyApplication.getHttpQueue().start();
    }

    public static void RequestPost(
          Context mcontext,//上下文
          String url, 
          String tag, 
          final Map<String,String> params,//請求參數,通過Map傳遞
          VolleyInterface vif)//回調接口
          {
          //獲取請求隊列,cancelAll防止重複請求,刪掉所有相同標籤的請求
        MyApplication.getHttpQueue().cancelAll(tag);
        stringRequest = new StringRequest(url,vif.loadingListener(),
                vif.errorListener()){
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                return params;
            }
        };
        stringRequest.setTag(tag);
        MyApplication.getHttpQueue().add(stringRequest);
        MyApplication.getHttpQueue().start();
    }
}

3、使用

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        volley_Get();
    }

    public void volley_Get() {
        String url=
        "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=15850781443";
        VolleyRequest.RequestGet(this, url, "abcGet",
                new VolleyInterface   (this,VolleyInterface.listener,VolleyInterface.errorListener) {
                    @Override
                    public void onMySuccess(String result) {
                        Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onMyError(VolleyError error) {
                        Toast.makeText(MainActivity.this, error.toString(), Toast.LENGTH_SHORT).show();
                    }
                });
    }
}

4.4 工作原理/源碼分析

1、Volley原理圖
Google官方Volley原理圖
在這裏插入圖片描述
2、Volley工作原理
右上圖可知,Volley的整個過程有三類線程在工作:

  • 主線程:所有的請求結果都會被分發到主線程。
  • 緩存線程(子線程):Volley默認開啓1個cache Thread用於讀取本地緩存/處理本地緩存隊列(cacheQueue)。
  • 網絡線程(子線程):Volley默認開啓4個network Thread用於處理網絡請求(也可以自己創建更多網絡請求)/處理網絡請求隊列(networkQueue)。

開發者無需關心子線程的內部實現流程,只需在主線程中將一個Request請求加入請求隊列RequestQueue,然後在主線程中實現一個接口回調拿到請求結果Response即可。即Volley在主線程中投放一個任務,並且異步地在主線程中獲得任務的結果。Volley簡化開發者搭建了一個網絡請求異步任務框架。

Volley初始化以後就創建了5個後臺線程在處理請求。只要你沒做處理,這5個線程一直在後臺跑。爲了節省資源,在同一個App中最好使用同一個單例Volley RequestQueue隊列來處理所有請求,以免創建過多線程浪費資源。還有在推出這個應用時,應該調用 RequestQueue#stop方法來幹掉所有Volley線程。如此纔是使用Volley最優雅的方式

Volley請求處理是一個異步的過程:(Volley的工作從:將一個Request請求添加到RequestQueue請求隊列中開始)

  1. 在主線程中默認先將請求提交給Cache Thread來處理,Cache Thread按照請求的優先級把Request添加到本地緩存隊列CacheQueue中,
  2. 緩存分發器CacheDispatcher輪詢本地是否已經緩存了這次請求的結果,
  3. 如果命中,則從緩存中讀取數據並且解析。解析完的結果被分發到主線程中。因此Volley默認自動緩存網絡請求結果。
  4. 如果沒有命中,則將這次請求提交給Network Thread處理,Network Thread將請求添加到網絡請求隊列NetworkQueue中,
  5. 網絡分發器NetworkDispatcher處理髮送網絡HTTP請求,獲取響應結果並解析同時把結果寫入緩存。解析完的結果被分發到主線程中。

五.Retrofit

5.1 簡介

Retrofit是Square公司基於RESTful風格推出的網絡請求框架封裝,是基於OKHttp的網絡請求框架的二次封裝,其本質仍是OKHttp(通過HTTP進行網絡請求)。
準確來說,Retrofit 是一個 RESTful 的 HTTP 網絡請求框架的封裝。
網絡請求的工作本質是OKHttp完成,Retrofit僅負責網絡請求接口的封裝
Retrofit與OKHttp的關係如下圖:
在這裏插入圖片描述

  1. App應用程序通過 Retrofit 請求網絡,實際上是使用 Retrofit 接口層封裝請求參數、Header、Url 等信息,之後由 OkHttp 完成後續的請求操作。
  2. 在服務端返回數據之後,OkHttp 將原始的結果交給 Retrofit,Retrofit根據用戶的需求對結果進行解析。

5.2 特點

  1. 基於OKHttp & 遵循Restful API設計風格

REST,即Representational State Transfer的縮寫。直接翻譯的意思是"表現層狀態轉化"。
它是一種互聯網應用程序的API設計理念:
URL定位資源+用HTTP動詞描述操作。
常用的HTTP動詞有下面五個:
1.GET(SELECT):從服務器取出資源(一項或多項)。
2.POST(CREATE):在服務器新建一個資源。
3.PUT(UPDATE):在服務器更新資源(客戶端提供改變後的完整資源)。
4.PATCH(UPDATE):在服務器更新資源(客戶端提供改變的屬性)。
5.DELETE(DELETE):從服務器刪除資源。
REST簡單來說就是url地址中只包含名詞表示資源,使用http動詞表示動作進行操作資源

  1. 功能強大
    (1)支持同步 & 異步網絡請求
    (2)支持多種數據的解析 & 序列化格式(Gson、Json、XML、Protobuf)
    (3)提供對Rxjava支持

RxJava是一個在Java VM上使用可觀測的序列來組成異步的、基於事件的程序的庫。
雖然,在Android中,我們可以使用AsyncTask來完成異步任務操作,但是當任務的梳理比較多的時候,我們要爲每個任務定義一個AsyncTask就變得非常繁瑣。
RxJava能幫助我們在實現異步執行的前提下保持代碼的清晰。
它的原理就是創建一個Observable來完成異步任務,組合使用各種不同的鏈式操作,來實現各種複雜的操作,最終將任務的執行結果發射給Observer進行處理。
當然,RxJava不僅適用於Android,也適用於服務端等各種場景。
簡單來說,RxJava2.0是非常好用的一個異步鏈式庫,遵循觀察者模式。

  1. 簡介易用
    (1)通過註解配置網絡請求參數
    (2)採用大量設計模式簡化使用
  2. 可擴展性好
    (1)功能模塊高度封裝
    (2)解耦徹底:如:自定義Converters

Retrofit適用於任何網絡請求的需求場景都應優先選擇,特別是後臺API遵循Restful API設計風格&項目中使用到Rxjava。

5.3 使用

實例:採用Post方法對 有道API 發送網絡請求,

  1. 添加Retrofit庫的依賴(包括:Retrofit開源庫、OkHttp網絡庫、數據解析器繼承、註冊網絡權限)
//依賴包導入
dependencies {
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    compile 'com.squareup.okhttp3:okhttp:3.4.1'
    compile 'com.squareup.retrofit2:converter-gson:2.0.2'
  }
  //網絡權限
  <uses-permission android:name="android.permission.INTERNET"/>
  1. 創建 接收服務器返回數據(網絡請求數據類型) 的類 與 用於描述網絡請求 的接口
    服務器返回數據的類應根據返回數據的格式和數據解析方式(Json、XML等)定義
    根據 有道API 返回數據Json格式,創建 接收服務器返回數據 的類:
public class Translation {

    private String type;
    private int errorCode;
    private int elapsedTime;
    private List<List<TranslateResultBean>> translateResult;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public int getElapsedTime() {
        return elapsedTime;
    }

    public void setElapsedTime(int elapsedTime) {
        this.elapsedTime = elapsedTime;
    }

    public List<List<TranslateResultBean>> getTranslateResult() {
        return translateResult;
    }

    public void setTranslateResult(List<List<TranslateResultBean>> translateResult) {
        this.translateResult = translateResult;
    }

    public static class TranslateResultBean {
        /**
         * src : merry me
         * tgt : 我快樂
         */

        public String src;
        public String tgt;

        public String getSrc() {
            return src;
        }

        public void setSrc(String src) {
            this.src = src;
        }

        public String getTgt() {
            return tgt;
        }

        public void setTgt(String tgt) {
            this.tgt = tgt;
        }
    }

}

創建用於描述網絡請求的接口,採用註解描述網絡請求參數
Retrofit將 Http請求 抽象成 Java接口:採用 註解 描述網絡請求參數 和配置網絡請求參數
原理是採用 動態代理 動態 將該接口的註解“翻譯”成一個 Http 請求,最後再執行 Http 請求

注:接口中的每個方法的參數都需要使用註解標註,否則會報錯

註解類型總結
在這裏插入圖片描述

public interface PostRequest_Interface {

    @POST("translate?doctype=json&jsonversion=&type=&keyfrom=&model=&mid=&imei=&vendor=&screen=&ssid=&network=&abtest=")
    @FormUrlEncoded
    Call<Translation1> getCall(@Field("i") String targetSentence);
    //採用@Post表示Post方法進行請求(傳入部分url地址)
    // 採用@FormUrlEncoded註解的原因:API規定採用請求格式x-www-form-urlencoded,即表單形式
    // 需要配合@Field 向服務器提交需要的字段
    // getCall() = 接收網絡請求數據的方法
    // 其中返回類型爲Call<*>,*是接收數據的類(即上面定義的Translation類)
    // 如果想直接獲得Responsebody中的內容,可以定義網絡請求返回值爲Call<ResponseBody>
}
  1. 創建 Retrofit 實例,設置數據解析器(Converter)與網絡請求適配器(CallAdapter)
    數據解析器(Converter):Retrofit支持多種數據解析方式,使用時需要在Gradle添加依賴
數據解析器 Gradle依賴
Gson com.squareup.retrofit2:converter-gson:2.0.2
Jackson com.squareup.retrofit2:converter-jackson:2.0.2
Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2

網絡請求適配器(CallAdapter):
Retrofit支持多種網絡請求適配器方式:guava、Java8和rxjava
使用時如使用的是 Android 默認的 CallAdapter,則不需要添加網絡請求適配器的依賴,否則則需要按照需求進行添加
Retrofit 提供的 CallAdapter
使用時需要在Gradle添加依賴:

網絡請求適配器 Gradle依賴
guava com.squareup.retrofit2:adapter-guava:2.0.2
Java8 com.squareup.retrofit2:adapter-java8:2.0.2
rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2
  1. 創建 網絡請求接口實例 並 配置網絡請求參數
  2. 調用接口方法返回Call對象,併發送網絡請求(異步 / 同步)(封裝了 數據轉換、線程切換的操作)
  3. 處理服務器返回的數據
    通過response類的body()對返回的數據進行處理
public class PostRequest extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        request();
    }
    public void request() {

        //步驟4:創建Retrofit對象
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fanyi.youdao.com/") // 設置 網絡請求 Url
                .addConverterFactory(GsonConverterFactory.create()) //設置使用Gson解析(記得加入依賴)
                //.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平臺
                .build();

        // 步驟5:創建 網絡請求接口 的實例
        PostRequest_Interface request = retrofit.create(PostRequest_Interface.class);

        //對 發送請求 進行封裝(設置需要翻譯的內容)
        Call<Translation1> call = request.getCall("I love you");

        //步驟6:發送網絡請求(異步)
        call.enqueue(new Callback<Translation1>() {

            //請求成功時回調
            @Override
            public void onResponse(Call<Translation1> call, Response<Translation1> response) {
                // 步驟7:處理返回的數據結果:輸出翻譯的內容
                System.out.println(response.body().getTranslateResult().get(0).get(0).getTgt());
            }

            //請求失敗時回調
            @Override
            public void onFailure(Call<Translation1> call, Throwable throwable) {
                System.out.println("請求失敗");
                System.out.println(throwable.getMessage());
            }
        });
    }
}

5.4 工作原理/源碼分析

(5.4.1)工作流程

Retrofit工作流程與一般網絡通信過程類似,它是通過使用大量的設計模式進行功能模塊的解耦,使得網絡通信過程進行得更加簡單 & 流暢。
在這裏插入圖片描述
具體過程解釋如下:

  1. 通過解析 網絡請求接口的註解 配置 網絡請求參數,創建請求(Build Request)
//用於描述網絡請求的接口,採用註解描述網絡請求參數和配置網絡請求參數
public interface GetRequest_Interface {

    @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")
    Call<Translation>  getCall();
    // @GET註解的作用:採用Get方法發送網絡請求
 
    // getCall() = 接收網絡請求數據的方法
    // 其中返回類型爲Call<*>,*是接收數據的類(即上面定義的Translation類)
    // 如果想直接獲得Responsebody中的內容,可以定義網絡請求返回值爲Call<ResponseBody>
}
  1. 通過 動態代理 生成 網絡請求對象/執行器(Call)
		// 創建 網絡請求接口 的實例
        GetRequest_Interface request = retrofit.create(GetRequest_Interface.class);
        //對 發送請求 進行封裝
        Call<Reception> call = request.getCall();
  1. 通過 網絡請求適配器(CallAdapter) 將 網絡請求對象 進行平臺(Android、Rxjava、java8等)適配到具體的Call
 Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fanyi.youdao.com/") // 設置網絡請求的Url地址
                .addConverterFactory(GsonConverterFactory.create()) // 設置數據解析器
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平臺
                .build();
  1. 通過 網絡請求執行器(Call) 發送網絡請求
//發送網絡請求(異步)
        call.enqueue(new Callback<Translation>() {
            //請求成功時回調
            @Override
            public void onResponse(Call<Translation> call, Response<Translation> response) {
                //請求處理,輸出結果
                response.body().show();
            }

            //請求失敗時候的回調
            @Override
            public void onFailure(Call<Translation> call, Throwable throwable) {
                System.out.println("連接失敗");
            }
        });

// 發送網絡請求(同步)
Response<Reception> response = call.execute();
  1. 通過 數據轉換器(Converter) 解析服務器返回的數據
 Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://fanyi.youdao.com/") // 設置網絡請求的Url地址
                .addConverterFactory(GsonConverterFactory.create()) // 設置數據解析器
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 支持RxJava平臺
                .build();
  1. 通過 回調執行器(CallBackExcutor) 切換線程(子線程 ->>主線程),用戶在主線程處理返回數據
//發送網絡請求(異步)
        call.enqueue(new Callback<Translation>() {
            //請求成功時回調
            @Override
            public void onResponse(Call<Translation> call, Response<Translation> response) {
                // 對返回數據進行處理
                response.body().show();
            }

            //請求失敗時候的回調
            @Override
            public void onFailure(Call<Translation> call, Throwable throwable) {
                System.out.println("連接失敗");
            }
        });

// 發送網絡請求(同步)
  Response<Reception> response = call.execute();
  // 對返回數據進行處理(通過response類的body()處理返回數據)
  response.body().show();

(5.4.2)重要角色

在這裏插入圖片描述

(5.4.3)源碼分析

  1. 創建Retrofit實例
Retrofit retrofit = new Retrofit.Builder()
                                 .baseUrl("http://fanyi.youdao.com/")
                                 .addConverterFactory(GsonConverterFactory.create())
                                 .build();

Retrofit實例是使用建造者模式通過Builder類創建的。
建造者模式:將一個複雜對象的構建與表示分離,使用戶在不知道對象的創建細節情況下就可以直接創建複雜的對象。
(1.1)new Retrofit

<-- Retrofit類 -->
 public final class Retrofit {
  
  private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();
  // 網絡請求配置對象(對網絡請求接口中方法註解進行解析後得到的對象)
  // 作用:存儲網絡請求相關的配置,如網絡請求的方法、數據轉換器、網絡請求適配器、網絡請求工廠、基地址等
  
  private final HttpUrl baseUrl;
  // 網絡請求的url地址

  private final okhttp3.Call.Factory callFactory;
  // 網絡請求器的工廠
  // 作用:生產網絡請求器(Call)
  // Retrofit是默認使用okhttp
  
   private final List<CallAdapter.Factory> adapterFactories;
  // 網絡請求適配器工廠的集合
  // 作用:放置網絡請求適配器工廠
  // 網絡請求適配器工廠作用:生產網絡請求適配器(CallAdapter)
  // 下面會詳細說明


  private final List<Converter.Factory> converterFactories;
  // 數據轉換器工廠的集合
  // 作用:放置數據轉換器工廠
  // 數據轉換器工廠作用:生產數據轉換器(converter)

  private final Executor callbackExecutor;
  // 回調方法執行器

private final boolean validateEagerly; 
// 標誌位
// 作用:是否提前對業務接口中的註解進行驗證轉換的標誌位


<-- Retrofit類的構造函數 -->
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,  
      List<Converter.Factory> converterFactories, List<CallAdapter.Factory> adapterFactories,  
      Executor callbackExecutor, boolean validateEagerly) {  
    this.callFactory = callFactory;  
    this.baseUrl = baseUrl;  
    this.converterFactories = unmodifiableList(converterFactories); 
    this.adapterFactories = unmodifiableList(adapterFactories);   
    // unmodifiableList(list)近似於UnmodifiableList<E>(list)
    // 作用:創建的新對象能夠對list數據進行訪問,但不可通過該對象對list集合中的元素進行修改
    this.callbackExecutor = callbackExecutor;  
    this.validateEagerly = validateEagerly;  
  ...
  // 僅貼出關鍵代碼
}

成功建立一個Retrofit對象的標準:配置好Retrofit類裏的成員變量,即配置好:

  • serviceMethod:包含所有網絡請求信息的對象
  • baseUrl:網絡請求的url地址
  • callFactory:網絡請求工廠,默認使用OkHttpCall
  • adapterFactories:網絡請求適配器工廠的集合,本質上配置了網絡請求適配器工廠,默認是ExecutorCallAdapterFactory
  • converterFactories:數據轉換器工廠的集合,本質是配置了數據轉換器工廠
  • callbackExecutor:回調方法執行器,默認回調方法執行器作用:切換線程(子線程-主線程)

所謂xxxFactory、“xxx工廠”其實是設計模式中工廠模式的體現:將“類實例化的操作”與“使用對象的操作”分開,使得使用者不用知道具體參數就可以實例化出所需要的“產品”類。
這裏介紹一下:CallAdapterFactory:該Factory生產的是CallAdapter。
CallAdapter

  1. 定義
    網絡請求執行器(Call)的適配器,Call在Retrofit裏默認是OkHttpCall,在Retrofit中提供了四種CallAdapterFactory: ExecutorCallAdapterFactory(默認)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory
  2. 作用
    將默認的網絡請求執行器(OkHttpCall)轉換成適合被不同平臺來調用的網絡請求執行器形式。如:一開始Retrofit只打算利用OkHttpCall通過ExecutorCallbackCall切換線程;但後來發現使用Rxjava更加方便(不需要Handler來切換線程)。想要實現Rxjava的情況,那就得使用RxJavaCallAdapterFactoryCallAdapter將OkHttpCall轉換成Rxjava(Scheduler)。Retrofit還支持java8、Guava平臺。
// 把response封裝成rxjava的Observeble,然後進行流式操作
Retrofit.Builder.addCallAdapterFactory(newRxJavaCallAdapterFactory().create()); 
// 關於RxJava的使用這裏不作更多的展開
  1. 好處
    用最小代價兼容更多平臺,即能適配更多的使用場景

(1.2)Builder()

<-- Builder類-->
public static final class Builder {
    private Platform platform;
    private okhttp3.Call.Factory callFactory;
    private HttpUrl baseUrl;
    private List<Converter.Factory> converterFactories = new ArrayList<>();
    private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
    private Executor callbackExecutor;
    private boolean validateEagerly;

// 從上面可以發現, Builder類的成員變量與Retrofit類的成員變量是對應的
// 所以Retrofit類的成員變量基本上是通過Builder類進行配置
// 開始看步驟1

<-- 步驟1 -->
// Builder的構造方法(無參)
 public Builder() {
      this(Platform.get());
// 用this調用自己的有參構造方法public Builder(Platform platform) ->>步驟5(看完步驟2、3、4再看)
// 並通過調用Platform.get()傳入了Platform對象
// 繼續看Platform.get()方法 ->>步驟2
// 記得最後繼續看步驟5的Builder有參構造方法
    }
...
}

<-- 步驟2 -->
class Platform {

  private static final Platform PLATFORM = findPlatform();
  // 將findPlatform()賦給靜態變量

  static Platform get() {
    return PLATFORM;    
    // 返回靜態變量PLATFORM,即findPlatform() ->>步驟3
  }

<-- 步驟3 -->
private static Platform findPlatform() {
    try {

      Class.forName("android.os.Build");
      // Class.forName(xxx.xx.xx)的作用:要求JVM查找並加載指定的類(即JVM會執行該類的靜態代碼段)
      if (Build.VERSION.SDK_INT != 0) {
        return new Android(); 
        // 此處表示:如果是Android平臺,就創建並返回一個Android對象 ->>步驟4
      }
    } catch (ClassNotFoundException ignored) {
    }

    try {
      // 支持Java平臺
      Class.forName("java.util.Optional");
      return new Java8();
    } catch (ClassNotFoundException ignored) {
    }

    try {
      // 支持iOS平臺
      Class.forName("org.robovm.apple.foundation.NSObject");
      return new IOS();
    } catch (ClassNotFoundException ignored) {
    }

// 從上面看出:Retrofit2.0支持3個平臺:Android平臺、Java平臺、IOS平臺
// 最後返回一個Platform對象(指定了Android平臺)給Builder的有參構造方法public Builder(Platform platform)  --> 步驟5
// 說明Builder指定了運行平臺爲Android
    return new Platform();
  }
...
}

<-- 步驟4 -->
// 用於接收服務器返回數據後進行線程切換在主線程顯示結果

static class Android extends Platform {

    @Override
      CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {

      return new ExecutorCallAdapterFactory(callbackExecutor);
    // 創建默認的網絡請求適配器工廠
    // 該默認工廠生產的 adapter 會使得Call在異步調用時在指定的 Executor 上執行回調
    // 在Retrofit中提供了四種CallAdapterFactory: ExecutorCallAdapterFactory(默認)、GuavaCallAdapterFactory、Java8CallAdapterFactory、RxJavaCallAdapterFactory
    // 採用了策略模式
    
    }

    @Override 
      public Executor defaultCallbackExecutor() {
      // 返回一個默認的回調方法執行器
      // 該執行器作用:切換線程(子->>主線程),並在主線程(UI線程)中執行回調方法
      return new MainThreadExecutor();
    }

    static class MainThreadExecutor implements Executor {
   
      private final Handler handler = new Handler(Looper.getMainLooper());
      // 獲取與Android 主線程綁定的Handler 

      @Override 
      public void execute(Runnable r) {
        
        
        handler.post(r);
        // 該Handler是上面獲取的與Android 主線程綁定的Handler 
        // 在UI線程進行對網絡請求返回數據處理等操作。
      }
    }

// 切換線程的流程:
// 1. 回調ExecutorCallAdapterFactory生成了一個ExecutorCallbackCall對象
//2. 通過調用ExecutorCallbackCall.enqueue(CallBack)從而調用MainThreadExecutor的execute()通過handler切換到主線程
  }

// 下面繼續看步驟5的Builder有參構造方法
<-- 步驟5 -->
//  Builder類的構造函數2(有參)
  public  Builder(Platform platform) {

  // 接收Platform對象(Android平臺)
      this.platform = platform;

// 通過傳入BuiltInConverters()對象配置數據轉換器工廠(converterFactories)

// converterFactories是一個存放數據轉換器Converter.Factory的數組
// 配置converterFactories即配置裏面的數據轉換器
      converterFactories.add(new BuiltInConverters());

// BuiltInConverters是一個內置的數據轉換器工廠(繼承Converter.Factory類)
// new BuiltInConverters()是爲了初始化數據轉換器
    }

對Builder類分析完畢,總結:
Builder設置了默認的

  • 平臺類型對象Platform:Android
  • 網絡請求適配器工廠:CallAdapterFactory(CallAdapter用於對原始Call進行再次封裝,如Call到Observable)
  • 數據轉換器工廠: converterFactory
  • 回調執行器:callbackExecutor

特別注意,這裏只是設置了默認值,但未真正配置到具體的Retrofit類的成員變量當中
(1.3)baseUrl(“http://fanyi.youdao.com/”)

<-- 步驟1 -->
public Builder baseUrl(String baseUrl) {

      // 把String類型的url參數轉化爲適合OKhttp的HttpUrl類型
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);     

    // 最終返回帶httpUrl類型參數的baseUrl()
    // 下面繼續看baseUrl(httpUrl) ->> 步驟2
      return baseUrl(httpUrl);
    }


<-- 步驟2 -->
    public Builder baseUrl(HttpUrl baseUrl) {

      //把URL參數分割成幾個路徑碎片
      List<String> pathSegments = baseUrl.pathSegments();   

      // 檢測最後一個碎片來檢查URL參數是不是以"/"結尾
      // 不是就拋出異常    
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }     
      this.baseUrl = baseUrl;
      return this;
    }

總結:baseUrl()用於配置Retrofit類的網絡請求url地址:將傳入的String類型url轉化爲適合OKhttp的HttpUrl類型的url
(1.4)addConverterFactory(GsonConverterFactory.create())
GsonConverterFactory.creat()

public final class GsonConverterFactory extends Converter.Factory {

<-- 步驟1 -->
  public static GsonConverterFactory create() {
    // 創建一個Gson對象
    return create(new Gson()); ->>步驟2
  }

<-- 步驟2 -->
  public static GsonConverterFactory create(Gson gson) {
    // 創建了一個含有Gson對象實例的GsonConverterFactory
    return new GsonConverterFactory(gson); ->>步驟3
  }

  private final Gson gson;

<-- 步驟3 -->
  private GsonConverterFactory(Gson gson) {
    if (gson == null) throw new NullPointerException("gson == null");
    this.gson = gson;
  }

所以,GsonConverterFactory.creat()是創建了一個含有Gson對象實例的GsonConverterFactory,並返回給addConverterFactory()
接下來繼續看:addConverterFactory()

// 將上面創建的GsonConverterFactory放入到 converterFactories數組
// 在第二步放入一個內置的數據轉換器工廠BuiltInConverters()後又放入了一個GsonConverterFactory
  public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

總結:用於創建一個含有Gson對象實例的GsonConverterFactory並放入到數據轉換器工廠converterFactories裏。即Retrofit默認使用Gson進行解析,若使用其他解析方式(如Json、XML或Protocobuf),也可通過自定義數據解析器來實現(必須繼承 Converter.Factory)
(1.5)build()

public Retrofit build() {
 
 <--  配置網絡請求執行器(callFactory)-->
      okhttp3.Call.Factory callFactory = this.callFactory;
      // 如果沒指定,則默認使用okhttp
      // 所以Retrofit默認使用okhttp進行網絡請求
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

 <--  配置回調方法執行器(callbackExecutor)-->
      Executor callbackExecutor = this.callbackExecutor;
      // 如果沒指定,則默認使用Platform檢測環境時的默認callbackExecutor
      // 即Android默認的callbackExecutor
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

 <--  配置網絡請求適配器工廠(CallAdapterFactory)-->
      List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
      // 向該集合中添加了步驟2中創建的CallAdapter.Factory請求適配器(添加在集合器末尾)
      adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));
    // 請求適配器工廠集合存儲順序:自定義1適配器工廠、自定義2適配器工廠...默認適配器工廠(ExecutorCallAdapterFactory)

 <--  配置數據轉換器工廠:converterFactory -->
      // 在步驟2中已經添加了內置的數據轉換器BuiltInConverters()(添加到集合器的首位)
      // 在步驟4中又插入了一個Gson的轉換器 - GsonConverterFactory(添加到集合器的首二位)
      List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories);
      // 數據轉換器工廠集合存儲的是:默認數據轉換器工廠( BuiltInConverters)、自定義1數據轉換器工廠(GsonConverterFactory)、自定義2數據轉換器工廠....

// 注:
//1. 獲取合適的網絡請求適配器和數據轉換器都是從adapterFactories和converterFactories集合的首位-末位開始遍歷
// 因此集合中的工廠位置越靠前就擁有越高的使用權限

      // 最終返回一個Retrofit的對象,並傳入上述已經配置好的成員變量
      return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
          callbackExecutor, validateEagerly);
    }

總結:在最後一步中,通過前面步驟設置的變量,將Retrofit類的所有成員變量都配置完畢。
所以,成功創建了Retrofit的實例
(1.6)總結
Retrofit使用建造者模式通過Builder類建立了一個Retrofit實例,具體創建細節是配置了:

  • 平臺類型對象(Platform - Android)
    單例獲取不同平臺,如Android平臺中MainThreadExecutor
  • 網絡請求的url地址(baseUrl)
  • 網絡請求工廠(callFactory):默認使用OkHttpCall
  • 網絡請求適配器工廠的集合(adapterFactories):本質是配置了網絡請求適配器工廠- 默認是ExecutorCallAdapterFactory
    CallAdapter用於對原始Call進行再次封裝,找到對應的執行器,如RxjavaCallFactory對應的Observable,可將Call轉換爲Observable
  • 數據轉換器工廠的集合(converterFactories):本質是配置了數據轉換器工廠
    converterFactory
    數據解析Converter,將response通過converterFactory轉換成對應的數據形式,GsonConverterFactory,FastJsonConverterFactory。
  • 回調方法執行器(callbackExecutor):默認回調方法執行器作用是:切換線程(子線程 - 主線程)

由於使用了建造者模式,所以開發者並不需要關心配置細節就可以創建好Retrofit實例,建造者模式get。
在創建Retrofit對象時,你可以通過更多更靈活的方式去處理你的需求,如使用不同的Converter、使用不同的CallAdapter,這也就提供了你使用RxJava來調用Retrofit的可能

  1. 創建 網絡請求接口實例 並 配置網絡請求參數
<-- 步驟1:定義接收網絡數據的類 -->
<-- JavaBean.java -->
public class JavaBean {
  .. // 這裏就不介紹了
  }

<-- 步驟2:定義網絡請求的接口類 -->
<-- AccessApi.java -->
public interface AccessApi {
    // 註解GET:採用Get方法發送網絡請求
    // Retrofit把網絡請求的URL分成了2部分:1部分baseurl放在創建Retrofit對象時設置;另一部分在網絡請求接口設置(即這裏)
    // 如果接口裏的URL是一個完整的網址,那麼放在創建Retrofit對象時設置的部分可以不設置
    @GET("openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q=car")

    // 接受網絡請求數據的方法
    Call<JavaBean> getCall();
    // 返回類型爲Call<*>,*是解析得到的數據類型,即JavaBean
}

<-- 步驟3:在MainActivity創建接口類實例  -->
AccessApi NetService = retrofit.create(AccessApi.class);
       
<-- 步驟4:對發送請求的url進行封裝,即生成最終的網絡請求對象  --> 
        Call<JavaBean> call = NetService.getCall();

(2.1)源碼分析
Retrofit是通過外觀模式 & 代理模式 使用create()方法創建網絡請求接口的實例。同時,通過網絡請求接口裏設置的註解進行了網絡請求參數的配置

外觀模式:定義一個統一接口,外部與通過該統一的接口對子系統裏的其他接口進行訪問。
代理模式:通過訪問代理對象的方式來間接訪問目標對象。

主要分析步驟3、4:
步驟3:AccessApi NetService = retrofit.create(NetService.class);在MainActivity創建網絡接口類實例

public <T> T create(final Class<T> service) {

       if (validateEagerly) {  
      // 判斷是否需要提前驗證
      eagerlyValidateMethods(service); 
      // 具體方法作用:
      // 1. 給接口中每個方法的註解進行解析並得到一個ServiceMethod對象
      // 2. 以Method爲鍵將該對象存入LinkedHashMap集合中
     // 特別注意:如果不是提前驗證則進行動態解析對應方法(下面會詳細說明),得到一個ServiceMethod對象,最後存入到LinkedHashMap集合中,類似延遲加載(默認)
    }  


        // 創建了網絡請求接口的動態代理對象,即通過動態代理創建網絡請求接口的實例 (並最終返回)
        // 該動態代理是爲了拿到網絡請求接口實例上所有註解
    return (T) Proxy.newProxyInstance(
          service.getClassLoader(),      // 動態生成接口的實現類 
          new Class<?>[] { service },    // 動態創建實例
          new InvocationHandler() {     // 將代理類的實現交給 InvocationHandler類作爲具體的實現(下面會解釋)
          private final Platform platform = Platform.get();

         // 在 InvocationHandler類的invoke()實現中,除了執行真正的邏輯(如再次轉發給真正的實現類對象),還可以進行一些有用的操作
         // 如統計執行時間、進行初始化和清理、對接口調用進行檢查等。
          @Override 
           public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
          
            // 下面會詳細介紹 invoke()的實現
            // 即下面三行代碼
            ServiceMethod serviceMethod = loadServiceMethod(method);     
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

// 特別注意
// return (T) roxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler invocationHandler)
// 可以解讀爲:getProxyClass(loader, interfaces) .getConstructor(InvocationHandler.class).newInstance(invocationHandler);
// 即通過動態生成的代理類,調用interfaces接口的方法實際上是通過調用InvocationHandler對象的invoke()來完成指定的功能
// 先記住結論,在講解步驟4的時候會再次詳細說明


<-- 關注點1:eagerlyValidateMethods() -->
private void eagerlyValidateMethods(Class<?> service) {  
    Platform platform = Platform.get();  
    for (Method method : service.getDeclaredMethods()) {  
      if (!platform.isDefaultMethod(method)) {  loadServiceMethod(method); } 
      // 將傳入的ServiceMethod對象加入LinkedHashMap<Method, ServiceMethod>集合
     // 使用LinkedHashMap集合的好處:lruEntries.values().iterator().next()獲取到的是集合最不經常用到的元素,提供了一種Lru算法的實現
    }  
}

創建網絡接口實例採用外觀模式 & 代理模式

  • 外觀模式:定義一個統一接口,用來包裝子系統中一個/多個複雜的類,客戶端可通過調用外觀類的方法來調用內部子系統中所有的方法。
Retrofit對象的外觀(門店) =  retrofit.create()

通過這一外觀方法就可以在內部調用各個方法創建網絡請求接口的實例和配置網絡請求參數,即用戶只需要通過Retrofit.create()這一外觀角色便完成任意類型網絡接口的創建。

  • 代理模式

代理模式:通過訪問代理對象的方式來間接訪問目標對象
分爲靜態代理 & 動態代理:

靜態代理:代理類在程序運行前已經存在的代理方式
動態代理:代理類在程序運行前不存在、運行時由程序動態生成的代理方式

return (T) roxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler)通過代理模式中的動態代理模式,動態生成網絡請求接口的代理類,並將代理類的實例創建交給InvocationHandler類 作爲具體的實現,並最終返回一個動態代理對象。
使用動態代理的好處:

  • 當NetService對象調用getCall()接口中方法時會進行攔截,調用都會集中轉發到 InvocationHandler#invoke (),可集中進行處理
  • 獲得網絡請求接口實例上的所有註解
  • 更方便封裝ServiceMethod

執行網絡接口中方法時會被網絡動態代理對象Proxy.newProxyInstance()攔截並最終調用InvocationHandler#invoke ()進行集中處理。下面分析InvocationHandler類 # invoke()裏的具體實現

 new InvocationHandler() {   
          private final Platform platform = Platform.get();

  @Override 
           public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {

            // 作用:讀取網絡請求接口裏的方法,並根據前面配置好的屬性配置serviceMethod對象
            // 一個serviceMethod 對象 對應網絡請求接口裏的一個方法,loadServiceMethod(method)負責加載ServiceMethod
            //serviceMethod對象根據從Retrofit對象中獲取對應的數據適配器、內容轉換器對網絡請求接口方法中註解的參數類型和註解內容進行解析從而完成網絡請求參數的配置,即配置好ServiceMethod對象
            //配置ServiceMethod對象包括對ServiceMethod的域進行賦值(方法的標註)與ParameterHandler<?>對象(方法參數的標註)
            ServiceMethod serviceMethod = loadServiceMethod(method);     
           
            // 作用:根據配置好的serviceMethod對象與輸入的網絡請求接口參數args創建okHttpCall對象 
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);

            // 作用:調用OkHttp,並根據okHttpCall返回rejava的Observe對象或者返回Call
            //將第二步創建的OkHttpCall對象傳給第一步創建的serviceMethod對象中對應的網絡請求適配器工廠的adapt()
            //返回對象類型:Android默認是Call<>;若設置了RxJavaCallAdapterFactory,返回的則是Observable<>
            //這裏採用了裝飾者模式:ExecutorCallbackCall = 裝飾者,而裏面真正去執行網絡請求的還是OkHttpCall。使用裝飾模式的原因:希望在OkHttpCall發送請求時做一些額外操作。這裏的額外操作是線程轉換,即將子線程切換到主線程
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }

步驟4:Call call = NetService.getCall();對發送請求的url進行封裝,生成最終的網絡請求對象

  1. NetService對象實際上是動態代理對象Proxy.newProxyInstance()(步驟3中已說明),並不是真正的網絡請求接口創建的對象
  2. 當NetService對象調用getCall()時會被動態代理對象Proxy.newProxyInstance()攔截,然後調用自身的InvocationHandler # invoke()
  3. invoke(Object proxy, Method method, Object… args)會傳入3個參數:Object proxy:(代理對象)、Method method(調用的getCall())、Object… args(方法的參數,即getCall()中的
  4. 接下來利用Java反射獲取到getCall()的註解信息,配合args參數創建ServiceMethod對象。
  5. 最終創建並返回一個OkHttpCall類型的Call對象(OkHttpCall類是OkHttp的包裝類,創建了OkHttpCall類型的Call對象還不能發送網絡請求,需要創建Request對象才能發送網絡請求)

(2.2)總結
Retrofit是Retrofit核心類,對外提供接口。通過retrofit.create()創建retrofit實例,外觀模式。在create()方法中,使用動態代理模式對請求的接口中方法進行封裝(ServiceMethod),初始化OkhttpCall。
Retrofit採用了外觀模式統一調用創建網絡請求接口實例和網絡請求參數配置的方法,具體步驟如下:

  • 動態創建網絡請求接口的實例(代理模式 - 動態代理)
  • 創建 serviceMethod 對象(建造者模式 & 單例模式(緩存機制))
    ServiceMethod是核心處理類,解析方法和註解,toRequest()方法中生成HttpRequest。創建responseConverter(將response流轉換爲String或實體),創建callAdapter。
  • 對 serviceMethod 對象進行網絡請求參數配置:通過解析網絡請求接口方法的參數、返回值和註解類型,從Retrofit對象中獲取對應的網絡請求的url地址、網絡請求執行器、網絡請求適配器 & 數據轉換器。(策略模式)
  • 對 serviceMethod 對象加入線程切換的操作,便於接收數據後通過Handler從子線程切換到主線程從而對返回數據結果進行處理(裝飾模式)
  • 最終創建並返回一個OkHttpCall類型的網絡請求對象
    OkHttpCall是對okhttp3.Call的封裝調用
  1. 發送網絡請求(封裝了數據轉換、線程切換的操作)
    Retrofit默認使用OkHttp,即OkHttpCall類(實現了 retrofit2.Call接口)。OkHttpCall提供了兩種網絡請求方式:
    同步請求:OkHttpCall.execute()
    異步請求:OkHttpCall.enqueue()
    (3.1)同步請求 OkHttpCall.execute()
Response<JavaBean> response = call.execute();  

包括網絡請求三個步驟:
步驟1:對網絡請求接口的方法中的每個參數利用對應ParameterHandler進行解析,再根據ServiceMethod對象創建一個OkHttp的Request對象(ServiceMethod幾乎保存了一個網絡請求所需要的數據。發送網絡請求時,OkHttpCall需要從ServiceMethod中獲得一個Request對象)
步驟2:使用OkHttp的Request發送網絡請求;
步驟3:對返回的數據使用之前設置的數據轉換器(GsonConverterFactory)解析返回的數據,最終得到一個Response對象

爲了提高效率,Retrofit還會對解析過的請求ServiceMethod進行緩存,存放在Map<Method,
ServiceMethod> serviceMethodCache = new LinkedHashMap<>();對象中

(3.2)異步請求

call.enqueue(new Callback<JavaBean>() {
            @Override
            public void onResponse(Call<JavaBean> call, Response<JavaBean> response) {
                System.out.println(response.isSuccessful());
                if (response.isSuccessful()) {
                    response.body().show();
                }
                else {
                    try {
                        System.out.println(response.errorBody().string());
                    } catch (IOException e) {
                        e.printStackTrace();
                    } ;
                }
            }

發送請求過程包括4個步驟:
步驟1:對網絡請求接口的方法中的每個參數利用對應ParameterHandler進行解析,再根據ServiceMethod對象創建一個OkHttp的Request對象
步驟2:使用OkHttp的Request發送網絡請求;
步驟3:對返回的數據使用之前設置的數據轉換器(GsonConverterFactory)解析返回的數據,最終得到一個Response對象
步驟4:進行線程切換從而在主線程處理返回的數據結果(若使用了RxJava,則直接回調到主線程)call是一個靜態代理,使用靜態代理的作用是:在okhttpCall發送網絡請求的前後進行額外操作(這裏的額外操作是:線程切換,即將子線程切換到主線程,從而在主線程對返回的數據結果進行處理)。

異步請求的過程跟同步請求類似,唯一不同之處在於:異步請求會將回調方法交給回調執行器在主線程中執行。

  1. 處理服務器返回的數據

(5.4.4)設計模式

(1)Builder 模式
(2)工廠模式
(3)適配器模式
(4)代理模式
(5)外觀模式
(6)策略模式
(7)觀察者模式

(5.4.5)總結

Retrofit 本質上是一個 RESTful 的HTTP 網絡請求框架的封裝,即通過 大量的設計模式 封裝了 OkHttp ,使得簡潔易用。具體過程如下:

  1. Retrofit 將 Http請求 抽象 成 Java接口
  2. 在接口裏用 註解 描述和配置 網絡請求參數
  3. 用動態代理 的方式,動態將網絡請求接口的註解 解析 成HTTP請求
  4. 最後執行HTTP請求

即網絡請求由OkHttp進行,而網絡請求的接口封裝由Retrofit進行:

  1. App應用程序通過Retrofit請求網絡,實際上是使用Retrofit接口層封裝請求參數,之後由OkHttp完成後續的請求操作。
  2. 在服務端返回數據之後,OkHttp將原始的結果交給Retrofit,Retrofit根據用戶的需求對結果進行解析。
  3. 完成數據的轉化(converterFactory),適配(callAdapterFactory),通過設計模式進行各種擴展。

在這裏插入圖片描述

5.5 總結

(5.5.1)Retrofit使用7步驟

  • 添加Retrofit依賴,網絡權限
  • 定義接收服務器返回數據的Bean
  • 創建用於描述網絡請求的接口,使用註解
  • 通過builder模式創建Retrofit實例,並設置數據解析器(Converter)與網絡請求適配器(CallAdapter)
  • Retrofit通過動態代理創建網絡請求接口實例,獲取具體的網絡請求對象Call
  • 通過Call對象發送同步/異步網絡請求(封裝了數據轉換,線程切換過程)
  • 處理服務器返回的數據

(5.5.2)網絡通信8步驟

  • 創建Retrofit實例
  • 定義網絡請求接口,併爲接口中的方法添加註解
  • 通過動態代理生成網絡請求對象
  • 通過網絡請求適配器將網絡請求對象進行平臺適配
  • 通過網絡請求執行器,發送網絡請求(call)
  • 通過數據解析器解析數據
  • 通過回調執行器,切換線程
  • 用戶在主線程處理返回結果
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章