天天看點

Retrofit-Okhttp-Okio-RxJava

先來一張Jake Wharton 關于 A Few “OK” Libraries 的簡介,幾個架構的關系

Retrofit-Okhttp-Okio-RxJava

前面先分析了RxJava的實作,第一感覺就是和Java 8裡面的stream很像,以函數式程式設計的形式來處理資料,個人了解的核心思想有2個:

  • 函數接口 在函數的參數裡面,定義了各種函數接口(functional interface),當需要這些參數的時候,有3種方式可以實作
    • 通過一個匿名内部類
    • 傳遞lambda表達式
    • 在某些情況下,傳遞方法引用(如System.out::println)而不是lambda表達式。
  • Builder模式 每次對資料的處理,都是生成的一個新的對象

RaJava更多的是可能是配合其它元件一起使用,如

Retrofit-Okhttp-Okio-RxJava 4個元件組合,在Android開發中基本上已經标配。在一般網絡程式設計中,主要是處理http連接配接以及request(需要什麼資料)、reponse(傳回結果)。

HTTP 簡介

HTTP請求封包 一個HTTP請求封包由請求行(request line)、請求頭部(header)、空行和請求資料4個部分組成

請求行分為三個部分:請求方法、請求位址和協定版本

  1. 請求方法 HTTP/1.1 定義的請求方法有8種:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE。最常的兩種GET和POST,如果是RESTful接口的話一般會用到GET、POST、DELETE、PUT。
  2. 請求位址 URL:統一資源定位符。 組成:<協定>://<主機>:<端口>/<路徑>
  3. 協定版本:如HTTP/1.1,HTTP/2.0

請求頭 為請求封包添加了一些附加資訊,由“名/值”鍵值組成,每行一對,名和值之間使用冒号分

HTTP響應封包 主要由狀态行、響應頭部、空行以及響應資料組成。

狀态行

由3部分組成,分别為:協定版本,狀态碼,狀态碼描述。其中協定版本與請求封包一緻,狀态碼描述是對狀态碼的簡單描述。

一些常用的狀态碼

  • 100~199:訓示資訊,表示請求已接收,繼續處理
  • 200~299:請求成功,表示請求已被成功接收、了解
  • 300~399:重定向,要完成請求必須進行更進一步的操作
  • 400~499:用戶端錯誤,請求有文法錯誤或請求無法實作
  • 500~599:伺服器端錯誤,伺服器未能實作合法的請求

響應頭部 與請求頭部類似,為響應封包添加了一些附加資訊

幾個架構的分工大概如下類圖

Retrofit-Okhttp-Okio-RxJava

詳細的流程圖:

Retrofit-Okhttp-Okio-RxJava

其中Retrofit的實作依賴于Okhttp,Okhttp的實作依賴于Okio

另外在Retrofit裡面,可以使用将傳回結果轉換成RxJava裡面的基礎類,如Flowable/Observable,充分利用RxJava響應式程式設計的優勢,提升資料處理效率

下面簡單介紹一下4個架構的主要功能

1.Retrofit

Retrofit 是一個 RESTful 的 HTTP 網絡請求架構的封裝。因為和網絡連接配接的處理是由Okhttp處理的,與伺服器的Socket讀寫是通過Okio來實作的,是在這2個架構上的進一步封裝

核心功能有2個:對request的處理以及對response結果的轉換處理

1.1 使用annotation來描述request

Retrofit裡面定義了很多注解,使用annotation來描述一個request應該怎麼被處理

  • request 方法 可以使用的注解有HTTP, GET, POST, PUT, PATCH, DELETE, OPTIONS and HEAD
  • url操作 一個請求url可以通過替換方法裡面的代碼塊和參數來動态更新,一個替換塊必須用{}包起來,且在參數裡面使用同樣的名字用@path注解
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId);

// query查詢也可以添加
@GET("group/{id}/users")
Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);

// 對于更複雜的請求參數可以使用@QueryMap
           
  • 通過@Body注解,可以将一個對象作為request的body來使用
  • 通過Headers注解可以設定請求頭
@Headers({
    "Accept: application/vnd.github.v3.full+json",
    "User-Agent: Retrofit-Sample-App"
})
@GET("users/{username}")
Call<User> getUser(@Path("username") String username);
           
1.2 資料類型轉換器converters

預設情況下,Retrofit隻支援把HTTP body作為Okhttp的ResponseBody類型,通過@body注解隻能接收RequestBody 類型

可以使用轉換器來支援其它 類型:

  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • JAXB: com.squareup.retrofit2:converter-jaxb
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

可以自定義converters,來處理非以上幾種類型的

1.3 Call Adapter Call擴充卡

在retrofit裡面,一般會定義一個接口,如下

interface Service {  
  @GET("/user")
  Call<User> user();
}
           

對象的轉換過程:

Call <-- CallAdapter <-- Call <-- Call <-- Call.Factory (如 OkHttpClient)

通過Call.Factory的抽象,來相容Okhtttp/RxJava等

如果想把擷取的結果轉換成RxJava裡面可以處理的Observable,可以将接口的定義修改如下:

interface Service {
  @GET("/user")
  Observable<User> user();
}
           

然後在裡面Retrofit裡面配置CallAdapter為Rxjava

Retrofit retrofit = new Retrofit.Builder()

    .baseUrl("http://example.com") 
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 
    .build();
           

預設情況下,create方法會建立一個響應式類型,在背景線程裡面異步執行一個http請求,有2種方式可以指定運作的線程

  • 使用createSynchronous(),然後調用subscribeOn在想要運作的Scheduler上
  • 使用createWithScheduler來提供一個預設的訂閱Scheduler

2.Okhttp

HTTP client主要工作是接收request,然後生成一個response

Requests

每個http請求包含一個URL,一個方法(像GET/POST),和一系列headers,可能包含有一個body:一種特定類型的資料流

Responses

Response響應request,傳回一個code(像200/404),一系列headers和一個可選的header

當把一個request發送到OkHttp時, OkHttp會重寫request然後再發送,OkHttp可能會增加一些headers,在原始的請求裡面沒有的,包括Content-Length, Transfer-Encoding, User-Agent, Host, Connection, and Content-Type

也在增加一個Accept-Encoding,除非原始request裡面已經有了,當需要cookies時,OkHttp會增加一個Cookie header

有些request可能有緩存的response,當緩存不是最新的時候,OkHttp會做一個有條件的GET來下載下傳更新,這種場景下,要求headers裡面加上

If-Modified-Since
If-None-Match
           

如果傳輸使用了壓縮,OkHttp會丢棄響應頭Content-Encoding and Content-Length,因為适用于解壓後的response body

rediect

當你請求的URL被移動,伺服器可能會傳回一個類似302的響應,來表明文檔的新URL,OkHttp會rediect來擷取一個最終的response

Authenticator

如果需要鑒權,OkHttp會調用Authenticator 來處理,如果Authenticator 提供了證書,請求的時候會包含此證書

重試

有時連接配接會失敗,OkHttp會使用一個不同的路由來重試

Call

OkHttp使用Call來模組化,不管需要多少中間請求和響應,來滿足request

Call執行有2種方式:

  • 同步:目前線程會阻塞,直到response可讀
  • 異步:把請求放在另外的線程,當response可讀時使用callback來回調

Calls可以在任意線程裡面取消.Call的管理

  • 對于同步調用,在單獨的線程裡面管理同時發出的請求數量
  • 對于異步Call,Dispatch實作了請求數量管理政策

response裡面有一個string()方法,對于小檔案,可以簡單高效的将response body轉換成string。如果response body比較大,超過1M,則避免使用string(),因為此方法會将内容全部加載到memory裡面,在這種場景下,應該把respnse當作stream來處理

同步調用,直接等待response然後再處理client.newCall(request).execute().use

異步調用,client.newCall(request).enqueue(object : Callback

則傳入一個callback,當reponse可用時,回調此callbase,okhttp不支援異步API把response分塊處理

通路請求頭

使用addHeader來增加新的header,如果存在,不需要删除,addHeader在處理的時候,如果value存在,則先remove掉,然後再添加新的value

使用header(name)來擷取一個head,使用headers(name),擷取所有值,作為一個List傳回

EventListener

可以用來擷取http call的調用數量,使用EventListener可以監聽

應用内部調用Http call的大小和頻率,當調用Call太多次或Call太大,應該了解這些情況

在目前網絡情況下的性能,如果網絡性能不夠好,應該增強網絡或減少調用

Interceptor

Interceptor 是一個功能強大的機制,可以監聽、重寫、重試Call

OkHttp 中的攔截器分為 Application Interceptor(應用攔截器) 和 NetWork Interceptor(網絡攔截器)兩種,支援自定義,注冊方式的差別:

  • 通過調用 OkHttpClient.Builder 的 addInterceptor() 方法來注冊應用攔截器
  • 通過調用OkHttpClient.Builder 的 addNetworkInterceptor() 方法來注冊網絡攔截器

2個可以自定義的Interceptor,5個内置的Interceptor.執行順序和功能簡介

  1. Application Interceptor:每次網絡請求它隻會執行一次攔截,而且它是第一個觸發攔截的,這裡攔截到的url請求的資訊都是最原始的資訊
  2. RetryAndFollowUpInterceptor 的作用,一個負責失敗重連的攔截器。它是 Okhttp 内置的第一個攔截器,通過 while (true) 的死循環來進行對異常結果或者響應結果判斷是否要進行重新請求。
  3. BridgeInterceptor 為使用者建構的一個 Request 請求轉化為能夠進行網絡通路的請求,同時将網絡請求回來的響應 Response 轉化為使用者可用的 Response
  4. CacheInterceptor 根據 OkHttpClient 對象的配置以及緩存政策對請求值進行緩存
  5. ConnectInterceptor 在 OKHTTP 底層是通過 SOCKET 的方式于服務端進行連接配接的,并且在連接配接建立之後會通過 OKIO 擷取通向 server 端的輸入流 Source 和輸出流 Sink
  6. NetWork Interceptor:它位于倒數第二層,會經過 RetryAndFollowIntercptor 進行重定向并且也會通過 BridgeInterceptor 進行 request請求頭和 響應 resposne 的處理
  7. CallServerInterceptor 在 ConnectInterceptor 攔截器的功能就是負責與伺服器建立 Socket 連接配接,并且建立了一個 HttpStream 它包括通向伺服器的輸入流和輸出流

3.Okio

Okio是一個實作java.io和java.nio的庫,更友善通路、存儲和處理資料。作為OkHttp元件的一部分,在Android中引入支援HTTP的用戶端

Buffer and ByteString 持有資料
  • ByteString 代表一個不可變的位元組序列。對于char資料,String是基礎類型。
  • Buffer 可變的位元組序列,像ArrayList,讀寫Buffer的操作與queue類似,從尾部寫,從頭部讀,不需要管理position/limit/capacities

在内部實作,ByteString和Buffer做了一些優化來節約CPU和記憶體,如果把一個UTF-8字元串編碼為ByteString,儲存了一個引用,後面如果需要解碼的時候可以直接使用,在encodeUtf8方法裡面

Buffer被實作為一個segment連結清單,當你從一個Buffer移動資料到另外一個Buffer的時候,重新設定了segment的所屬隊列,沒有直接copy一份資料,在多線程場景下很有幫助,在buffer.write方法裡面

class Buffer implements BufferedSink,BufferedSource
           
Source and Sink 移動資料

在okio裡面,有自己的stream類型Source和Sink,使用方式類似于InputStream和OutputStream

Source對應為輸入,在BufferedSource裡面封裝了一個InputStream

interface Source {
 long read(Buffer sink, long byteCount);
 Timeout timeout();
 void close();
}

interface BufferedSource extends Source {
 byte readByte();
 short readShort();
 short readShortLe();
 int readInt();
 int readIntLe();
 long readLong();
 long readLongLe();
 long readDecimalLong();
 long readHexadecimalUnsignedLong();
 ByteString readByteString();
 ByteString readByteString(long byteCount);
 byte[] readByteArray();
 byte[] readByteArray(long byteCount);
 int read(byte[] sink);
 void readFully(byte[] sink);
 int read(byte[] sink, int offset, int byteCount);
 void readFully(Buffer sink, long byteCount);
 long readAll(Sink sink);
 String readUtf8();
 String readUtf8(long byteCount);
 String readUtf8Line();
 String readUtf8LineStrict();
 String readString(Charset charset);
 String readString(long byteCount, Charset charset);
 long indexOf(byte b);
 long indexOf(byte b, long fromIndex);
 long indexOfElement(ByteString targetBytes);
 long indexOfElement(ByteString targetBytes, long fromIndex);
 boolean exhausted();
 void require(long byteCount);
 boolean request(long byteCount);
 void skip(long byteCount);
 Buffer buffer();
 InputStream inputStream();
}
           

Sink對應為輸出,在BufferedSink裡面封裝了一個outputStream

interface Sink {
 void write(Buffer sink, long byteCount);
 Timeout timeout();
 void close();
void flush();
}

interface BufferedSink extends Sink {
 BufferedSink write(ByteString byteString);
 BufferedSink write(byte[] source);
 BufferedSink write(byte[] source, int offset, int byteCount);
 long writeAll(Source source);
 BufferedSink write(Source source, long byteCount);
 BufferedSink writeUtf8(String string);
 BufferedSink writeString(String string, Charset charset);
 BufferedSink writeByte(int b);
 BufferedSink writeShort(int s);
 BufferedSink writeShortLe(int s);
 BufferedSink writeInt(int i);
 BufferedSink writeIntLe(int i);
 BufferedSink writeLong(long v);
 BufferedSink writeLongLe(long v);
 BufferedSink writeDecimalLong(long v);
 BufferedSink writeHexadecimalUnsignedLong(long v);
 BufferedSink emitCompleteSegments();
 BufferedSink emit();
 Buffer buffer();
 OutputStream outputStream();
}
           

主要差異:

  • 提供了IO語義層面的逾時
  • 容易實作 ,容易使用
  • 沒有人為區分字元流和位元組流,都作為資料處理,讀寫都按位元組來操作
  • 容易測試

Buffer 内部儲存的資料在Segment Pool裡面

BufferedSource and BufferedSink 更高效的移動資料,具體的細節,可以參考前面pdf裡面的文檔,後面會專門針對幾個架構來分析實作