OkHttp 優雅封裝 HttpUtils 之 氣海雪山初探

曾經在代碼裏放蕩不羈,現在在博文中日夜兼行,只爲今天與你分享成果。若是以爲本文有用,記得關注我,我將帶給你更多。

介紹

HttpUtils 是近期開源的對 OkHttp 輕量封裝的框架,它首創的異步預處理器,特點的標籤,靈活的上傳下載進度監聽過程控制功能,在輕鬆解決不少本來使人頭疼問題的同時,設計上也力求純粹與優雅。java

  • 鏈式調用,一點到底
  • BaseURL、URL佔位符、JSON自動封裝與解析
  • 同步攔截器、異步預處理器、回調執行器
  • 文件上傳下載(過程控制、進度監聽)
  • TCP鏈接池、Http2

項目地址 Gitee:https://gitee.com/ejlchina-zhxu/httputils GitHub:https://github.com/ejlchina/httputilsgit

安裝教程

Maven

<dependency>
     <groupId>com.ejlchina</groupId>
     <artifactId>httputils</artifactId>
     <version>2.2.0</version>
</dependency>

Gradle

compile 'com.ejlchina:httputils:2.2.0'

使用說明

1 簡單示例

1.1 構建 HTTP

HTTP http = HTTP.builder().build();

  以上代碼構建了一個最簡單的HTTP實例,它擁有如下三個方法:github

  • async(String url) 開始一個異步請求
  • sync(String url) 開始一個同步請求
  • cancel(String tag) 根據標籤批量取消請求

  爲了使用方便,在構建的時候,咱們更願意指定一個BaseUrl(請參見下文[5.1 設置 BaseUrl]):json

HTTP http = HTTP.builder()
        .baseUrl("http://api.demo.com")
        .build();

  爲了簡化文檔,下文中出現的http均是在構建時設置了BaseUrlHTTP實例。api

1.2 同步請求

  使用方法sync(String url)開始一個同步請求:數組

List<User> users = http.sync("/users") // http://api.demo.com/users
        .get()                         // GET請求
        .getBody()                     // 獲取響應報文體
        .toList(User.class);           // 獲得目標數據

  方法sync返回一個同步HttpTask,可鏈式使用。緩存

1.3 異步請求

  使用方法async(String url)開始一個異步請求:網絡

http.async("/users/1")                //  http://api.demo.com/users/1
        .setOnResponse((HttpResult result) -> {
            // 獲得目標數據
            User user = result.getBody().toBean(User.class);
        })
        .get();                       // GET請求

  方法async返回一個異步HttpTask,可鏈式使用。框架

2 請求方法(GET|POST|PUT|DELETE)

  同步與異步的HttpTask都擁有getpostputdelete方法。不一樣的是:同步HttpTask的這些方法返回一個HttpResult,而異步HttpTask的這些方法返回一個HttpCall異步

HttpResult res1 = http.sync("/users").get();     // 同步 GET
HttpResult res2 = http.sync("/users")post();     // 同步 POST
HttpResult res3 = http.sync("/users/1").put();   // 同步 PUT
HttpResult res4 = http.sync("/users/1").delete();// 同步 DELETE
HttpCall call1 = http.async("/users").get();     // 異步 GET
HttpCall call2 = http.async("/users").post();    // 異步 POST
HttpCall call3 = http.async("/users/1").put();   // 異步 PUT
HttpCall call4 = http.async("/users/1").delete();// 異步 DELETE

3 解析請求結果

3.1 回調函數

  只有異步請求才能夠設置回調函數:

http.async("/users/{id}")             // http://api.demo.com/users/1
        .addPathParam("id", 1)
        .setOnResponse((HttpResult result) -> {
            // 響應回調
        })
        .setOnException((Exception e) -> {
            // 異常回調
        })
        .setOnComplete((State state) -> {
            // 完成回調,不管成功失敗都會執行
        })
        .get();

3.2 HttpResult

  HttpResult是HTTP請求執行完後的結果,它是同步請求方法( getpostputdelete)的返回值,也是異步請求響應回調(OnResponse)的參數,它定義了以下方法:

  • getState() 獲得請求執行狀態枚舉,它有如下取值:
    • State.CANCELED 請求被取消
    • State.RESPONSED 已收到響應
    • State.TIMEOUT 請求超時
    • State.NETWORK_ERROR 網絡錯誤
    • State.EXCEPTION 其它請求異常
  • getStatus() 獲得HTTP狀態碼
  • isSuccessful() 是否響應成功,狀態碼在 [200..300) 之間
  • getHeaders() 獲得HTTP響應頭
  • getBody() 獲得響應報文體Body實例,它定義了以下方法(對同一個Body實例,如下的toXXX()類方法只能使用一個且僅能調用一次):
    • toBytes() 返回字節數組
    • toByteStream() 返回字節輸入流
    • toCharStream() 返回字符輸入流
    • toString() 返回字符串
    • toJsonObject() 返回Json對象
    • toJsonArray() 返回Json數組
    • toBean(Class<T> type) 返回根據type自動json解析後的JavaBean
    • toList(Class<T> type) 返回根據type自動json解析後的JavaBean列表
    • toFile(String filePath) 下載到指定路徑
    • toFile(File file) 下載到指定文件
    • toFolder(String dirPath) 下載到指定目錄
    • toFolder(File dir) 下載到指定目錄
    • getContentType() 返回報文體的媒體類型
    • getContentLength() 返回報文體的字節長度
    • close() 關閉報文體,未對報文體作任何消費時使用,好比只讀取報文頭
  • getError() 執行中發生的異常,自動捕獲執行請求是發生的 網絡超時、網絡錯誤 和 其它請求異常
  • close() 關閉報文,未對報文體作任何消費時使用,好比只讀取長度

  示例,請求結果自動轉Bean和List:

// 自動轉Bean
Order order = http.sync("/orders/1")
        .get().getBody().toBean(Order.class);
        
// 自動轉List
List<Order> orders = http.sync("/orders")
        .get().getBody().toList(Order.class);

  示例,下載文件到指定目錄:

String path = "D:/reports/2020-03-01.xlsx";    // 文件保存目錄

// 同步下載
http.sync("/reports/2020-03-01.xlsx")
        .get().getBody().toFile(path).start();

// 異步下載
http.async("/reports/2020-03-01.xlsx")
        .setOnResponse((HttpResult result) -> {
            result.getBody().toFile(path).start();
        })
        .get();

關於上傳下載更詳細的介紹請看後文:OkHttp 優雅封裝 HttpUtils 之 上傳下載解密

3.3 HttpCall

  HttpCall對象是異步請求方法(getpostputdelete)的返回值,與javaFuture接口很像,它有以下方法:

  • cancel() 取消本次請求,返回取消結果
  • isCanceled() 返回請求是否被取消
  • isDone() 返回是否執行完成,包含取消和失敗
  • getResult() 返回執行結果HttpResult對象,若請求未執行完,則掛起當前線程直到執行完成再返回

  取消一個異步請求示例:

HttpCall call = http.async("/users/1").get();

System.out.println(call.isCanceled());     // false

boolean success = call.cancel();           // 取消請求

System.out.println(success);               // true
System.out.println(call.isCanceled());     // true

4 構建HTTP任務

  HTTP對象的syncasync方法返回一個HttpTask對象,該對象提供了可鏈式調用的addXXXsetXXX系列方法用於構建任務自己。

  • addHeader(String name, String value) 添加請求頭

  • addHeader(Map<String, String> headers) 添加請求頭

  • addPathParam(String name, Object value) 添加路徑參數:替換URL裏的{name}佔位符

  • addPathParam(Map<String, ?> params) 添加路徑參數:替換URL裏的{name}佔位符

  • addUrlParam(String name, Object value) 添加URL參數:拼接在URL的?以後(查詢參數)

  • addUrlParam(Map<String, ?> params) 添加URL參數:拼接在URL的?以後(查詢參數)

  • addBodyParam(String name, Object value) 添加Body參數:以表單key=value&的形式放在報文體內(表單參數)

  • addBodyParam(Map<String, ?> params) 添加Body參數:以表單key=value&的形式放在報文體內(表單參數)

  • addJsonParam(String name, Object value) 添加Json參數:請求體爲Json(支持多層結構)

  • addJsonParam(Map<String, ?> params) 添加Json參數:請求體爲Json(支持多層結構)

  • setRequestJson(Object json) 設置請求體的Json字符串 或待轉換爲 Json的 JavaBean

  • setRequestJson(Object bean, String dateFormat) 設置請求體的Json字符串 或待轉換爲 Json的 JavaBean

  • addFileParam(String name, String filePath) 上傳文件

  • addFileParam(String name, File file) 上傳文件

  • addFileParam(String name, String type, InputStream inputStream) 上傳文件

  • addFileParam(String name, String type, String fileName, InputStream input) 上傳文件

  • addFileParam(String name, String type, byte[] content) 上傳文件

  • addFileParam(String name, String type, String fileName, byte[] content) 上傳文件

  • setTag(String tag) 爲HTTP任務添加標籤

  • setRange(long rangeStart) 設置Range頭信息,用於斷點續傳

  • setRange(long rangeStart, long rangeEnd) 設置Range頭信息,用於分塊下載

5 使用標籤

  有時候咱們想對HTTP任務加以分類,這時候可使用標籤功能:

http.async("/users")    //(1)
        .setTag("A").get();
        
http.async("/users")    //(2)
        .setTag("A.B").get();
        
http.async("/users")    //(3)
        .setTag("B").get();
        
http.async("/users")    //(4)
        .setTag("B.C").get();
        
http.async("/users")    //(5)
        .setTag("C").get();

  當使用標籤後,就能夠按標籤批量的對HTTP任務進行取消:

int count = http.cancel("B");              //(2)(3)(4)被取消(取消標籤包含"B"的任務)
System.out.println(count);                 // 輸出 3

  一樣的,只有異步HTTP任務才能夠被取消。標籤除了能夠用來取消任務,在預處理器中它也能夠發揮做用,請參見下文[6.4 並行預處理器]與[6.5 串行預處理器]。

6 配置 HTTP

6.1 設置 BaseUrl

HTTP http = HTTP.builder()
        .baseUrl("http://api.demo.com")    // 設置 BaseUrl
        .build();

  配置了BaseUrl以後,具體的請求即可以省略BaseUrl部分,使得代碼更加簡潔,例如:

http.sync("/users").get()                  // http://api.demo.com/users

http.sync("/auth/signin")                  // http://api.demo.com/auth/signin
        .addBodyParam("username", "Jackson")
        .addBodyParam("password", "xxxxxx")
        .post()                            // POST請求

  配置了BaseUrl以後,若有特殊請求,仍然可使用全路徑的方式,一點都不妨礙:

http.sync("https://www.baidu.com").get()

6.2 回調執行器

  如何想改變執行回調函數的線程時,能夠配置回調執行器。例如在Android裏,讓全部的回調函數都在UI線程執行,則能夠在構建HTTP時配置以下:

HTTP http = HTTP.builder()
        .callbackExecutor((Runnable run) -> {
            runOnUiThread(run);            // 在UI線程執行
        })
        .build();

  該配置影響的回調爲:OnResponseOnExceptionOnComplete

6.3 配置 OkHttpClient

  與其餘封裝 OkHttp 的框架不一樣,HttpUtils 並不會遮蔽 OkHttp 自己就很好用的功能,以下:

HTTP http = HTTP.builder()
    .config((Builder builder) -> {
        // 配置鏈接池 最小10個鏈接(不配置默認爲 5)
        builder.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES));
        // 配置鏈接超時時間
        builder.connectTimeout(20, TimeUnit.SECONDS);
        // 配置攔截器
        builder.addInterceptor((Chain chain) -> {
            Request request = chain.request();
            // 必須同步返回,攔截器內沒法執行異步操做
            return chain.proceed(request);
        });
        // 其它配置: SSL、緩存、代理、事件監聽...
    })
    .build();

6.4 並行預處理器

  預處理器(Preprocessor)可讓咱們在請求發出以前根據業務對請求自己作一些處理,但與 OkHttp 的攔截器(Interceptor)不一樣:預處理器可讓咱們異步處理這些問題。

  例如,當咱們想爲請求任務自動添加Token頭信息,而Token只能經過異步方法requestToken獲取時,這時使用Interceptor就很難處理了,但可使用預處理器輕鬆解決:

HTTP http = HTTP.builder()
        .addPreprocessor((PreChain chain) -> {
            HttpTask<?> task = chain.getTask();// 得到當前的HTTP任務
            if (!task.isTagged("Auth")) {      // 根據標籤判斷該任務是否須要Token
                return;
            }
            requestToken((String token) -> {   // 異步獲取 Token
                task.addHeader("Token", token);// 爲任務添加頭信息
                chain.proceed();               // 繼續當前的任務
            });
        })
        .build();

  和Interceptor同樣,Preprocessor也能夠添加多個。

6.5 串行預處理器

  普通預處理器都是可並行處理的,然而有時咱們但願某個預處理器同時只處理一個任務。好比 當Token過時時咱們須要去刷新獲取新Token,而刷新Token這個操做只能有一個任務去執行,由於若是n個任務同時執行的話,那麼必有n-1個任務剛刷新獲得的Token可能就立馬失效了,而這是咱們所不但願的。

  爲了解決這個問題,HttpUtils 提供了串行預處理器,它可讓HTTP任務排好隊,一個一個地進入預處理器:

HTTP http = HTTP.builder()
        .addSerialPreprocessor((PreChain chain) -> {
            HttpTask<?> task = chain.getTask();
            if (!task.isTagged("Auth")) {
                return;
            }
            // 檢查過時,若須要則刷新Token
            requestTokenAndRefreshIfExpired((String token) -> {
                task.addHeader("Token", token);  
                chain.proceed();               // 調用此方法前,不會有其它任務進入該處理器
            });
        })
        .build();

  串行預處理器實現了讓HTTP任務排隊串行處理的功能,但值得一提的是:它並無所以而阻塞任何線程!

7 使用 HttpUtils 類

  類HttpUtils本是 1.x 版本里的最重要的核心類,因爲在 2.x 版本里抽象出了HTTP接口,使得它的重要性已不如往昔。但合理的使用它,仍然能夠帶來很多便利,特別是在沒有IOC容器的環境裏,好比在Android開發和一些工具項目的開發中。

  類HttpUtils共定義了四個靜態方法:

  • async(String url) 開始一個異步請求 (內容經過一個HTTP單例實現)
  • sync(String url) 開始一個同步請求 (內容經過一個HTTP單例實現)
  • cancel(String tag) 按標籤取消請求(內容經過一個HTTP單例實現)
  • of(HTTP http) 配置HttpUtils持有的HTTP實例(不調用此方法前默認使用一個沒有沒有通過任何配置的HTTP懶實例)

  也就是說,能使用http實例的地方,均可以使用HttpUtils類,例如:

// 在配置HTTP實例以前,只能使用全路徑方式
List<Role> roles = HttpUtils.sync("http://api.demo.com/roles")
        .get().getBody().toList(Role.class);

// 配置HTTP實例,全局生效
HttpUtils.of(HTTP.builder()
        .baseUrl("http://api.demo.com")
        .build());

// 內部使用新的HTTP實例
List<User> users = HttpUtils.sync("/users")
        .get().getBody().toList(User.class);

下篇文章:OkHttp 優雅封裝 HttpUtils 之 上傳下載解密


曾經在代碼裏放蕩不羈,現在在博文中日夜兼行,只爲今天與你分享成果。若是以爲本文有用,記得關注我,我將帶給你更多。

相關文章
相關標籤/搜索