天天看點

Android網絡篇(一)—— OkHttp的基本使用

因為這個系列是屬于Android的基礎系列,是以并不會涉及到很深的内容層次。作為網絡這一個子產品而言,我們經曆了幾個階段。從最初的HttpClicent(已被廢棄)到後面的HttpUrlConnection,當然在HttpUrlConnection的基礎上出現了很多的網絡架構,諸如:Volley,XUtils等等。但是随着OkHttp的推出,原來的這些網絡架構都失去了色彩,OkHttp以其獨特的魅力迅速獲得了廣大開發者的喜愛,今天我們就來看看關于OkHttp的那些事。

OkHttp的基本特性

(1)支援HTTP 2.0,允許所有同一個主機位址的請求共享一個socket連結。

(2)連接配接池複用減少請求延時,同步避免了資源浪費。

(3)透明的GZIP壓縮減少響應資料的大小。

(4)自帶緩存機制,可以避免一些重複請求。

(5)多IP使用,當服務斷開後,自動切換備用IP位址,重新發起連接配接。

OkHttp的基本使用

添加依賴

// 網絡請求架構
// define a BOM and its version
api(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))

// define any required OkHttp artifacts without version
api("com.squareup.okhttp3:okhttp")
api("com.squareup.okhttp3:logging-interceptor")
           

異步get請求

主要的步驟為:

(1)建立OkHttp執行個體。

(2)建構請求參數 預設為get()請求,可以不寫。

(3)建構請求。

(4)發送請求并擷取回調(enqueue為異步請求,execute為同步請求(由于會阻塞線程,不建議使用))。

/**
 * 普通的Get請求
 */
fun okHttpGet(view: View) {
    val url = "http://www.baidu.com"
    // 1.建立OkHttp執行個體
    val okHttpClient = OkHttpClient()
    // 2.建構請求參數 預設為get()請求,可以不寫
    val request = Request.Builder().url(url).get().build()
    // 3.建構請求
    val call = okHttpClient.newCall(request)
    // 4.發送請求并擷取回調(enqueue為異步請求,execute為同步請求(由于會阻塞線程,不建議使用))
    call.enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            Log.i("okHttp", "請求失敗${e.printStackTrace()}")
        }

        override fun onResponse(call: Call, response: Response) {
            Log.i(
                "okHttp",
                response.protocol.toString() + " " + response.code + " " + response.message
            )
            val headers = response.headers
            for (i in 0 until headers.size) {
                Log.i("okHttp", headers.name(i) + ":" + headers.value(i))
            }
            Log.i("okHttp", "onResponse: " + response.body!!.string())
        }
    })
}
           

異步post請求

主要的步驟為:

(1)建立OkHttp執行個體。

(2)建構請求參數 預設為get()請求,可以不寫。

(3)建構請求。

(4)發送請求并擷取回調(enqueue為異步請求,execute為同步請求(由于會阻塞線程,不建議使用))。

/**
 * Post方式送出String
 */
fun okHttpPost(view: View) {
    val url = "http://www.5mins-sun.com:8081/user/direct_login"
    //1.建構OkHttp執行個體
    val okHttpClient = OkHttpClient()
    //2.建構請求參數
    val mediaType = "application/json; charset=utf-8".toMediaTypeOrNull()
    val map = mutableMapOf<String, Any>()
    map["phone"] = "13701659446"
    val requestBody = Gson().toJson(map)
    val request = Request.Builder().url(url).post(requestBody.toRequestBody(mediaType)).build()
    //3.建構請求
    val call = okHttpClient.newCall(request)
    //4.發送請求
    call.enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            Log.i("okHttp", "請求失敗${e.printStackTrace()}")
        }

        override fun onResponse(call: Call, response: Response) {
            Log.i(
                "okHttp",
                response.protocol.toString() + " " + response.code + " " + response.message
            )
            val headers = response.headers
            for (i in 0 until headers.size) {
                Log.i("okHttp", headers.name(i) + ":" + headers.value(i))
            }
            Log.i("okHttp", "onResponse: " + response.body!!.string())
        }
    })
}
           

異步post送出流

/**
 * Post方式送出流
 */
fun okHttpPostStream(view: View) {
    val url = "http://www.5mins-sun.com:8081/user/direct_login"
    //1.建構OkHttp執行個體
    val okHttpClient = OkHttpClient()
    //2.建構請求參數
    val requestBody = object : RequestBody() {
        override fun contentType(): MediaType? {
            return "application/json; charset=utf-8".toMediaTypeOrNull()
        }

        override fun writeTo(sink: BufferedSink) {
            val map = mutableMapOf<String, Any>()
            map["phone"] = "13701659446"
            val requestBody = Gson().toJson(map)
            sink.writeUtf8(requestBody)
        }

    }
    val request = Request.Builder().url(url).post(requestBody).build()
    //3.建構請求
    val call = okHttpClient.newCall(request)
    //4.發送請求
    call.enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            Log.i("okHttp", "請求失敗${e.printStackTrace()}")
        }

        override fun onResponse(call: Call, response: Response) {
            Log.i(
                "okHttp",
                response.protocol.toString() + " " + response.code + " " + response.message
            )
            val headers = response.headers
            for (i in 0 until headers.size) {
                Log.i("okHttp", headers.name(i) + ":" + headers.value(i))
            }
            Log.i("okHttp", "onResponse: " + response.body!!.string())
        }
    })
}
           

異步post送出檔案

/**
 * Post送出檔案
 */
fun okHttpPostFile(view: View) {
    val url = "http://www.5mins-sun.com:8081/manage/test_save_file_by_stream"
    //1.建構OkHttp執行個體
    val okHttpClient = OkHttpClient()
    //2.建構請求參數
    val file = File(Environment.getExternalStorageDirectory().absolutePath + "/zf.txt")
    val mediaType = "application/octet-stream".toMediaTypeOrNull()
    val request = Request.Builder().url(url).post(RequestBody.create(mediaType, file)).build()
    //3.建構請求
    val call = okHttpClient.newCall(request)
    //4.發送請求
    call.enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            Log.i("okHttp", "請求失敗${e.printStackTrace()}")
        }

        override fun onResponse(call: Call, response: Response) {
            Log.i(
                "okHttp",
                response.protocol.toString() + " " + response.code + " " + response.message
            )
            val headers = response.headers
            for (i in 0 until headers.size) {
                Log.i("okHttp", headers.name(i) + ":" + headers.value(i))
            }
            Log.i("okHttp", "onResponse: " + response.body!!.string())
        }
    })
}

           

異步post送出表單

fun okHttpPostForm(view: View) {
    val url = "http://www.5mins-sun.com:8081/manage/test_save_file"
    //1.建構OkHttp執行個體
    val okHttpClient = OkHttpClient()
    //2.建構請求參數
    val file = File(Environment.getExternalStorageDirectory().absolutePath + "/zhoufn.txt")
    val mediaType = "application/octet-stream".toMediaTypeOrNull()
    val fileBody = RequestBody.create(mediaType, file)
    val requestBody = MultipartBody.Builder().setType(MultipartBody.FORM)
        .addFormDataPart("files", file.name, fileBody).build()
    val request = Request.Builder().url(url).post(requestBody).build()
    //3.建構請求
    val call = okHttpClient.newCall(request)
    //4.發送請求
    call.enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            Log.i("okHttp", "請求失敗${e.printStackTrace()}")
        }

        override fun onResponse(call: Call, response: Response) {
            Log.i(
                "okHttp",
                response.protocol.toString() + " " + response.code + " " + response.message
            )
            val headers = response.headers
            for (i in 0 until headers.size) {
                Log.i("okHttp", headers.name(i) + ":" + headers.value(i))
            }
            Log.i("okHttp", "onResponse: " + response.body!!.string())
        }
    })
}

           

其實仔細觀察不難發現,無論是哪種方式互動,其大抵的步驟都是一樣的,即:

(1)建立OkHttp執行個體。
(2)建構請求參數 預設為get()請求,可以不寫。
(3)建構請求。
(4)發送請求并擷取回調(enqueue為異步請求,execute為同步請求(由于會阻塞線程,不建議使用))。

唯一的差別在于第2步建構請求參數的時候有所不同,當使用get請求方式的時候,無須設定RequestBody,而在使用post請求的時候,必須設定RequestBody,那麼,什麼是RequestBody呢?

RequestBody

在使用RequestBody的時候,我們一般是通過調用它的onCreate來拿到它的執行個體,先看下面這張圖:

Android網絡篇(一)—— OkHttp的基本使用

注意看,RequestBody.create()有4個重載的方法,裡面的參數都不盡相同,其中第一個參數是MediaType,那麼,什麼是mediaType呢?

MediaType:表示要傳遞的資料的MIME類型,即你要傳遞的是什麼類型的東西。比如,需要傳遞的是json格式的字元串,你就可以設定為application/json; charset=utf-8;再比如,需要傳遞的是圖檔檔案,可以設定為image/png等等。假設類型設定不對,傳回碼會傳回為415。

接着看,除了MediaType,後面的參數主要有:File檔案,String字元串等等。當然,在實際的開發過程中,我們用到最多的也就是這兩個。其中File代表的是上傳的是檔案,String代表的是上傳的是字元串。而在上面的例子中,我們使用的是MultipartBody來對檔案進行上傳,如果我們僅僅隻想傳遞Map集合呢?其實對于這種key-value形式的資料,我們可以按照表單來傳遞,具體的寫法為:

val okHttpClient = OkHttpClient()
val requestBody: RequestBody = FormBody.Builder()
    .add("key", "value")
    .build()
val request: Request = Builder()
    .url("url")
    .post(requestBody)
    .build()
           

OkHttp的攔截器

OkHttp的優秀之處還在于其強大的攔截器功能,先上一張圖

Android網絡篇(一)—— OkHttp的基本使用

有沒有覺得這種模式很熟悉,其實這是Java中23種設計模式之一的責任鍊模式,從上到下鍊到底,一層嵌套着一層。其中最上層可選的為我們自定義的攔截器,在這裡我們可以監聽列印自己想要的東西,比如:

(1)給我們所有的網絡請求接口添加通用字段

public class HttpHeaderInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        int userId = IOFactoryUtil.getIOFactoryUtil().getDefaultHandler().getInt("user_id", 0);
        if (userId > 0) {
            Request request = original.newBuilder()
                    .header("userID", String.valueOf(userId))
                    .build();
            return chain.proceed(request);
        }
        return chain.proceed(original);
    }
}

           

(2)列印我們所有的請求參數

public class HttpLoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        long startTime = System.currentTimeMillis();
        okhttp3.Response response = chain.proceed(chain.request());
        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;
        okhttp3.MediaType mediaType = response.body().contentType();
        String content = response.body().string();
        Log.d("NetRequest", "請求位址:| " + request.toString());
        printParams(request.body());
        Log.d("NetRequest", "請求體傳回:| Response:" + content);
        Log.d("NetRequest", "----------請求耗時:" + duration + "毫秒----------");
        return response.newBuilder().body(okhttp3.ResponseBody.create(mediaType, content)).build();
    }

    private void printParams(RequestBody body) {
        Buffer buffer = new Buffer();
        try {
            body.writeTo(buffer);
            Charset charset = Charset.forName("UTF-8");
            MediaType contentType = body.contentType();
            if (contentType != null) {
                charset = contentType.charset(UTF_8);
            }
            String params = buffer.readString(charset);
            Log.d("NetRequest", "請求參數: | " + params);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
           

上面隻是簡單的舉了兩個例子,當然還可以設定更多,具體根據自己的實際需求而定。

除了我們自定義的攔截器,系統還幫我們自動添加了一些攔截器,具體來說:

(1)RetryAndFollowUpInterceptor

處理重定向和錯誤的攔截器

(2)BridgeInterceptor

添加必要的請求頭資訊、gzip處理等。

(3)CacheInterceptor

緩存處理

(4)ConnectInterceptor

處理網絡連接配接

(5)CallServerInterceptor

通路伺服器

看到這裡,真心的不由得佩服OkHttp攔截器的設計之巧妙,通過多層不同作用的攔截器的使用,讓各個環節都能各司其職而且職責分明。

注意事項:

(1)OkHttp在全局盡量保持執行個體,這樣所有的請求都可以共享連接配接池、線程池和配置資訊。

(2)OkHttp的預設連接配接網絡時間、讀取資料時間、寫入資料時間為10s,我們可以手動重新設定時間,具體的方法為:readTimeout、connectTimeout、writeTimeout。

(3)onResponse回調的參數是response,一般情況下,比如我們希望獲得傳回的字元串,可以通過response.body().string()擷取;如果希望獲得傳回的二進制位元組數組,則調用response.body().bytes();如果你想拿到傳回的inputStream,則調用response.body().byteStream()。

(4)在okhttp3.Callback的回調方法裡面有個參數是Call 這個call可以單獨取消相應的請求,随便在onFailure或者onResponse方法内部執行call.cancel()都可以。如果想取消所有的請求,則可以okhttpclient.dispatcher().cancelAll();。