RxHttp 讓你眼前一亮的Http請求框架

来源:https://www.cnblogs.com/liujingxing/archive/2019/12/26/12103229.html
-Advertisement-
Play Games

# 1、前言RxHttp在今年4月份一經推出,就受到了廣大Android 開發者的喜愛,截止本文發表在github上已有[800+star](https://github.com/liujingxing/RxHttp),為此,我自己也建個RxHttp&RxLife 的群(群號:378530627)目 ...


# 1、前言
RxHttp在今年4月份一經推出,就受到了廣大Android 開發者的喜愛,截止本文發表在github上已有[800+star](https://github.com/liujingxing/RxHttp),為此,我自己也建個RxHttp&RxLife 的群(群號:378530627)目前群里也有將近120號人,裡面有不少小伙伴提了很多有價值的創意,才使得RxHttp一直堅持走到了現在,在此,感謝大家的喜愛。

這期間,一直有人問我,retrofit不香嗎?之前不知道該如何回答這個問題,現在我想說,香!!retrofit無疑是目前綜合得分最高的選手,但它也有它的不足。

RxHttp相較於retrofit,功能上,兩者均能實現,並無多大差異,更多的差異體現功能的使用上,也就是易用性,如對文件上傳/下載/進度監聽的操作上,RxHttp用及簡的API,可以說碾壓retrofit;另外在baseUrl、公共參數/請求頭、請求加解密等功能上的易用性都要優於retrofit;然而這些,個人覺得都不算什麼,個人覺得RxHttp最大的優勢在於它近乎為0的上手成本、及簡的API以及高擴展性,看完這篇文章,相信你會有同感。

那RxHttp就沒有缺點嗎?有,那就是它的穩定性目前還不如retrofit,畢竟RxHttp剛出道8個月,且全部是我一個人在維護,當然,並不是說RxHttp不穩定,RxHttp未開源前,在我司的項目已經使用了近2年,接著今年4月份將其開源,至今大大小小已迭代20多個版本,目前用的人也不在少數,可以說很穩定了。

 

# 2、簡介
RxHttp是基於OkHttp的二次封裝,並與RxJava做到無縫銜接,一條鏈就能發送任意請求。主要優勢如下:

**1. 支持Gson、Xml、ProtoBuf、FastJson等第三方數據解析工具**

**2. 支持Get、Post、Put、Delete等任意請求方式,可自定義請求方式**

**3. 支持在Activity/Fragment/View/ViewModel/任意類中,自動關閉請求**

**4. 支持統一加解密,且可對單個請求設置是否加解密**

**5. 支持添加公共參數/頭部,且可對單個請求設置是否添加公共參數/頭部**

**6. 史上最優雅的實現文件上傳/下載及進度的監聽,且支持斷點下載**

**7. 史上最優雅的對錯誤統一處理,且不打破Lambda表達式**

**8. 史上最優雅的處理多個BaseUrl及動態BaseUrl**

**9. 史上最優雅的處理網路緩存**

**10. 30秒即可上手,學習成本極低**

**gradle依賴**
```java
implementation 'com.rxjava.rxhttp:rxhttp:1.3.6'
//註解處理器,生成RxHttp類,即可一條鏈發送請求
annotationProcessor 'com.rxjava.rxhttp:rxhttp-compiler:1.3.6'
//管理RxJava及生命周期,Activity/Fragment 銷毀,自動關閉未完成的請求
implementation 'com.rxjava.rxlife:rxlife:1.1.0'

//非必須 根據自己需求選擇Converter RxHttp預設內置了GsonConverter
implementation 'com.rxjava.rxhttp:converter-jackson:1.3.6'
implementation 'com.rxjava.rxhttp:converter-fastjson:1.3.6'
implementation 'com.rxjava.rxhttp:converter-protobuf:1.3.6'
implementation 'com.rxjava.rxhttp:converter-simplexml:1.3.6'
```
`註:kotlin用戶,請使用kapt替代annotationProcessor`

緩存功能,請查看:[RxHttp 全網Http緩存最優解](https://juejin.im/post/5dff3c2de51d45582c27cea6)

# 3、使用

## 3.1、準備工作
RxHttp 要求項目使用Java 8,請在 app 的 build.gradle 文件中添加以下代碼

```java
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
```
此時,再Rebuild一下項目(通過Rebuild生成RxHttp類),就可以開始RxHttp的入坑之旅

## 3.2、配置預設的BaseUrl

通過`@DefaultDomain`註解配置預設功能變數名稱,如下:

```java
public class Url {
@DefaultDomain //設置為預設功能變數名稱
public static String baseUrl = "https://www.wanandroid.com/";
}
```
此步驟是非必須的,這裡先介紹`@DefaultDomain`註解的用法,更多有關功能變數名稱的介紹,請查看本文3.6章節----多功能變數名稱/動態功能變數名稱
## 3.3、請求三部曲
先來看看如何發送一個最簡單的請求,如下
```java
RxHttp.get("http://...") //第一步, 通過get、postXxx、putXxx等方法,確定請求類型
.asString() //第二步, 通過asXxx系列方法,確定返回數據類型
.subscribe(s -> { //第三步, 訂閱回調(此步驟同RxJava訂閱觀察者)
//請求成功
}, throwable -> {
//請求失敗
});
```
是的,不用懷疑,就是這麼簡單,重要的事情說3遍

**任意請求,任意返回數據類型,皆遵循請求三部曲**

**任意請求,任意返回數據類型,皆遵循請求三部曲**

**任意請求,任意返回數據類型,皆遵循請求三部曲**

到這,你已經掌握了[RxHttp](https://github.com/liujingxing/RxHttp)的精髓,我們只需牢記請求三部曲,使用RxHttp就會得心應手。

### 3.3.1、第一部曲:確定請求類型
RxHttp內部共提供了14個請求方法,如下:
```java
RxHttp.get(String) //get請求 參數拼接在url後面
RxHttp.head(String) //head請求 參數拼接在url後面
RxHttp.postForm(String) //post請求 參數以{application/x-www-form-urlencoded}形式提交
RxHttp.postJson(String) //post請求 參數以{application/json; charset=utf-8}形式提交,發送Json對象
RxHttp.postJsonArray(String) //post請求 參數以{application/json; charset=utf-8}形式提交,發送Json數組
RxHttp.putForm(String) //put請求 參數以{application/x-www-form-urlencoded}形式提交
RxHttp.putJson(String) //put請求 參數以{application/json; charset=utf-8}形式提交,發送Json對象
RxHttp.putJsonArray(String) //put請求 參數以{application/json; charset=utf-8}形式提交,發送Json數組
RxHttp.patchForm(String) //patch請求 參數以{application/x-www-form-urlencoded}形式提交
RxHttp.patchJson(String) //patch請求 參數以{application/json; charset=utf-8}形式提交,發送Json對象
RxHttp.patchJsonArray(String) //patch請求 參數以{application/json; charset=utf-8}形式提交,發送Json數組
RxHttp.deleteForm(String) //delete請求 參數以{application/x-www-form-urlencoded}形式提交
RxHttp.deleteJson(String) //delete請求 參數以{application/json; charset=utf-8}形式提交,發送Json對象
RxHttp.deleteJsonArray(String) //delete請求 參數以{application/json; charset=utf-8}形式提交,發送Json數組
```
以上14個請求方法你會發現,其實就6個類型,分別對應是Get、Head、Post、Put、Patch、Delete方法,只是其中Post、Put、Patch、Delete各有3個方法有不同形式的提交方式,只需要根據自己的需求選擇就好。

如以上方法還不能滿足你的需求,我們還可以通過`@Param`註解自定義請求方法,有關註解的使用,本文後續會詳細介紹。

`註:當調用xxxForm方法發送請求時,通過setMultiForm()方法或者調用addFile(String, File)添加文件時,內部會自動將參數以{multipart/form-data}方式提交`

**添加參數/請求頭**

確定請求方法後,我們就可以調用一系列`addXxx()`方法添加參數/請求頭,如下:
```java
RxHttp.get("/service/...") //發送get請求
.add("key", "value") //添加參數
.addAll(new HashMap<>()) //通過Map添加多個參數
.addHeader("deviceType", "android") //添加請求頭
...
```
任意請求,都可調用以上3個方法添加參數/請求頭,當然,在不同的請求方式下,也會有不同的addXxx方法供開發者調用。如下:
```java
//postJson請求方法下會有更多addAll等方法可供調用
RxHttp.postJson("/service/...") //發送post Json請求
.addAll(new JsonObject()) //通過json對象添加多個參數
.addAll("{\"height\":180,\"weight\":70}") //通過json字元串添加多個參數
...

//postForm請求方法下會有一系列addFile方法可供調用
RxHttp.postForm("/service/...") //發送post表單請求
.addFile("file", new File("xxx/1.png")) //添加單個文件
.addFile("fileList", new ArrayList<>()) //添加多個文件
...
```

以上只列出了幾個常用的addXxx方法,更多方法請下載源碼體驗。

### 3.3.2、第二部曲:確定返回數據類型
添加好參數/請求頭後,正式進入第二部曲,確定返回數據類型,我們通過`asXxx`方法確定返回類型,比如,我們要返回一個Student對象,就可以通過`asObject(Class<T>)`方法,如下:
```java
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asObject(Student.class) //返回Student類型
.subscribe(student -> {
//請求成功,這裡就能拿到 Student對象
}, throwable -> {
//請求失敗
});
```
如果要返回Student對象列表,則可以通過`asList(Class<T>)`方法,如下:
```java
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asList(Student.class) //返回List<Student>類型
.subscribe(students -> {
//請求成功,這裡就能拿到 Student對象列表
}, throwable -> {
//請求失敗
});
```
**解析`Response<T>`類型數據**

然而,現實開發中,大多數人的介面,返回的數據結構都類似下麵的這個樣子
```java
public class Response<T> {
private int code;
private String msg;
private T data;
//這裡省略get、set方法
}
```
對於這種數據結構,按傳統的寫法,每次都要對code做判斷,如果有100個請求,就要判斷100次,真的會逼死強迫症患者。

RxHttp對於這種情況,給出完美的答案,比如`Response<T>`裡面的T代表一個Student對象,則可以通過`asResponse(Class<T>)`方法獲取,如下:
```java
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asResponse(Student.class) //返回Student類型
.subscribe(student -> {
//請求成功,這裡能拿到 Student對象
}, throwable -> {
//請求失敗
});
```
如果`Response<T>`裡面的T代表一個`List<Student>`列表對象,則可以通過`asResponseList(Class<T>)`方法獲取,如下
```java
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asResponseList(Student.class) //返回List<Student>類型
.subscribe(students -> {
//請求成功,這裡能拿到List<Student>列表對象
}, throwable -> {
//請求失敗
});
```
更多時候,我們的列表數據是分頁的,類似下麵的數據結構
```java
{
"code": 0,
"msg": "",
"data": {
"totalPage": 0,
"list": []
}
}
```
此時,調用RxHttp的`asResponsePageList(Class<T>)`方法依然可以完美解決,如下:

```java
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asResponsePageList(Student.class) //返回PageList<Student>類型
.subscribe(pageList -> {
//請求成功,這裡能拿到PageList<Student>列表對象
int totalPage = pageList.getTotalPage(); //總頁數
List<Student> students = pageList.getData(); //單頁列表數據
}, throwable -> {
//請求失敗
});
```
到這,估計很多人會問我:
- 你的code在哪裡判斷的?
- 我的code是100或者其它值才代表正確,怎麼改?
- 我的`Response<T>`類裡面的欄位名,跟你的都不一樣,怎麼該?
- 你這成功的時候直接返回`Response<T>`裡面的T,那我還要拿到code做其他的判斷,執行不同業務邏輯,怎麼辦?

這裡可以先告訴大家,`asResponse(Class<T>)`、`asResponseList(Class<T>)`、`asResponsePageList(Class<T>)`這3個方法並不是RxHttp內部提供的,而是通過自定義解析器生成,裡面的code判斷、`Response<T>`類都是開發者自定義的,如何自定義解析器,請查看本文5.1章節----自定義Parser。

接著回答第4個問題,如何拿到code做其他的業務邏輯判斷,很簡單,我們只需用`OnError`介面處理錯誤回調即可,如下:
```java
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asResponse(Student.class) //返回Student類型
.subscribe(student -> {
//請求成功,這裡能拿到 Student對象
}, (OnError) error -> { //註意,這裡要用OnError介面,其中error是一個ErrorInfo對象
//失敗回調
//拿到code欄位,此時就可以對code做判斷,執行不同的業務邏輯
int code = error.getErrorCode();
String errorMsg = error.getErrorMsg() //拿到msg欄位
});
```
`註:上面的OnError介面並非是RxHttp內部提供的,而是自定義的,在Demo里可以找到`

以上介紹的5個asXxx方法,可以說基本涵蓋80%以上的業務場景,接下來我們看看RxHttp都提供了哪些asXxx方法,如下:![](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d31d028d95?w=1348&h=832&f=png&s=238055)
RxHttp內部共提供了`23`個`asXXX`方法,其中:
- 有7個是返回基本類型的包裝類型,如:asInteger、asBoolean、asLong等等;
- 還有7個是返回對象類型,如:asString、asBitmap、asList、asMap(3個)以及最常用`asObject`方法;
- 剩下9個是`asParser(Parser<T>)`、 `asUpload`系列方法及`asDownload`系列方法。

duang、duang、duang !!! 劃重點,這裡我可以告訴大家,其實前面的14個方法,最終都是通過`asParser(Parser<T>)`方法實現的,具體實現過程,這裡先跳過,後續會詳細講解。

### 3.3.3、第三部曲:訂閱回調
這一步就很簡單了,在第二部曲中,asXxx方法會返回`Observable<T>`對象,沒錯,就是RxJava內部的`Observable<T>`對象,此時我們便可通過`subscribe`系列方法訂閱回調,如下:
```java
//不處理任何回調
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asResponseList(Student.class) //返回List<Student>類型
.subscribe(); //不訂閱任何回調

//僅訂閱成功回調
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asResponseList(Student.class) //返回List<Student>類型
.subscribe(students -> {
//請求成功,這裡能拿到List<Student>列表對象
});

//訂閱成功與失敗回調
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asResponseList(Student.class) //返回List<Student>類型
.subscribe(students -> {
//請求成功,這裡能拿到List<Student>列表對象
}, throwable -> {
//請求失敗
});

//等等,省略
```
另外,我們還可以訂閱請求開始/結束的回調,如下:
```java
RxHttp.get("/service/...")
.asString()
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> {
//請求開始,當前在主線程回調
})
.doFinally(() -> {
//請求結束,當前在主線程回調
})
.as(RxLife.as(this)) //感知生命周期
.subscribe(pageList -> {
//成功回調,當前在主線程回調
}, (OnError) error -> {
//失敗回調,當前在主線程回調
});
```

到這,請求三部曲介紹完畢,接著,將介紹其它常用的功能

## 3.4、初始化
```java
//設置debug模式,預設為false,設置為true後,發請求,過濾"RxHttp"能看到請求日誌
RxHttp.setDebug(boolean debug)
//非必須,只能初始化一次,第二次將拋出異常
RxHttp.init(OkHttpClient okHttpClient)
//或者,調試模式下會有日誌輸出
RxHttp.init(OkHttpClient okHttpClient, boolean debug)
```
此步驟是非必須的,如需要添加攔截器等其他業務需求,則可調用`init`方法進行初始化,不初始化或者傳入`null`即代表使用預設OkHttpClient對象,建議在Application中初始化,預設的OkHttpClient對象在HttpSender類中可以找到,如下:

```java
private static OkHttpClient getDefaultOkHttpClient() {
X509TrustManager trustAllCert = new X509TrustManagerImpl();
SSLSocketFactory sslSocketFactory = new SSLSocketFactoryImpl(trustAllCert);
return new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.sslSocketFactory(sslSocketFactory, trustAllCert) //添加信任證書
.hostnameVerifier((hostname, session) -> true) //忽略host驗證
.build();
}
```
雖然初始化是非必須的,但是建議大家傳入自定義的OkHttpClient對象,一來,自定義的OkHttpClient能最大化滿足自身的業務;二來,隨著RxHttp版本的升級,預設的OkHttpClient可能會發生變化(雖然可能性很小),故建議自定義OkHttpClient對象傳入RxHttp。

## 3.5、公共參數/請求頭
RxHttp支持為所有的請求添加公共參數/請求頭,當然,如果你希望某個請求不添加公共參數/請求頭,也是支持的,而且非常簡單。如下:
```java
RxHttp.setOnParamAssembly(new Function() {
@Override
public Param apply(Param p) { //此方法在子線程中執行,即請求發起線程
Method method = p.getMethod();
if (method.isGet()) { //可根據請求類型添加不同的參數
} else if (method.isPost()) {
}
return p.add("versionName", "1.0.0")//添加公共參數
.addHeader("deviceType", "android"); //添加公共請求頭
}
});
```
我們需要調用`RxHttp.setOnParamAssembly(Function)`方法,並傳入一個Function介面對象,每次發起請求,都會回調該介面。

當然,如果希望某個請求不回調該介面,即不添加公共參數/請求頭,則可以調用`setAssemblyEnabled(boolean)`方法,並傳入false即可,如下:
```java
RxHttp.get("/service/...") //get請求
.setAssemblyEnabled(false) //設置是否添加公共參數/頭部,預設為true
.asString() //返回字元串數據
.subscribe(s -> { //這裡的s為String類型
//請求成功
}, throwable -> {
//請求失敗
});
```

## 3.6、多功能變數名稱/動態功能變數名稱

**3.6.1、多功能變數名稱**

現實開發中,我們經常會遇到多個功能變數名稱的情況,其中1個為預設功能變數名稱,其它為非預設功能變數名稱,對於這種情況,RxHttp提供了`@DefaultDomain()`、`@Domain()`這兩個註解來標明預設功能變數名稱和非預設功能變數名稱,如下:

```java
public class Url {
@DefaultDomain() //設置為預設功能變數名稱
public static String baseUrl = "https://www.wanandroid.com/"

@Domain(name = "BaseUrlBaidu") //非預設功能變數名稱,並取別名為BaseUrlBaidu
public static String baidu = "https://www.baidu.com/";

@Domain(name = "BaseUrlGoogle") //非預設功能變數名稱,並取別名為BaseUrlGoogle
public static String google = "https://www.google.com/";
}
```
通過`@Domain()`註解標註非預設功能變數名稱,就會在RxHttp類中生成`setDomainToXxxIfAbsent()`方法,其中Xxx就是註解中取的別名。

上面我們使用了兩個`@Domain()`註解,此時(需要Rebuild一下項目)就會在RxHttp類中生成`setDomainToBaseUrlBaiduIfAbsent()`、`setDomainToBaseUrlGoogleIfAbsent()`這兩方法,此時發請求,我們就可以使用指定的功能變數名稱,如下:
```java
//使用預設功能變數名稱,則無需添加任何額外代碼
//此時 url = "https://www.wanandroid.com/service/..."
RxHttp.get("/service/...")
.asString()
.subscribe();

//手動輸入功能變數名稱,此時 url = "https://www.mi.com/service/..."
RxHttp.get("https://www.mi.com/service/...")
.asString()
.subscribe();

//手動輸入功能變數名稱時,若再次指定功能變數名稱,則無效
//此時 url = "https://www.mi.com/service/..."
RxHttp.get("https://www.mi.com/service/...")
.setDomainToBaseUrlBaiduIfAbsent() //此時指定Baidu功能變數名稱無效
.asString()
.subscribe();

//使用谷歌功能變數名稱,此時 url = "https://www.google.com/service/..."
RxHttp.get("/service/...")
.setDomainToBaseUrlGoogleIfAbsent() //指定使用Google功能變數名稱
.asString()
.subscribe();
```

通過以上案例,可以知道,RxHttp共有3種指定功能變數名稱的方式,按優先順序排名分別是:手動輸入功能變數名稱 > 指定非預設功能變數名稱 > 使用預設功能變數名稱。

**3.6.2、動態功能變數名稱**

現實開發中,也會有動態功能變數名稱切換的需求,如功能變數名稱被封、或者需要根據服務端下發的功能變數名稱去配置,這對於RxHttp來說簡直就是 so easy !!! 我們只需要對BaseUrl重新賦值,此時發請求便會立即生效,如下:
```java
//此時 url = "https://www.wanandroid.com/service/..."
RxHttp.get("/service/...")
.asString()
.subscribe();

Url.baseUrl = "https://www.qq.com"; //動態更改預設功能變數名稱,改完立即生效,非預設功能變數名稱同理
//此時 url = "https://www.qq.com/service/..."
RxHttp.get("/service/...")
.asString()
.subscribe();
```
## 3.7、關閉請求
我們知道,在Activity/Fragment中發起請求,如果頁面銷毀時,請求還未結束,就會有記憶體泄漏的危險,因此,我們需要在頁面銷毀時,關閉一些還未完成的請求,RxHttp提供了兩種關閉請求的方式,分別是自動+手動。

**3.7.1、自動關閉請求**

自動關閉請求,需要引入本人開源的另一個庫[RxLife](https://github.com/liujingxing/RxLife),先來看看如何用:
```java
//以下代碼均在FragmentActivty/Fragment中調用

RxHttp.postForm("/service/...")
.asString()
.as(RxLife.as(this)) //頁面銷毀、自動關閉請求
.subscribe();
//或者
RxHttp.postForm("/service/...")
.asString()
.as(RxLife.asOnMain(this)) //頁面銷毀、自動關閉請求 並且在主線程回調觀察者
.subscribe();

//kotlin用戶,請使用life或lifeOnMain方法,如下:
RxHttp.postForm("/service/...")
.asString()
.life(this) //頁面銷毀、自動關閉請求
.subscribe();
//或者
RxHttp.postForm("/service/...")
.asString()
.lifeOnMain(this) //頁面銷毀、自動關閉請求 並且在主線程回調觀察者
.subscribe();
```
上面的`this`為`LifecycleOwner`介面對象,我們的FragmentActivity/Fragment均實現了這個介面,所有我們在FragmentActivity/Fragment中可以直接傳`this`。
對`RxLife`不瞭解的同學請查看[RxLife 史上最優雅的管理RxJava生命周期](https://juejin.im/post/5cf3e1235188251c064815f1),這裡不詳細講解。

**3.7.2、手動關閉請求**

手動關閉請求,我們只需要在訂閱回調的時候拿到Disposable對象,通過該對象可以判斷請求是否結束,如果沒有,就可以關閉請求,如下:
```java
//訂閱回調,可以拿到Disposable對象
Disposable disposable = RxHttp.get("/service/...")
.asString()
.subscribe(s -> {
//成功回調
}, throwable -> {
//失敗回調
});

if (!disposable.isDisposed()) { //判斷請求有沒有結束
disposable.dispose(); //沒有結束,則關閉請求
}
```

## 3.8、文件上傳/下載/進度監聽
RxHttp可以非常優雅的實現上傳/下載及進度的監聽,是騾子是馬,拉出來溜溜

**3.8.1上傳**

通過addFile系列方法添加文件,如下:
```java
RxHttp.postForm("/service/...") //發送Form表單形式的Post請求
.addFile("file1", new File("xxx/1.png")) //添加單個文件
.addFile("fileList", new ArrayList<>()) //通過List對象,添加多個文件
.asString()
.subscribe(s -> {
//上傳成功
}, throwable -> {
//上傳失敗
});
```
通過asUpload系列方法監聽上傳進度,如下:
```java
RxHttp.postForm("/service/...") //發送Form表單形式的Post請求
.addFile("file1", new File("xxx/1.png"))
.addFile("file2", new File("xxx/2.png"))
.asUpload(progress -> {
//上傳進度回調,0-100,僅在進度有更新時才會回調
int currentProgress = progress.getProgress(); //當前進度 0-100
long currentSize = progress.getCurrentSize(); //當前已上傳的位元組大小
long totalSize = progress.getTotalSize(); //要上傳的總位元組大小
}, AndroidSchedulers.mainThread()) //指定回調(進度/成功/失敗)線程,不指定,預設在請求所線上程回調
.subscribe(s -> {
//上傳成功
}, throwable -> {
//上傳失敗
});
```
可以看到,跟上傳的代碼相比,我們僅僅是使用了`asUpload(Consumer, Scheduler)`方法替換`asString()`方法,第一個參數是進度監聽介面,每當進度有更新時,都會回調該介面,第二個參數是指定回調的線程,這裡我們指定了在UI線程中回調。

**3.8.2、下載**

下載使用`asDownload(String)`方法,傳入本地路徑即可
```java
//文件存儲路徑
String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk";
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
.asDownload(destPath) //註意這裡使用asDownload操作符,並傳入本地路徑
.subscribe(s -> {
//下載成功,回調文件下載路徑
}, throwable -> {
//下載失敗
});
```
**3.8.3、帶進度下載**

帶進度下載使用`asDownload(String,Consumer,Scheduler)`方法
```java
//文件存儲路徑
String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk";
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
.asDownload(destPath, progress -> {
//下載進度回調,0-100,僅在進度有更新時才會回調,最多回調101次,最後一次回調文件存儲路徑
int currentProgress = progress.getProgress(); //當前進度 0-100
long currentSize = progress.getCurrentSize(); //當前已下載的位元組大小
long totalSize = progress.getTotalSize(); //要下載的總位元組大小
}, AndroidSchedulers.mainThread()) //指定主線程回調
.subscribe(s -> {//s為String類型,這裡為文件存儲路徑
//下載完成,處理相關邏輯
}, throwable -> {
//下載失敗,處理相關邏輯
});
```

**3.8.4、斷點下載**

`斷點下載`相較於`下載`,僅需要調用`setRangeHeader(long startIndex, long endIndex)`方法傳入開始及結束位置即可(結束位置不傳預設為文件末尾),其它沒有任何差別
```java
String destPath = getExternalCacheDir() + "/" + "Miaobo.apk";
long length = new File(destPath).length(); //已下載的文件長度
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
.setRangeHeader(length) //設置開始下載位置,結束位置預設為文件末尾
.asDownload(destPath)
.subscribe(s -> { //s為String類型
//下載成功,處理相關邏輯
}, throwable -> {
//下載失敗,處理相關邏輯
});
```

**3.8.5、帶進度斷點下載**

`帶進度斷點下載`相較於`帶進度下載`僅需要調用`setRangeHeader`方法傳入開始及結束位置即可(結束位置不傳預設為文件末尾),其它沒有任何差別
```java
String destPath = getExternalCacheDir() + "/" + "Miaobo.apk";
long length = new File(destPath).length(); //已下載的文件長度
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
.setRangeHeader(length) //設置開始下載位置,結束位置預設為文件末尾
.asDownload(destPath, progress -> {
//下載進度回調,0-100,僅在進度有更新時才會回調
int currentProgress = progress.getProgress(); //當前進度 0-100
long currentSize = progress.getCurrentSize(); //當前已下載的位元組大小
long totalSize = progress.getTotalSize(); //要下載的總位元組大小
}, AndroidSchedulers.mainThread()) //指定主線程回調
.subscribe(s -> { //s為String類型
//下載成功,處理相關邏輯
}, throwable -> {
//下載失敗,處理相關邏輯
});
```
`註:`上面帶進度斷點下載中,返回的進度會從0開始,如果需要銜接上次下載的進度,則調用`asDownload(String,long,Consumer,Scheduler)`方法傳入上次已經下載好的長度(第二個參數),如下:

```java
String destPath = getExternalCacheDir() + "/" + "Miaobo.apk";
long length = new File(destPath).length(); //已下載的文件長度
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
.setRangeHeader(length) //設置開始下載位置,結束位置預設為文件末尾
.asDownload(destPath, length, progress -> {
//下載進度回調,0-100,僅在進度有更新時才會回調
int currentProgress = progress.getProgress(); //當前進度 0-100
long currentSize = progress.getCurrentSize(); //當前已下載的位元組大小
long totalSize = progress.getTotalSize(); //要下載的總位元組大小
}, AndroidSchedulers.mainThread()) //指定主線程回調
.subscribe(s -> { //s為String類型
//下載成功,處理相關邏輯
}, throwable -> {
//下載失敗,處理相關邏輯
});
```

## 3.9、超時設置

**3.9.1、設置全局超時**


RxHttp內部預設的讀、寫、連接超時時間均為10s,如需修改,請自定義OkHttpClient對象,如下:
```java
//設置讀、寫、連接超時時間為15s
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build();
RxHttp.init(client);
```
**3.9.2、為單個請求設置超時**

為單個請求設置超時,使用的是RxJava的`timeout(long timeout, TimeUnit timeUnit)`方法,如下:
```java
RxHttp.get("/service/...")
.asString()
.timeout(5, TimeUnit.SECONDS)//設置總超時時間為5s
.as(RxLife.asOnMain(this)) //感知生命周期,併在主線程回調
.subscribe(pageList -> {
//成功回調
}, (OnError) error -> {
//失敗回調
});
```
**註:這裡設置的總超時時間要小於全局讀、寫、連接超時時間之和,否則無效**


## 3.10、設置Converter

**3.10.1、設置全局Converter**

```java
IConverter converter = FastJsonConverter.create();
RxHttp.setConverter(converter)
```
**3.10.2、為請求設置單獨的Converter**

首先需要在任意public類中通過@Converter註解聲明Converter,如下:
```java
public class RxHttpManager {
@Converter(name = "XmlConverter") //指定Converter名稱
public static IConverter xmlConverter = XmlConverter.create();
}
```
然後,rebuild 一下項目,就在自動在RxHttp類中生成`setXmlConverter()`方法,隨後就可以調用此方法為單個請求指定Converter,如下:

```java
RxHttp.get("/service/...")
.setXmlConverter() //指定使用XmlConverter,不指定,則使用全局的Converter
.asObject(NewsDataXml.class)
.as(RxLife.asOnMain(this)) //感知生命周期,併在主線程回調
.subscribe(dataXml -> {
//成功回調
}, (OnError) error -> {
//失敗回調
});
```
## 3.11、請求加解密

**3.11.1、加密**

請求加密,需要自定義Param,非常簡單,詳情請查看本文5.2章節----自定義Param

**3.11.2、解密**

有些時候,請求會返回一大串的密文,此時就需要將密文轉化為明文,直接來看代碼,如下:
```java
//設置數據解密/解碼器
RxHttp.setResultDecoder(new Function<String, String>() {
//每次請求成功,都會回調這裡,並傳入請求返回的密文
@Override
public String apply(String s) throws Exception {
String plaintext = decode(s); //將密文解密成明文,解密邏輯自己實現
return plaintext; //返回明文
}
});
```
很簡單,通過`RxHttp.setResultDecoder(Function<String, String>)`靜態方法,傳入一個介面對象,此介面會在每次請求成功的時候被回調,並傳入請求返回的密文,只需要將密文解密後返回即可。

然而,有些請求是不需求解密的,此時就可以調用`setDecoderEnabled(boolean)`方法,並傳入false即可,如下:
```java
RxHttp.get("/service/...")
.setDecoderEnabled(false) //設置本次請求不需要解密,預設為true
.asString()
.subscribe(pageList -> {
//成功回調
}, (OnError) error -> {
//失敗回調
});
```

## 3.12、指定請求/回調線程
RxHttp預設在Io線程執行請求,也預設在Io線程回調,即預設在同一Io線程執行請求並回調,當然,我們也可以指定請求/回調所線上程。

**3.12.1、指定請求所線上程**

我們可以調用一些列subscribeXxx方法指定請求所線上程,如下:
```java
//指定請求所線上程,需要在第二部曲前任意位置調用,第二部曲後調用無效
RxHttp.get("/service/...")
.subscribeOnCurrent() //指定在當前線程執行請求,即同步執行,
.asString()
.subscribe();

//其它subscribeXxx方法
subscribeOnIo() //RxHttp預設的請求線程
subscribeOnSingle()
subscribeOnNewThread()
subscribeOnComputation()
subscribeOnTrampoline()
subscribeOn(Scheduler) //自定義請求線程
```
以上使用的皆是RxJava的線程調度器,不熟悉的請自行查閱相關資料,這裡不做詳細介紹。

**3.12.2、指定回調所線上程**

指定回調所線上程,依然使用RxJava的線程調度器,如下:
```java
//指定回調所線上程,需要在第二部曲後調用
RxHttp.get("/service/...")
.asString()
.observeOn(AndroidSchedulers.mainThread()) //指定在主線程回調
.subscribe(s -> { //s為String類型,主線程回調
//成功回調
}, throwable -> {
//失敗回調
});
```

## 3.13、 Retrofit用戶

時常會有童鞋問我,我是Retrofit用戶,喜歡把介面寫在一個類里,然後可以直接調用,RxHttp如何實現?其實,這個問題壓根就不是問題,在介紹第二部曲的時候,我們知道,使用asXxx方法後,就會返回`Observable<T>`對象,因此,我們就可以這樣實現:
```java
public class HttpWrapper {

public static Observable<List<Student>> getStudent(int page) {
return RxHttp.get("/service/...")
.add("page", page)
.asList(Student.class);
}
}

//隨後在其它地方就可以直接調用
HttpWrapper.getStudent(1)
.as(RxLife.asOnMain(this)) //主線程回調,併在頁面銷毀自動關閉請求(如果還未關閉的話)
.subscribe(students -> { //學生列表
//成功回調
}, throwable -> {
//失敗回調
});
```
很簡單,封裝的時候返回`Observable<T>`對象即可。

還有的同學問,我們獲取列表的介面,頁碼是和url拼接在一起的,Retrofit可以通過占位符,那RxHttp又如何實現?簡單,如下:
```java
public class HttpWrapper {

//單個占位符
public static Observable<Student> getStudent(int page) {
return RxHttp.get("/service/%d/...", page) //使用標準的占位符協議
.asObject(Student.class);
}

//多個占位符
public static Observable<Student> getStudent(int page, int count) {
return RxHttp.get("/service/%1$d/%2$d/...", page, count) //使用標準的占位符協議
.asObject(Student.class);
}
}
```
這一點跟Retrofit不同,Retrofit是通過註解指定占位符的,而RxHttp是使用標準的占位符,我們只需要在url中聲明占位符,隨後在傳入url的後面,帶上對應的參數即可。

# 4、原理剖析

## 4.1、工作流程

在RxHttp有4個重要的角色,分別是:
- Param:RxHttp類中所有添加的參數/請求頭/文件都交由它處理,它最終目的就是為了構建一個Request對象
- HttpSender :它負責從Param對象中拿到Request對象,從而執行請求,最終返回Response對象
- Parser:它負責將HttpSender返回的Response對象,解析成我們期望的實體類對象,也就是泛型T
- RxHttp:它像一個管家,指揮前面3個角色做事情,當然,它也有自己的事情要做,比如:請求線程的調度,BaseUrl的處理、允許開發者自定義API等等


為此,我畫了一個流程圖,可以直觀的瞭解到RxHttp的大致工作流程
![在這裡插入圖片描述](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d31d469969?w=1308&h=712&f=png&s=143004)

我想應該很好理解,RxHttp要做的事情,就是把添加的參數/請求頭等全部丟給Param處理,自己啥事也不敢;隨後將Param交給HttpSender,讓它去執行請求,執行完畢,返回Response對象;接著又將Response對象丟給Parser去做數據解析工作,並返回實體類對象T;最後,將T通過回調傳給開發者,到此,一個請求就處理完成。


## 4.2、Param
首先,附上一張Param類的繼承關係圖
![](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d31ed76c6f?w=1180&h=896&f=png&s=78084)
下麵將從上往下對上圖中的類做個簡單的介紹:
- IHeaders:介面類,裡面定義了一系列addHeader方法
- IParam:介面類,裡面定義了add(String,Object)、addAll(Map)等方法,
- IRequest:介面類,裡面定義了一系列getXxx方法,通過這些方法最終構建一個Request對象
- Param:介面類,是一個空介面,繼承了前面3個介面,裡面有一系列靜態方法可以獲取到Param的具體實現類
- AbstractParam:Param介面的抽象實現類,實現了Param介面的所有方法
- IFile:介面類,裡面定義了一系列addFile方法
- IUploadLengthLimit:介面類,裡面就定義了一個checkLength()方法,用於限制文件上傳大小
- NoBodyParam:Param的具體實現類,Get、Head請求就是通過該類去實現的
- JsonParam:Param的具體實現類,調用RxHttp.xxxJson(String)請求方法時,內部就是通過該類去實現的
- JsonArrayParam:Param的具體實現類,調用RxHttp.xxxJsonArray(String)請求方法時,內部就是通過該類去實現的
- FormParam:Param的具體實現類,同時又實現了IFile、IUploadLengthLimit兩個介面,調用RxHttp.xxxForm(String)請求方法時,內部就是通過該類去實現的


## 4.3、HttpSender
HttpSender可以把它理解為請求發送者,裡面聲明OkHttpClient對象和一系列靜態方法,我們來簡單看下:
```java
public final class HttpSender {

private static OkHttpClient mOkHttpClient; //只能初始化一次,第二次將拋出異常
//處理化OkHttpClient對象
public static void init(OkHttpClient okHttpClient) {
if (mOkHttpClient != null)
throw new IllegalArgumentException("OkHttpClient can only be initialized once");
mOkHttpClient = okHttpClient;
}

//通過Param對象同步執行一個請求
public static Response execute(@NonNull Param param) throws IOException {
return newCall(param).execute();
}

static Call newCall(Param param) throws IOException {
return newCall(getOkHttpClient(), param);
}
//所有的請求,最終都會調此方法拿到Call對象,然後執行請求
static Call newCall(OkHttpClient client, Param param) throws IOException {
param = RxHttpPlugins.onParamAssembly(param);
if (param instanceof IUploadLengthLimit) {
((IUploadLengthLimit) param).checkLength();
}
Request request = param.buildRequest(); //通過Param拿到Request對象
LogUtil.log(request);
return client.newCall(request);
}

//省略了部分方法
}
```
這裡我們重點看下`newCall(OkHttpClient, Param)`方法,該方法第一行就是為Param添加公共參數;然後判斷Param有沒有實現IUploadLengthLimit介面,有的話,檢查文件上傳大小,超出大小,則拋出IO異常;接著就是通過Param拿到Request對象;最後拿到Call對象,就可以發送一個請求。

## 4.4、Parser
先看下Parser繼承結構圖
![在這裡插入圖片描述](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d320d64d39?w=1728&h=816&f=png&s=76503)
這裡對上圖中的類做個簡單的介紹
- Parser:介面類,裡面定義了一個`T onParse(Response)`方法,輸入Response對象,輸出實體類對象T
- AbstractParser:抽象類,裡面沒有任何具體實現,主要作用是在構造方法內獲取泛型類型
- SimpleParser:是一個萬能的解析器,可以解析任意數據結構,RxHttp內置的大部分asXxx方法,內部就是通過該解析器實現的
- ListParser:是一個列表解析器,可以解析任意列表數據,內置`asList(Class<T>)`方法,就是通過該解析器實現的
- MapParser:是一個Map解析器,可以解析任意Map數據類型,內置的asMap系列方法,就是通過該解析器實現的
- BitmapParser:是一個Bitmap解析器,通過該解析器可以獲得一個Bitmap對象,asBitmap()方法內部就是通過該解析器實現的
- DownloadParser:文件下載解析器,用於文件下載,內置的一系列asDownload方法就是通過該解析器實現的

# 5、擴展
## 5.1、自定義Parser
前面第二部曲中,我們介紹了一系列asXxx方法,通過該系列方法可以很方便的指定數據返回類型,特別是自定義的`asResponse(Class<T>)`、`asResponseList(Class<T>)`、`asResponsePageList(Class<T>)`這3個方法,將`Reponse<T>`類型數據,處理的簡直不要太完美,下麵我們就來看看如何自定義Parser。

源碼永遠是最好的學習方式,在學習自定義Parser前,我們不妨先看看內置的Parser是如何實現的

**SimPleParser**
```java
public class SimpleParser<T> extends AbstractParser<T> {

//省略構造方法
@Override
public T onParse(Response response) throws IOException {
return convert(response, mType);
}
}
```
可以看到,SimpleParser除了構造方法,就剩一個onParser方法,該方法是在Parser介面中定義的,再來看看具體的實現`convert(Response, Type)`,這個方法也是在Parser介面中定義的,並且有預設的實現,如下:
```java
public interface Parser<T> {

//輸入Response 輸出T
T onParse(@NonNull Response response) throws IOException;

//對Http返回的結果,轉換成我們期望的實體類對象
default <R> R convert(Response response, Type type) throws IOException {
ResponseBody body = ExceptionHelper.throwIfFatal(response); //這裡內部會判斷code<200||code>=300 時,拋出異常
boolean onResultDecoder = isOnResultDecoder(response); //是否需要對返回的數據進行解密
LogUtil.log(response, onResultDecoder, null);
IConverter converter = getConverter(response); //取出轉換器
return converter.convert(body, type, onResultDecoder); //對數據進場轉換
}
//省略若幹方法
}
```
可以看到,非常的簡單,輸入Response對象和泛型類型Type,內部就通過IConverter介面轉換為我們期望的實體類對象並返回。

到這,我想大家應該就多少有點明白了,自定義Parser,無非就是繼承AbstractParser,然後實現onParser方法即可,那我們來驗證一下,我們來看看內置ListParser是不是這樣實現的,如下:
```java
public class ListParser<T> extends AbstractParser<List<T>> {

//省略構造方法
@Override
public List<T> onParse(Response response) throws IOException {
final Type type = ParameterizedTypeImpl.get(List.class, mType); //拿到泛型類型
return convert(response, type);
}
}
```
可以看到,跟SimpleParser解析器幾乎是一樣的實現,不同是的,這裡將我們輸入的泛型T與List組拼為一個新的泛型類型,最終返回`List<T>`對象。

現在,我們就可以來自定義Parser了,先來自定義ResponseParser,用來處理`Response<T>`數據類型,先看看數據結構:
```java
public class Response<T> {
private int code;
private String msg;
private T data;
//這裡省略get、set方法
}
```
自定義ResponseParser代碼如下:
```java
//通過@Parser註解,為解析器取別名為Response,此時就會在RxHttp類生成asResponse(Class<T>)方法
@Parser(name = "Response")
public class ResponseParser<T> extends AbstractParser<T> {

//省略構造方法
@Override
public T onParse(okhttp3.Response response) throws IOException {
final Type type = ParameterizedTypeImpl.get(Response.class, mType); //獲取泛型類型
Response<T> data = convert(response, type);
T t = data.getData(); //獲取data欄位
if (data.getCode() != 0 || t == null) {//這裡假設code不等於0,代表數據不正確,拋出異常
throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
}
return t;
}
}
```
可以看到,非常的簡單,首先將我們輸入泛型和自定義的`Response<T>`類組拼成新的泛型類型,隨後通過`convert(Response, Type)`方法得到`Response<T>`對象,接著又對code及T做了判斷,如果不正確就拋出異常,最後返回T。

估計這裡有人會問,我怎麼用這個解析器呢?相信不少小伙伴以及發現了,我們在ResponseParser類名上面用了`@Parser`註解,只要用了該註解,就會在RxHttp自動生成`asXxx(Class<T>)`方法,其中Xxx就是我們在`@Parser`註解中為解析器取的別名,這裡取別名為Response,所以便會在RxHttp類中自動生成`asResponse(Class<T>)`方法,如下:
```java
public <T> Observable<T> asResponse(Class<T> type) {
return asParser(new ResponseParser(type));
}
```
可以看到,該方法內部又調用了`asParser(Parser<T>)`方法,並傳入了ResponseParser,因此,我們有兩種方式使用自定義的ResponseParser,如下:
```java
//第一種方式,使用@parser註解生成的asResponse方法
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asResponse(Student.class) //返回Student類型
.subscribe(student -> {
//請求成功,這裡能拿到 Student對象
}, throwable -> {
//請求失敗
});

//第二種方式,直接使用asParser(Parser<T>)方法
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asParser(new ResponseParser<Student>(){}) //返回Student類型
.subscribe(student -> {
//請求成功,這裡能拿到 Student對象
}, throwable -> {
//請求失敗
});
```
以上兩種方式,除了寫法上的區別,其它都一樣,用哪種,看個人喜好,但還是建議使用第一種方式,不僅寫法簡單,也降低了耦合。


這裡最後再貼上ResponseListParser、ResponsePageListParser的源碼,原理都是一樣的,代碼實現也差不多,就不再詳解
**ResponseListParser**
```java
@Parser(name = "ResponseList")
public class ResponseListParser<T> extends AbstractParser<List<T>> {

//省略構造方法
@Override
public List<T> onParse(okhttp3.Response response) throws IOException {
final Type type = ParameterizedTypeImpl.get(Response.class, List.class, mType); //獲取泛型類型
Response<List<T>> data = convert(response, type);
List<T> list = data.getData(); //獲取data欄位
if (data.getCode() != 0 || list == null) { //code不等於0,說明數據不正確,拋出異常
throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
}
return list;
}
}
```
**ResponsePageListParser**
```java
@Parser(name = "ResponsePageList")
public class ResponsePageListParser<T> extends AbstractParser<PageList<T>> {

//省略構造方法
@Override
public PageList<T> onParse(okhttp3.Response response) throws IOException {
final Type type = ParameterizedTypeImpl.get(Response.class, PageList.class, mType); //獲取泛型類型
Response<PageList<T>> data = convert(response, type);
PageList<T> pageList = data.getData(); //獲取data欄位
if (data.getCode() != 0 || pageList == null) { //code不等於0,說明數據不正確,拋出異常
throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
}
return pageList;
}
}
```
## 5.2、自定義Param
自定義Param,想較於自定義Parser,要更加的簡單,我們只需根據自己的需求,繼承NoBodyParam、FormParam、JsonParam等,增加或者重寫方法即可,比如我們有以下3種情況,需要自定義Param,如下:

- postForm請求,需要將所有添加的參數,拼接在一起,隨後加密,最後將加密的字元串添加到請求頭中
- postJson請求,需要將所有的參數,也就是json字元串加密後再發送出去
- FormParam裡面的API不夠用,我要自定義API

#### 5.2.1、postForm請求加密
這種情況,我們需要繼承FormParam,並重寫getRequestBody()方法,如下:
```java
@Param(methodName = "postEncryptForm")
public class PostEncryptFormParam extends FormParam {

public PostEncryptFormParam(String url) {
super(url, Method.POST); //Method.POST代表post請求
}

@Override
public RequestBody getRequestBody() {
//這裡拿到你添加的所有參數
List<KeyValuePair> keyValuePairs = getKeyValuePairs();
String encryptStr = "加密後的字元串"; //根據上面拿到的參數,自行實現加密邏輯
addHeader("encryptStr", encryptStr);
return super.getRequestBody();
}
}
```
#### 5.2.2、postJson請求加密
這種情況,我們需要繼承JsonParam,也重寫getRequestBody()方法,如下:
```java
@Param(methodName = "postEncryptJson")
public class PostEncryptJsonParam extends JsonParam {

public PostEncryptFormParam(String url) {
super(url, Method.POST);
}

@Override
public RequestBody getRequestBody() {
//這裡拿到你添加的所有參數
Map<String, Object> params = getParams();
String encryptStr = "加密後的字元串"; //根據上面拿到的參數,自行實現解密邏輯
return RequestBody.create(MEDIA_TYPE_JSON, encryptStr); //發送加密後的字元串
}
}
```
#### 5.2.3、自定義API
我們繼承FormParam,並新增兩個test方法`,如下:
```java
@Param(methodName = "postTestForm")
public class PostTestFormParam extends FormParam {

public PostEncryptFormParam(String url) {
super(url, Method.POST);
}

public PostEncryptFormParam test(long a, float b) {
//這裡的業務邏輯自行實現
return this;
}

public PostEncryptFormParam test1(String s, double b) {
//這裡的業務邏輯自行實現
return this;
}
}
```
#### 5.2.4、使用自定義的Param
同樣的問題,我們怎麼用這3個自定義的Param呢?我想大多數人在類名前發現類`@Param`註解,併為Param取了別名。那這個又有什麼作用呢?
答案揭曉,只要在自定的Param上使用了`@Param`註解,並取了別名,就會在RxHttp類自動生成一個跟別名一樣的方法,在上面我們自定義了3個Param,並分別取別名為postEncryptForm、postEncryptJson、postTestForm,此時就會在RxHttp類中生成`postEncryptForm(String)`、`postEncryptJsonString)`、`postTestForm(String)`這3個方法,我們在RxHttp這個類中來看下:
```java
public static RxHttp$PostEncryptFormParam postEncryptForm(String url) {
return new RxHttp$PostEncryptFormParam(new PostEncryptFormParam(url));
}

public static RxHttp$PostEncryptJsonParam postEncryptJson(String url) {
return new RxHttp$PostEncryptJsonParam(new PostEncryptJsonParam(url));
}

public static RxHttp$PostTestFormParam postTestForm(String url) {
return new RxHttp$PostTestFormParam(new PostTestFormParam(url));
}
```

發請求時,只需要調用對應的方法就好,如:
```java
//發送加密的postForm請求
RxHttp.postEncryptForm("/service/...")
.add("key", "value") //添加參數,可調用多次
.asString() //返回String類型
.subscribe(s-> {
//請求成功
}, throwable -> {
//請求失敗
});

//發送加密的postJson請求
RxHttp.postEncryptJson("/service/...")
.add("key", "value") //添加參數,可調用多次
.asString() //返回String類型
.subscribe(s-> {
//請求成功
}, throwable -> {
//請求失敗
});
```

那我自定義的API如何調用呢,so easy!!!!,選擇對應的請求方法後,就可以直接調用,如下:
```java
//發送加密的postJson請求
RxHttp.postTestJson("/service/...")
.test(100L, 99.99F) //調用自定義的API
.test1("testKey", 88.88D) //調用自定義的API
.add("key", "value") //添加參數,可調用多次
.asString() //返回String類型
.subscribe(s-> {
//請求成功
}, throwable -> {
//請求失敗
});
```

## 5.3、自定義Converter
RxHttp內部預設使用來GsonConverter,並且額外提供了4個Converter,如下:
```java
//非必須 根據自己需求選擇Converter RxHttp預設內置了GsonConverter
implementation 'com.rxjava.rxhttp:converter-jackson:1.3.6'
implementation 'com.rxjava.rxhttp:converter-fastjson:1.3.6'
implementation 'com.rxjava.rxhttp:converter-protobuf:1.3.6'
implementation 'com.rxjava.rxhttp:converter-simplexml:1.3.6'
```
#### 5.3.1、自定義TestConverter
即使這樣,RxHttp也無法保證滿足所有的業務需求,為此,我們可以選擇自定義Converter,自定義Converter需要繼承IConverter介面,如下:
```java
public class TestConverter implements IConverter {

/**
* 請求成功後會被回調
* @param body ResponseBody
* @param type 泛型類型
* @param onResultDecoder 是否需要對結果進行解碼/解密
*/
@Override
public <T> T convert(ResponseBody body, Type type, boolean onResultDecoder) throws IOException {
//自行實現相關邏輯
return null;
}

/**
* json請求前會被回調,需要自行根據泛型T創建RequestBody對象,並返回
*/
@Override
public <T> RequestBody convert(T value) throws IOException {
//自行實現相關邏輯
return null;
}
}
```

以上兩個convert方法根據自身業務需求自行實現,可以參考RxHttp提供FastJsonConverter、SimpleXmlConverter等Converter

#### 5.3.2、怎麼用Converter
請查看本文3.10章節----設置Converter

# 6、小技巧

在這教大家一個小技巧,由於使用RxHttp發送請求都遵循請求三部曲,故我們可以在android studio 設置代碼模版,如下![在這裡插入圖片描述](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d320e10621?w=2420&h=1106&f=jpeg&s=317956)
如圖設置好後,寫代碼時,輸入rp,就會自動生成模版,如下:
![在這裡插入圖片描述](https://user-gold-cdn.xitu.io/2019/12/9/16ee64d321027db0?w=380&h=361&f=gif&s=54893)

# 7、小結
到這,RxHttp常用功能介紹完畢,你會發現,一切都是那麼的美好,無

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文主要針對中小型應用或網站,重點探討日常程式開發中SQL語句的優化問題,所謂“大數據”、“高併發”僅針對中小型應用而言,專業的資料庫運維大神請無視。以下實踐為個人在實際開發工作中,針對相對“大數據”和相對“高併發”場景的一些應對策略,部分措施並沒有經過嚴格的對比測試和原理分析,如有錯漏歡迎各種批評 ...
  • redis介紹 redis是什麼 redis是一種基於鍵值對的NOsql資料庫,與很多鍵值對資料庫不同,redis中的值 等多種數據機構和演算法組成,因為redis會將所有的數據都放在記憶體中,所以他的讀寫性能非常驚人,不僅如此,redis還可以將記憶體中的數據利用快照和日誌的形式保存在硬碟上,redis ...
  • 原文來我的公眾號:Spark性能優化指南——初級篇 一. Spark作業原理 我們使用spark-submit提交一個Spark作業之後,這個作業就會啟動一個對應的Driver進程。該進程是向集群管理器(Yarn,K8s)申請運行Spark作業需要使用的資源,這裡的資源指的就是Executor進程。 ...
  • test* [ ] ````-------> -----------------------------------~~_***nohup rman target / cmdfile=rman.sql msglog=rman.log &####select 'set newname for data... ...
  • For Example: # sqlplus / as sysdbaSQL> START sqlhc.sql T djkbyr8vkc64h ...
  • mysql中用命令行複製表結構的方法主要有一下幾種: 1.只複製表結構到新表 CREATE TABLE 新表 SELECT * FROM 舊表 WHERE 1=2;或 CREATE TABLE 新表 LIKE 舊表 ; 註意上面兩種方式,前一種方式是不會複製時的主鍵類型和自增方式是不會複製過去的,而 ...
  • <!--文章類型-->轉載oriency755 發佈於2012-12-04 11:34:45 閱讀數 6870 收藏 sp_send_dbmail [ [ @profile_name = ] 'profile_name' ] [ , [ @recipients = ] 'recipients [ ; ...
  • iOS開發過程中我們經常會遇到異常問題 對異常的處理一般採用列印或者直接拋出。這樣可以很方便我們調試過程有所參考,而且方便我們查看異常產生的位置信息 NSError(錯誤信息) 採用NSError的情況 使用 NSError 的形式可以把程式中導致錯誤原因回報給調用者,而且使程式正常運行不會造成奔潰 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...