簡介
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'
複製程式碼
初始化
//設定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
物件,然後返回一個RxJava
的Observable
物件,此時,我們就可以使用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都有哪些靜態方法供我們選擇請求方式
現實中,這些預設的請求方式顯然不能滿足我們的需求,如:我要傳送加密的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 -> {
//失敗回撥
});
複製程式碼
這裡的this
為LifecycleOwner
物件,它是一個介面,這裡我們傳入的是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方法,我們來看一下:
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系列其它文章