Android OkHttp常用詳解

OkHttp不需要多介紹了,已經是網絡框架界的大佬了,很多網絡框架都基於OkHttp封裝,也有很多涉及到網絡的第三方框架都可以支持使用OkHttp替換網絡。

OkHttp的4.0.x版本已經全部由java替換到了Kotlin,API的一些使用也會有些不同,具體的參考Upgrading to OkHttp 4

由於不熟悉Kotlin代碼,本文使用的OkHttp的版本爲3.14.2,是3.14.x的最後一個版本

接入

OkHttp在3.13.x以上的版本需要在Android 5.0+ (API level 21+)和Java 1.8的環境開發。

同時還需要再添加Okio的依賴庫,而Okio在1.x版本是基於Java實現的,2.x則是Kotlin實現的。

dependencies {
    //...
    //OkHttp
    implementation 'com.squareup.okhttp3:okhttp:3.14.2'
    implementation 'com.squareup.okio:okio:1.17.4'
}

3.12.x以及以下的版本支持Android 2.3+ (API level 9+)和Java 1.7的開發環境

Get請求

請求分爲同步請求和異步請求,先看看同步請求

public void getSyn(final String url) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                //創建OkHttpClient對象
                OkHttpClient client = new OkHttpClient();
                //創建Request
                Request request = new Request.Builder()
                        .url(url)//訪問連接
                        .get()
                        .build();
                //創建Call對象        
                Call call = client.newCall(request);
                //通過execute()方法獲得請求響應的Response對象        
                Response response = call.execute();
                if (response.isSuccessful()) {
                    //處理網絡請求的響應,處理UI需要在UI線程中處理
                    //...
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

這就是一段同步Get請求的代碼,同步網絡請求需要在子線程中執行,而處理UI需要回到UI線程中處理。

在看看Get的異步請求,這時就不需要自己創建子線程了,但是處理UI同樣需要在UI線程中處理,不能再請求響應的回調方法中處理

public void getAsyn(String url) {
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url(url).build();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            //...
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if(response.isSuccessful()){
                String result = response.body().string();
                //處理UI需要切換到UI線程處理
            }
        }
    });
}

Request.Builder中默認的使用Get請求,所以可以不調用get()方法

看了兩種不同的Get請求,基本流程都是先創建一個OkHttpClient對象,然後通過Request.Builder()創建一個Request對象,OkHttpClient對象調用newCall()並傳入Request對象就能獲得一個Call對象。而同步和異步不同的地方在於execute()enqueue()方法的調用,調用execute()爲同步請求並返回Response對象;調用enqueue()方法測試通過callback的形式返回Response對象。

注意:無論是同步還是異步請求,接收到Response對象時均在子線程中,其中通過Response對象獲取請求結果需要在子線程中完成,在得到結果後再切換到UI線程改變UI

Post請求

Post請求與Get請求不同的地方在於Request.Builderpost()方法,post()方法需要一個RequestBody的對象作爲參數

public void post(String url,String key,String value){
    OkHttpClient client = new OkHttpClient();
    FormBody body = new FormBody.Builder()
            .add(key,value)
            .build();
    Request request = new Request.Builder()
            .url(url)
            .post(body)
            .build();
    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            //...
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            if(response.isSuccessful()){
                String result = response.body().string();
                //處理UI需要切換到UI線程處理
            }
        }
    });
}

RequestBody是一個抽象類,分別有FormBodyMultipartBody兩個子類,上面這個例子使用的是FormBody,用於傳輸表單類型的參數。MultipartBody則支持多類型的參數傳遞,例如:在傳輸表單類型的參數的同時,還是可以傳輸文件。創建一個MultipartBody對象再調用post()方法就OK了。

MultipartBody body = new MultipartBody.Builder()
//      添加表單參數
//      .addFormDataPart(key,value)
        .addFormDataPart(name, fileName, RequestBody.create(MediaType.get("application/octet-stream"), file))
        .build();

RequestBody

前面的代碼可以看到使用了RequestBody.create()方法,改方法的返回值也是一個RequestBody對象。

其實Post請求就是包含了RequestBody對象,MultipartBody則是可以支持多中以及多個RequestBody對象。

RequestBody提供了5個重載的create()靜態方法,如下圖

如果只需要上傳文件,請求就比較簡單了,如下:

public void uploadFile(String url, File file) {
    OkHttpClient client = new OkHttpClient();
    RequestBody body =  RequestBody.create(MediaType.get("application/octet-stream"), file);
    Request request = new Request.Builder()
            .url(url)
            .post(body)
            .build();
    Call call = client.newCall(request);
    //call.enqueue();
    //...
}

這個MediaType.get()的傳值是"application/octet-stream",這是二進制流傳輸,在不知道文件類型的情況下可以這麼操作,具體的傳參可以參考Content-Type的類型使用對照表

PS:如果要監聽文件的上傳進度就沒這麼簡單了

這是生成一個上傳JSON的RequestBody對象的代碼

MediaType jsonType = MediaType.parse("application/json; charset=utf-8");
String jsonStr = "{\"username\":\"Sia\"}";//json數據.
RequestBody body = RequestBody.create(jsonType, josnStr);

設置超時時間

OkHttp可以設置調用、連接和讀寫的超時時間,都是通過OkHttpClient.Builder設置的。如果不主動設置,OkHttp將使用默認的超時設置。

OkHttpClient mClient = new OkHttpClient.Builder()
        .callTimeout(6_000, TimeUnit.MILLISECONDS)
        .connectTimeout(6_000, TimeUnit.MILLISECONDS)
        .readTimeout(20_000, TimeUnit.MILLISECONDS)
        .writeTimeout(20_000, TimeUnit.MILLISECONDS)
        .build();

設置請求Header

請求的Header是通過Request.Builder對象的相關方法來維護的,如下:

  • headers(Headers headers)
  • header(String name, String value)
  • addHeader(String name, String value)
  • removeHeader(String name)

addHeaderremoveHeader方法比較好理解,分別是添加和移除header信息。header(String name, String value)這是會重新設置指定name的header信息。

headers(Headers headers)則是會移除掉原有的所有header信息,將參數headers的header信息添加到請求中。這是這幾個方法的一些差別。

使用的話都是Builder模式的鏈式調用,舉個栗子

Request request = new Request.Builder()
        .header("Accept","image/webp")
        .addHeader("Charset","UTF-8")
        .url(url)
        .build();

Cookie也是header信息中的一個字段,通過Header相關方法添加就好了

請求部分的基礎使用基本上就這些了,具體的一些用法可以參考官方文檔https://square.github.io/okhttp/recipes/

Interceptors(攔截器)

攔截器是OkHttp當中一個比較強大的機制,可以監視、重寫和重試調用請求。

這是一個比較簡單的Interceptor的實現,對請求的發送和響應進行了一些信息輸出。

class LoggingInterceptor implements Interceptor {
    public static final String TAG = "Http_log";

    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();

        long t1 = System.nanoTime();
        Log.i(TAG, String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));

        Response response = chain.proceed(request);

        long t2 = System.nanoTime();
        Log.i(TAG, String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), (t2 - t1) / 1e6d, response.headers()));

        return response;
    }
}

需要實現其中intercept(Interceptor.Chain chain)方法,同時必須調用chain.proceed(request)代碼,也就是網絡請求真正發生的地方。

攔截器可以設置多個,並且攔截器的調用是有順序的。官網舉的例子是,同時添加一個壓縮攔截器和一個校驗攔截器,需要決定數據是先被壓縮在校驗,還是先校驗在壓縮。

攔截器還分爲應用攔截器(Application Interceptors)和網絡攔截器(Network Interceptors)

上圖可以看出應用攔截器是處於應用和OkHttp核心之間,而網絡攔截器則是在OkHttp核心與網絡之間,這裏就直接搬運官網的示例,使用LoggingInterceptor來看一下兩種攔截器的差異。

OkHttp自身有5個Interceptor的實現,有興趣可以閱讀源碼

  • RetryAndFollowUpInterceptor
  • BridgeInterceptor
  • CacheInterceptor
  • ConnectInterceptor
  • CallServerInterceptor
Application Interceptors

先看看應用攔截器,通過OkHttpClient.BuilderaddInterceptor方法添加攔截器

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new LoggingInterceptor())
    .build();

Request request = new Request.Builder()
    .url("http://www.publicobject.com/helloworld.txt")
    .header("User-Agent", "OkHttp Example")
    .build();

Response response = client.newCall(request).execute();
response.body().close();

看請求和響應的兩個鏈接是不同的,URL http://www.publicobject.com/helloworld.txt會重定向到 https://publicobject.com/helloworld.txt,OkHttp會自動跟隨重定向,而應用攔截器只被調用一次,並且chain.proceed()返回的Response對象是具有重定向響應對象。

INFO: Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Example

INFO: Received response for https://publicobject.com/helloworld.txt in 1179.7ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive
Network Interceptors

再來看看網絡攔截器,通過OkHttpClient.BuilderaddNetworkInterceptor方法添加攔截器

OkHttpClient client = new OkHttpClient.Builder()
    .addNetworkInterceptor(new LoggingInterceptor())
    .build();

Request request = new Request.Builder()
    .url("http://www.publicobject.com/helloworld.txt")
    .header("User-Agent", "OkHttp Example")
    .build();

Response response = client.newCall(request).execute();
response.body().close();

結果日誌:

INFO: Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Example
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for http://www.publicobject.com/helloworld.txt in 115.6ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/html
Content-Length: 193
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt

INFO: Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=54.187.32.157 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA protocol=http/1.1}
User-Agent: OkHttp Example
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

INFO: Received response for https://publicobject.com/helloworld.txt in 80.9ms
Server: nginx/1.4.6 (Ubuntu)
Content-Type: text/plain
Content-Length: 1759
Connection: keep-alive

從日誌來看,攔截器運行了兩次,第一次請求了http://www.publicobject.com/helloworld.txt,第二次則是重定向到https://publicobject.com/helloworld.txt。同時通過網絡攔截能獲得更多的header信息。更多的關於Interceptor的使用以及它們各自的優缺點,請參考OkHttp官方說明文檔

其他

OkHttp已經出來很久了,本文只是爲了完成自己的執念吧!還有Events的使用沒有寫出來,這個東西平常也沒用過,後續研究了會補充上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章