RxHttp 一條鏈傳送請求,新一代Http請求神器(一)

不怕天黑發表於2019-04-22

簡介

RxHttp是基於OkHttp的二次封裝,並於RxJava做到無縫銜接,一條鏈就能傳送一個完整的請求。主要功能如下:

  • 支援Get、Post、Put、Delete等任意請求方式,可自定義請求方式
  • 支援Json、DOM等任意資料解析方式,可自定義資料解析器
  • 支援檔案下載/上傳,及進度的監聽,並且支援斷點下載
  • 支援在Activity/Fragment的任意生命週期方法,自動關閉未完成的請求
  • 支援新增公共引數/頭部資訊,且可動態更改baseUrl
  • 支援請求序列和並行

gradle依賴

implementation 'com.rxjava.rxhttp:rxhttp:1.0.3'
//註解處理器,生成RxHttp類,即可一條鏈傳送請求
annotationProcessor 'com.rxjava.rxhttp:rxhttp-compiler:1.0.3'
//管理RxJava及生命週期,Activity/Fragment 銷燬,自動關閉未完成的請求
implementation 'com.rxjava.rxlife:rxlife:1.0.4'
複製程式碼

RxHttp 原始碼 RxLife 原始碼

初始化

//設定debug模式,此模式下有日誌列印
HttpSender.setDebug(boolean debug)
//非必須,只能初始化一次,第二次將丟擲異常
HttpSender.init(OkHttpClient okHttpClient)
//或者,除錯模式下會有日誌輸出
HttpSender.init(OkHttpClient okHttpClient, boolean debug)
複製程式碼

此步驟是非必須的,不初始化或者傳入null即代表使用預設OkHttpClient物件。

疑問:標題不是說好的是RxHttp,這麼用HttpSender做一些初始化呢?這裡先賣一個關子,後面會解答

新增公共引數/頭部及重新設定url

相信大多數開發者在開發中,都遇到要為Http請求新增公共引數/請求頭,甚至要為不同型別的請求新增不同的公共引數/請求頭,為此,RxHttp為大家提供了一個靜態介面回撥,如下,每發起一次請求,此介面就會被回撥一次,並且此回撥在子執行緒進行(在請求執行執行緒回撥)

HttpSender.setOnParamAssembly(new Function() {
    @Override
    public Param apply(Param p) { 
        if (p instanceof GetRequest) {//根據不同請求新增不同引數
        } else if (p instanceof PostRequest) {
        } else if (p instanceof PutRequest) {
        } else if (p instanceof DeleteRequest) {
        }
        //可以通過 p.getSimpleUrl() 拿到url更改後,重新設定
        //p.setUrl("");
        return p.add("versionName", "1.0.0")//新增公共引數
                .addHeader("deviceType", "android"); //新增公共請求頭
    }
});
複製程式碼

然後有些請求我們不希望新增公共引數/請求頭,RxHttp又改如何實現呢?很簡單,發起請求前,設定不新增公共引數,如下:

Param param = Param.get("http://...")
        //設定是否對Param物件修飾,即是否新增公共引數,預設為true
        .setAssemblyEnabled(false); //設為false,就不會回撥上面的靜態介面
複製程式碼

到這,也許你們會有疑問,Param 是什麼東東,下面就為大家講解。

Param

首先,我們來看看如何傳送一個請求

Param param = Param.get("http://...")
        .add("key", "value");
Disposable disposable = HttpSender.from(param)
        .subscribe(s -> { //這裡的s為String型別,即Http請求的返回結果
           //成功回撥
        }, throwable -> {
           //失敗回撥
        });
複製程式碼

疑問:說好的一條鏈傳送請求呢?彆著急,還沒到放大招的時候 到這,我可以告訴大家,Param承擔的是一個請求體的一個角色,我們通過Param可以確定請求方式(如:Get、Post、Put、Delete等請求方式)、新增請求引數、新增請求頭、新增File物件等;然後通過HttpSender,傳入Param物件,將請求傳送出去。

HttpSender

到這,有人又有疑問,前面初始化、設定公共引數都用到了HttpSender,這裡傳送請求又用到了HttpSender ,那麼它又是承擔怎麼樣的一個角色呢?看名字,我們可以理解為它就是一個請求傳送者,通過一個from操作符,傳入一個Param物件,然後返回一個RxJavaObservable物件,此時,我們就可以使用RxJava強大的操作符去處理相關的邏輯(這就是簡介說的,做到了與RxJava的無縫連結),在這,我們只是使用了subscribe操作符去訂閱觀察者。

RxHttp

現在,我們正式放大招,標題說好的一條鏈傳送請求,既然吹牛了,就要去實現它。拿上面的例子,看看我們如何一條鏈實現,上程式碼

  RxHttp.get("http://...")
        .add("key", "value")
        .from()  
        .subscribe(s -> { //這裡的s為String型別,即Http請求的返回結果
           //成功回撥
        }, throwable -> {
           //失敗回撥
        });
複製程式碼

我們的主角RxHttp終於登場了,可以看到使用RxHttp類我們就實現了一條鏈完成請求的傳送,那它又是承擔一個什麼角色呢?我們暫時可以理解為RxHttp=Param+HttpSender,並且還有自己特殊的使命。至於什麼使用,後面會講解。

我們現在來解疑惑,為什麼我們的庫叫RxHttp,但是初始化、設定公共引數等卻用HttpSender?因為RxHttp這個類不在RxHttp庫中,它是通過註解處理器生成的類。前面我們看到gradle依賴時,使用了

annotationProcessor 'com.rxjava.rxhttp:rxhttp-compiler:1.0.2'
複製程式碼

該註解處理器的目的就是在專案中生成RxHttp類,那為何不直接把它寫到庫裡面去呢?前面講過,因為它有自己的使命,而這個使命,就是我們可以通過註解,在RxHttp中生成自定義的api,我們來看看如何使用註解。

動態設定baseUrl

現實開發中,大部人開發者都會將baseUrl 單獨抽取出來,RxHttp也考慮到了這一點,RxHttp通過@DefaultDomain註解來配置baseUrl,看程式碼

public class Url {
    @DefaultDomain() //設定為預設域名
    public static String baseUrl = "http://ip.taobao.com/";
}
複製程式碼

rebuild一下專案,此時我們傳送請求就可以直接傳入path路徑,如下:

  RxHttp.get("/service/getIpInfo.php")
        .add("key", "value")
        .from()  
        .subscribe(s -> { //這裡的s為String型別,即Http請求的返回結果
           //成功回撥
        }, throwable -> {
           //失敗回撥
        });
複製程式碼

RxHttp在傳送請求前,會對url做判斷,如果沒有域名,就會自定加上預設的域名,也就是baseUrl。然後,如果我們不想使用預設的域名呢?RxHttp也考慮到來,提供了一個@Domain註解,我們再來看看用法:

public class Url {
    @Domain(name = "Update9158") //設定非預設域名,name 可不傳,不傳預設為變數的名稱
    public static String update = "http://update.9158.com";

    @DefaultDomain() //設定為預設域名
    public static String baseUrl = "http://ip.taobao.com/";
}
複製程式碼

此時再rebuild一下專案,就會在RxHttp類中生成一個setDomainToUpdate9158IfAbsent()方法,其中的Update9158字元就是name指定的名字,然後發請求就可以這樣:

  RxHttp.get("/service/getIpInfo.php")
        .setDomainToUpdate9158IfAbsent()
        .add("key", "value")
        .from()  
        .subscribe(s -> { //這裡的s為String型別,即Http請求的返回結果
           //成功回撥
        }, throwable -> {
           //失敗回撥
        });
複製程式碼

此時,RxHttp檢測到url已經配置了域名,就不會再去使用預設的域名。同樣的,setDomainToUpdate9158IfAbsent也會檢測url 有沒有配置域名,如果配置了,也不會使用我們指定的域名。

注意:@Domain註解可以在多個地方使用,而@DefaultDomain()只能在一個地方使用,否則編譯不通過,很好理解,預設域名只可能有一個。兩個註解都要使用在public static修飾的String型別變數上,對final關鍵字沒有要求,可寫可不寫,這就表明,baseUrl 可以動態更改,RxHttp始終會拿到的最新的baseUrl 。怎麼樣,是不是很nice!!
更多註解使用請檢視RxHttp 一條鏈傳送請求之註解處理器 Generated API(四)

接下來,我們來看看,如何傳送Post請求、如何在Activity/Fragment銷燬時,自動關閉為完成的請求、如何上傳/下載檔案及進度的監聽、如何把Http返回的結果自動解析成我們想要的物件。

注:以下講解均使用RxHttp

Post

  RxHttp.postForm("http://...")
        .add("key", "value")
        .from()  
        .subscribe(s -> { //這裡的s為String型別,即Http請求的返回結果
           //成功回撥
        }, throwable -> {
           //失敗回撥
        });
複製程式碼

可以看到,跟上面的Get請求只有一點不同,Get是RxHttp.get,而Post是RxHttp.postForm,除此之外,沒有任何區別,我們在看來來,RxHttp都有哪些靜態方法供我們選擇請求方式

在這裡插入圖片描述
可以看到,預設提供了10個靜態方法供我們選擇具體的請求方式,有Get、Post、Put等,而Post等又分為postForm和postJson,這個好理解,前者是傳送表單形式的post請求,後者是傳送json字串的post請求。

現實中,這些預設的請求方式顯然不能滿足我們的需求,如:我要傳送加密的post請求,這個時候該怎麼辦呢?此時就需要我們自定義請求方式。自定義請求方式請檢視RxHttp 一條鏈傳送請求之強大的Param類(三)

Activity 銷燬,自動關閉未完成的請求

上面的案例中,在Activity/Fragment銷燬時,如果請求還未完成,就會造成Activity/Fragment 無法回收,導致記憶體洩漏。這是非常嚴重的問題,那麼RxHttp是如何解決的呢?此時,就要引入我自己寫的另一個庫RxLife,直接看看如何使用

  RxHttp.postForm("http://...")
        .add("key", "value")
        .from()
        .as(RxLife.as(this)) //訂閱觀察者前,加上這句話即可
        .subscribe(s -> {
           //成功回撥
        }, throwable -> {
           //失敗回撥
        });
  //或者
  RxHttp.postForm("http://...")
        .add("key", "value")
        .from()
        .as(RxLife.asOnMain(this)) //asOnMain 可以在主執行緒回撥觀察者
        .subscribe(s -> {
           //成功回撥
        }, throwable -> {
           //失敗回撥
        });
複製程式碼

這裡的thisLifecycleOwner物件,它是一個介面,這裡我們傳入的是Activity,因為Activity實現了LifecycleOwner介面。當Activity/Fragment銷燬時,會將RxJava的管道中斷,管道中斷時,又會將未完成的請求自動關閉。 對RxLife不瞭解的同學請檢視Android RxLife 一款輕量級別的RxJava生命週期管理庫 (一),這裡不詳細講解。在下面的講解中,我們均會使用RxLife

檔案上傳/下載及進度監聽

使用RxHttp,可以很優雅的實現檔案上傳/下載及進度的監聽,如何優雅?直接上程式碼

檔案上傳

  RxHttp.postForm("http://...") //傳送Form表單形式的Post請求
        .add("key", "value")
        .add("file1", new File("xxx/1.png")) //新增file物件
        .add("file2", new File("xxx/2.png"))
        .from() //from操作符,是非同步操作
        .as(RxLife.asOnMain(this))  //感知生命週期,並在主執行緒回撥
        .subscribe(s -> { 
            //成功回撥
        }, throwable -> {
            //失敗回撥
        });
複製程式碼

可以看到,檔案上傳跟普通的post請求其實沒啥區別,無非就是在post請求的基礎上,呼叫add方法新增要上傳的檔案物件。

檔案下載

  //檔案儲存路徑
  String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk";
  RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
        .download(destPath) //注意這裡使用download操作符,並傳入本地路徑
        .as(RxLife.asOnMain(this))  //感知生命週期,並在主執行緒回撥
        .subscribe(s -> {
            //下載成功,回撥檔案下載路徑
        }, throwable -> {
            //下載失敗
        });
複製程式碼

下載跟普通請求不同的是,下載使用的是download操作符,其它都一樣。

檔案下載進度監聽

//檔案儲存路徑
  String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk";
  RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
        .downloadProgress(destPath) //注:如果需要監聽下載進度,使用downloadProgress操作符
        .observeOn(AndroidSchedulers.mainThread())
        .doOnNext(progress -> {
            //下載進度回撥,0-100,僅在進度有更新時才會回撥,最多回撥101次,最後一次回撥檔案儲存路徑
            int currentProgress = progress.getProgress(); //當前進度 0-100
            long currentSize = progress.getCurrentSize(); //當前已下載的位元組大小
            long totalSize = progress.getTotalSize();     //要下載的總位元組大小
            String filePath = progress.getResult(); //檔案儲存路徑,最後一次回撥才有內容
        })
        .filter(Progress::isCompleted)//下載完成,才繼續往下走
        .map(Progress::getResult) //到這,說明下載完成,返回下載目標路徑
        .as(RxLife.as(this)) //感知生命週期
        .subscribe(s -> {//s為String型別,這裡為檔案儲存路徑
            //下載完成,處理相關邏輯
        }, throwable -> {
            //下載失敗,處理相關邏輯
        });
複製程式碼

下載進度的監聽我們稍微看一下 ,首先一點,下載使用download操作符,而下載進度監聽使用downloadProgress操作符,隨後,我們使用了doOnNext操作符處理進度回撥,注意這裡是僅當有進度更新時,才會回撥,其中的progress變數是一個Progress型別的物件,我們貼上原始碼:

public class Progress<T> {
    private int  progress; //當前進度 0-100
    private long currentSize;//當前已完成的位元組大小
    private long totalSize; //總位元組大小
    private T mResult; //http返回結果,上傳/下載完成時呼叫
    //省略get/set方法
}
複製程式碼

由於進度回撥會執行101次(上面註釋有解釋),而最下面觀察者其實是不需要關心這麼多事件的,只需要關心最後下載完成的事件,所以使用了filter操作符過濾事件,只要還未下載完成,就將事件過濾調,不讓往下走。最終下載完成後,拿到本地下載路徑。

檔案上傳進度監聽

  RxHttp.postForm("http://www.......") //傳送Form表單形式的Post請求
        .add("file1", new File("xxx/1.png"))
        .add("file2", new File("xxx/2.png"))
        .add("key1", "value1")//新增引數,非必須
        .add("key2", "value2")//新增引數,非必須
        .addHeader("versionCode", "100") //新增請求頭,非必須
        .uploadProgress() //注:如果需要監聽上傳進度,使用uploadProgress操作符
        .observeOn(AndroidSchedulers.mainThread()) //主執行緒回撥
        .doOnNext(progress -> {
            //上傳進度回撥,0-100,僅在進度有更新時才會回撥,最多回撥101次,最後一次回撥Http執行結果
            int currentProgress = progress.getProgress(); //當前進度 0-100
            long currentSize = progress.getCurrentSize(); //當前已上傳的位元組大小
            long totalSize = progress.getTotalSize();     //要上傳的總位元組大小
            String result = progress.getResult(); //Http執行結果,最後一次回撥才有內容
        })
        .filter(Progress::isCompleted)//過濾事件,上傳完成,才繼續往下走
        .map(Progress::getResult) //到這,說明上傳完成,拿到Http返回結果並繼續往下走
        .as(RxLife.as(this))  //感知生命週期
        .subscribe(s -> { //s為String型別,由SimpleParser類裡面的泛型決定的
            //上傳成功,處理相關邏輯
        }, throwable -> {
            //上傳失敗,處理相關邏輯
        });
複製程式碼

上傳進度監聽使用downloadProgress操作符,剩下的操作跟下載進度監聽的操作都一樣,通過doOnNext監聽上傳進度,然後過濾事件,最終拿到Http的返回結果。

資料解析器Parser

在上面的案例中,觀察者拿到資料型別都是String型別,然後現實開發中,我們經常需要對資料解析成我們想要的物件,RxHttp考慮到了這一點,現在我們就來看看如何的到我們想要的物件

我們拿淘寶獲取IP的介面作為測試介面http://ip.taobao.com/service/getIpInfo.php?ip=63.223.108.42 對應的資料結構如下

public class Response {
    private int     code;
    private Address data;
    //省略set、get方法

    class Address {
        //為簡單起見,省略了部分欄位
        private String country; //國家
        private String region; //地區
        private String city; //城市
        //省略set、get方法
    }
}
複製程式碼

開始傳送請求

  RxHttp.get("http://ip.taobao.com/service/getIpInfo.php") //Get請求
        .add("ip", "63.223.108.42")//新增引數
        .addHeader("accept", "*/*") //新增請求頭
        .addHeader("connection", "Keep-Alive")
        .addHeader("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)")
        .fromSimpleParser(Response.class)  //這裡返回Observable<Response> 物件
        .as(RxLife.asOnMain(this))  //感知生命週期,並在主執行緒回撥
        .subscribe(response -> {
            //成功回撥
        }, throwable -> {
            //失敗回撥
        });
複製程式碼

可以看到,這裡我們沒有用from操作符,而是用了fromSimpleParser操作符,並且傳入Response.class,最後觀察者拿到的response變數就是Response型別的物件。怎麼樣,是不是很簡單。RxHttp為我們提供了一系列的fromXXX方法,我們來看一下:

在這裡插入圖片描述
我們可以看到,一些基本型別的封裝物件RxHttp都為我們封裝好了,還有一個fromListParser方法,此方法是用來解析集合物件的,一些常見的資料結構,RxHttp都為我們考慮到了,並封裝好了,然後,一些不常見的資料呢?眼尖的你也許發現了,上圖中還有一個<T> Observable<T> from(Parser<T> parser)方法,它允許我們傳入一個自定義的解析器,更多解析器的介紹,請檢視RxHttp 之強大的資料解析功能(二)

最後,附上RxHttp一些常用的用法,如下:

  RxHttp.postForm("/service/getIpInfo.php")       //傳送Form表單形式的Post請求
        .setDomainToUpdate9158IfAbsent()  //手動設定域名,此方法是通過@Domain註解生成的
        .tag("RxHttp.get")          //為單個請求設定tag
        .setUrl("http://...")       //重新設定url
        .setAssemblyEnabled(false)  //設定是否新增公共引數,預設為true
        .cacheControl(CacheControl.FORCE_NETWORK)  //快取控制
        .setParam(Param.postForm("http://..."))    //重新設定一個Param物件
        .add(new HashMap<>())   //通過Map新增引數
        .add("int", 1)          //新增int型別引數
        .add("float", 1.28838F) //新增float型別引數
        .add("double", 1.28838) //新增double型別引數
        .add("key1", "value1")  //新增String型別引數
        .add("key2", "value2", false) //根據最後的boolean欄位判斷是否新增引數 
        .add("file1", new File("xxx/1.png"))            //新增檔案物件
        .addHeader("headerKey1", "headerValue1")        //新增頭部資訊
        .addHeader("headerKey2", "headerValue2", false)//根據最後的boolean欄位判斷是否新增頭部資訊 
        .fromSimpleParser(String.class)  //這裡返回Observable<T> 物件  fromXXX都是非同步操作符
        //感知生命週期,並在主執行緒回撥,當Activity/Fragment銷燬時,自動關閉未完成的請求
        .as(RxLife.asOnMain(this))  
        .subscribe(s -> {    //訂閱觀察者
            //成功回撥
        }, throwable -> {
            //失敗回撥
        });
複製程式碼

小結

到這,RxHttp的基本用法我們就講解完畢了,可以看到,使用RxHttp類一條鏈就能完成一個完整的Http請求,簡單一點,就是請求三部曲:

  • 首先,確定請求方式並新增相關引數
  • 然後,確定解析器,指定要解析成的型別
  • 最後,訂閱觀察者,開始傳送請求

以上所有的案例都離不開這3個步驟。最後,你會發現,RxHttp除了提供的一系列強大的功能外,在寫法上,不管什麼請求,都極其的相似,只要通過RxHttp類,就能一條鏈,完成所有的請求,極大的降低了學習成本。

注:要想在專案中生成RxHttp類,至少需要使用一次註解類,否則檢測不到註解,就無法生成。

如果你覺得RxHttp+RxLife好用,請記得給我star 如果有好的idea,請留言或者聯絡我。

更過詳情請檢視RxHttp系列其它文章

RxHttp 一條鏈傳送請求之強大的資料解析功能(二)

RxHttp 一條鏈傳送請求之強大的Param類(三)

RxHttp 一條鏈傳送請求之註解處理器 Generated API(四)

相關文章