這篇12731字的RxHttp Http請求框架,看完為何會如此銷魂,建議收藏

1、前言

RxHttp在19年4月份一經推出,就受到了廣大Android 開發者的喜愛,截止目前(20年3月)在github上已有1500+star,為此,我自己也建個RxHttp&RxLife 的群(群號:378530627)目前群裡也300+號人,裡面有不少小夥伴提了很多有價值的創意,才使得RxHttp一直堅持走到了現在,在此,感謝大家的喜愛。

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

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

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

2、簡介

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

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

2. 完美支持 Kotlin 協程

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

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

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

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

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

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

9. 支持全局加解密、添加公共參數及頭部、網絡緩存,均支持對某個請求單獨設置

gradle依賴

<code>dependencies {
   implementation 

'com.rxjava.rxhttp:rxhttp:2.1.1'

annotationProcessor

'com.rxjava.rxhttp:rxhttp-compiler:2.1.1'

implementation

'io.reactivex.rxjava2:rxandroid:2.1.1'

implementation

'com.rxjava.rxlife:rxlife-x:2.0.0'

implementation

'com.rxjava.rxhttp:converter-jackson:2.1.1'

implementation

'com.rxjava.rxhttp:converter-fastjson:2.1.1'

implementation

'com.rxjava.rxhttp:converter-protobuf:2.1.1'

implementation

'com.rxjava.rxhttp:converter-simplexml:2.1.1'

} 複製代碼/<code>

注:添加依賴後,需要rebuild一下項目,註解處理器才會生成RxHttp類; 另外kotlin用戶,請使用kapt替代annotationProcessor

以上步驟後,還未生成RxHttp類,請查看RxHttp類沒有生成,檢查步驟

緩存功能,請查看:RxHttp 全網Http緩存最優解

協程使用,請查看:RxHttp ,比Retrofit 更優雅的協程體驗

3、使用

3.1、準備工作

RxHttp 要求項目使用Java 8,請在 app 的 build.gradle 文件中添加以下代碼

<code>

compileOptions

{

sourceCompatibility

JavaVersion.VERSION_1_8

targetCompatibility

JavaVersion.VERSION_1_8

}

複製代碼

/<code>

此時,再Rebuild一下項目(通過Rebuild生成RxHttp類),就可以開始RxHttp的入坑之旅

3.2、配置默認的BaseUrl

通過@DefaultDomain註解配置默認域名,如下:

<code>

public

class

Url

{

public

static

String baseUrl =

"https://www.wanandroid.com/"

; } 複製代碼/<code>

此步驟是非必須的,這裡先介紹@DefaultDomain註解的用法,更多有關域名的介紹,請查看本文3.6章節----多域名/動態域名

3.3、請求三部曲


這篇12731字的RxHttp Http請求框架,看完為何會如此銷魂,建議收藏


代碼表示,發送一個最簡單的請求,如下

<code>

RxHttp

.get

(

"http://..."

)

.asString

()

.subscribe

(s -> { }, throwable -> { }); 複製代碼/<code>

是的,不用懷疑,就是這麼簡單,重要的事情說3遍

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

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

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

到這,你已經掌握了RxHttp的精髓,我們只需牢記請求三部曲,使用RxHttp就會得心應手。

3.3.1、第一部曲:確定請求類型

RxHttp內部共提供了14個請求方法,如下:

<code>RxHttp.get(

String

) RxHttp.head(

String

) RxHttp.postForm(

String

) RxHttp.postJson(

String

) RxHttp.postJsonArray(

String

) RxHttp.putForm(

String

) RxHttp.putJson(

String

) RxHttp.putJsonArray(

String

) RxHttp.patchForm(

String

) RxHttp.patchJson(

String

) RxHttp.patchJsonArray(

String

) RxHttp.deleteForm(

String

) RxHttp.deleteJson(

String

) RxHttp.deleteJsonArray(

String

) 複製代碼/<code>

以上14個請求方法你會發現,其實就6個類型,分別對應是Get、Head、Post、Put、Patch、Delete方法,只是其中Post、Put、Patch、Delete各有3個方法有不同形式的提交方式,只需要根據自己的需求選擇就好。

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

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

添加參數/請求頭

確定請求方法後,我們就可以調用一系列addXxx()方法添加參數/請求頭,如下:

<code>

RxHttp

.get

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.addAll

(new HashMap<>())

.addHeader

(

"deviceType"

,

"android"

) ... 複製代碼/<code>

任意請求,都可調用以上3個方法添加參數/請求頭,當然,在不同的請求方式下,也會有不同的addXxx方法供開發者調用。如下:

<code> 

RxHttp

.postJson

(

"/service/..."

)

.addAll

(new JsonObject())

.addAll

(

"{"

height\

":180,"

weight\

":70}"

) ...

RxHttp

.postForm

(

"/service/..."

)

.addFile

(

"file"

, new File(

"xxx/1.png"

))

.addFile

(

"fileList"

, new ArrayList<>()) ... 複製代碼/<code>

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

3.3.2、第二部曲:確定返回數據類型

添加好參數/請求頭後,正式進入第二部曲,確定返回數據類型,我們通過asXxx方法確定返回類型,比如,我們要返回一個Student對象,就可以通過asObject(Class)方法,如下:

<code>

RxHttp

.postForm

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.asObject

(Student.class)

.subscribe

(student -> { }, throwable -> { }); 複製代碼/<code>

如果要返回Student對象列表,則可以通過asList(Class)方法,如下:

<code>

RxHttp

.postForm

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.asList

(Student.class)

.subscribe

(students -> { }, throwable -> { }); 複製代碼/<code>

解析Response類型數據

然而,現實開發中,大多數人的接口,返回的數據結構都類似下面的這個樣子

<code>

public

class

Response

{

private

int

code;

private

String msg;

private

T data; } 複製代碼/<code>

對於這種數據結構,按傳統的寫法,每次都要對code做判斷,如果有100個請求,就要判斷100次,真的會逼死強迫症患者。

RxHttp對於這種情況,給出完美的答案,比如Response裡面的T代表一個Student對象,則可以通過asResponse(Class)方法獲取,如下:

<code>

RxHttp

.postForm

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.asResponse

(Student.class)

.subscribe

(student -> { }, throwable -> { }); 複製代碼/<code>

如果Response裡面的T代表一個List列表對象,則可以通過asResponseList(Class)方法獲取,如下

<code>

RxHttp

.postForm

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.asResponseList

(Student.class)

.subscribe

(students -> { }, throwable -> { }); 複製代碼/<code>

更多時候,我們的列表數據是分頁的,類似下面的數據結構

<code>

{

"code":

0

,

"msg":

""

,

"data":

{

"totalPage":

0

,

"list":

[]

}

}

複製代碼

/<code>

此時,調用RxHttp的asResponsePageList(Class)方法依然可以完美解決,如下:

<code>RxHttp.postForm(

"/service/..."

) .add(

"key"

,

"value"

) .asResponsePageList(Student

.

class

) //返回

PageList

<

Student

>類型 .

subscribe

(

pageList

->

{

int

totalPage = pageList.getTotalPage(); List students = pageList.getData(); }, throwable -> { }); 複製代碼/<code>

到這,估計很多人會問我:

  • 你的code在哪裡判斷的?
  • 我的code是100或者其它值才代表正確,怎麼改?
  • 我的Response類裡面的字段名,跟你的都不一樣,怎麼該?
  • 你這成功的時候直接返回Response裡面的T,那我還要拿到code做其他的判斷,執行不同業務邏輯,怎麼辦?

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

接著回答第4個問題,如何拿到code做其他的業務邏輯判斷,很簡單,我們只需用OnError接口處理錯誤回調即可,如下:

<code>RxHttp.postForm(

"/service/..."

) .add(

"key"

,

"value"

) .asResponse(Student.class) .subscribe(student -> { }, (OnError) error -> { int code = error.getErrorCode();

String

errorMsg = error.getErrorMsg() }); 複製代碼/<code>

注:上面的OnError接口並非是RxHttp內部提供的,而是自定義的,在Demo裡可以找到

以上介紹的5個asXxx方法,可以說基本涵蓋80%以上的業務場景,接下來我們看看RxHttp都提供了哪些asXxx方法,如下:


這篇12731字的RxHttp Http請求框架,看完為何會如此銷魂,建議收藏

RxHttp內部共提供了23個asXXX方法,其中:


  • 有7個是返回基本類型的包裝類型,如:asInteger、asBoolean、asLong等等;
  • 還有7個是返回對象類型,如:asString、asBitmap、asList、asMap(3個)以及最常用asObject方法;
  • 剩下9個是asParser(Parser)、 asUpload系列方法及asDownload系列方法。

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

3.3.3、第三部曲:訂閱回調

這一步就很簡單了,在第二部曲中,asXxx方法會返回Observable對象,沒錯,就是RxJava內部的Observable對象,此時我們便可通過subscribe系列方法訂閱回調,如下:

<code> 

RxHttp

.postForm

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.asResponseList

(Student.class)

.subscribe

();

RxHttp

.postForm

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.asResponseList

(Student.class)

.subscribe

(students -> { });

RxHttp

.postForm

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.asResponseList

(Student.class)

.subscribe

(students -> { }, throwable -> { }); 複製代碼/<code>

另外,我們還可以訂閱請求開始/結束的回調,如下:

<code>

RxHttp

.get

(

"/service/..."

)

.asString

()

.observeOn

(AndroidSchedulers.mainThread())

.doOnSubscribe

(disposable -> { })

.doFinally

(() -> { })

.as

(RxLife.as(this))

.subscribe

(s -> { }, (OnError) error -> { }); 複製代碼/<code>

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

3.4、初始化

<code> 

RxHttp

.setDebug

(boolean debug)

RxHttp

.init

(OkHttpClient okHttpClient)

RxHttp

.init

(OkHttpClient okHttpClient, boolean debug) 複製代碼/<code>

此步驟是非必須的,如需要添加攔截器等其他業務需求,則可調用init方法進行初始化,不初始化或者傳入null即代表使用默認OkHttpClient對象,建議在Application中初始化,默認的OkHttpClient對象在HttpSender類中可以找到,如下:

<code>

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

) .build(); } 複製代碼/<code>

雖然初始化是非必須的,但是建議大家傳入自定義的OkHttpClient對象,一來,自定義的OkHttpClient能最大化滿足自身的業務;二來,隨著RxHttp版本的升級,默認的OkHttpClient可能會發生變化(雖然可能性很小),故建議自定義OkHttpClient對象傳入RxHttp。

3.5、公共參數/請求頭

RxHttp支持為所有的請求添加公共參數/請求頭,當然,如果你希望某個請求不添加公共參數/請求頭,也是支持的,而且非常簡單。如下:

<code>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"

); } }); 複製代碼/<code>

我們需要調用RxHttp.setOnParamAssembly(Function)方法,並傳入一個Function接口對象,每次發起請求,都會回調該接口。

當然,如果希望某個請求不回調該接口,即不添加公共參數/請求頭,則可以調用setAssemblyEnabled(boolean)方法,並傳入false即可,如下:

<code>

RxHttp

.get

(

"/service/..."

)

.setAssemblyEnabled

(false)

.asString

()

.subscribe

(s -> { }, throwable -> { }); 複製代碼/<code>

3.6、多域名/動態域名

3.6.1、多域名

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

<code>

public

class

Url

{

@DefaultDomain

() public static String baseUrl =

"https://www.wanandroid.com/"

@Domain

(name =

"BaseUrlBaidu"

) public static String baidu =

"https://www.baidu.com/"

;

@Domain

(name =

"BaseUrlGoogle"

) public static String google =

"https://www.google.com/"

; } 複製代碼/<code>

通過@Domain()註解標註非默認域名,就會在RxHttp類中生成setDomainToXxxIfAbsent()方法,其中Xxx就是註解中取的別名。

上面我們使用了兩個@Domain()註解,此時(需要Rebuild一下項目)就會在RxHttp類中生成setDomainToBaseUrlBaiduIfAbsent()、setDomainToBaseUrlGoogleIfAbsent()這兩方法,此時發請求,我們就可以使用指定的域名,如下:

<code> 
 

RxHttp

.get

(

"/service/..."

)

.asString

()

.subscribe

();

RxHttp

.get

(

"https://www.mi.com/service/..."

)

.asString

()

.subscribe

();

RxHttp

.get

(

"https://www.mi.com/service/..."

)

.setDomainToBaseUrlBaiduIfAbsent

()

.asString

()

.subscribe

();

RxHttp

.get

(

"/service/..."

)

.setDomainToBaseUrlGoogleIfAbsent

()

.asString

()

.subscribe

(); 複製代碼/<code>

通過以上案例,可以知道,RxHttp共有3種指定域名的方式,按優先級排名分別是:手動輸入域名 > 指定非默認域名 > 使用默認域名。

3.6.2、動態域名

現實開發中,也會有動態域名切換的需求,如域名被封、或者需要根據服務端下發的域名去配置,這對於RxHttp來說簡直就是 so easy !!! 我們只需要對BaseUrl重新賦值,此時發請求便會立即生效,如下:

<code> 
RxHttp.

get

(

"/service/..."

) .asString() .subscribe(); Url.baseUrl =

"https://www.qq.com"

; RxHttp.

get

(

"/service/..."

) .asString() .subscribe(); 複製代碼/<code>

3.7、關閉請求

我們知道,在Activity/Fragment中發起請求,如果頁面銷燬時,請求還未結束,就會有內存洩漏的危險,因此,我們需要在頁面銷燬時,關閉一些還未完成的請求,RxHttp提供了兩種關閉請求的方式,分別是自動+手動。

3.7.1、自動關閉請求

自動關閉請求,需要引入本人開源的另一個庫RxLife,先來看看如何用:

<code> 

RxHttp

.postForm

(

"/service/..."

)

.asString

()

.as

(RxLife.as(this))

.subscribe

();

RxHttp

.postForm

(

"/service/..."

)

.asString

()

.as

(RxLife.asOnMain(this))

.subscribe

();

RxHttp

.postForm

(

"/service/..."

)

.asString

()

.life

(this)

.subscribe

();

RxHttp

.postForm

(

"/service/..."

)

.asString

()

.lifeOnMain

(this)

.subscribe

(); 複製代碼/<code>

上面的this為LifecycleOwner接口對象,我們的FragmentActivity/Fragment均實現了這個接口,所有我們在FragmentActivity/Fragment中可以直接傳this。 對RxLife不瞭解的同學請查看RxLife 史上最優雅的管理RxJava生命週期,這裡不詳細講解。

3.7.2、手動關閉請求

手動關閉請求,我們只需要在訂閱回調的時候拿到Disposable對象,通過該對象可以判斷請求是否結束,如果沒有,就可以關閉請求,如下:

<code> 
Disposable disposable = RxHttp.get(

"/service/..."

) .asString() .subscribe(s -> { }, throwable -> { });

if

(!disposable.isDisposed()) { disposable.dispose(); } 複製代碼/<code>

3.8、文件上傳/下載/進度監聽

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

3.8.1上傳

通過addFile系列方法添加文件,如下:

<code>

RxHttp

.postForm

(

"/service/..."

)

.addFile

(

"file1"

, new File(

"xxx/1.png"

))

.addFile

(

"fileList"

, new ArrayList<>())

.asString

()

.subscribe

(s -> { }, throwable -> { }); 複製代碼/<code>

通過upload系列方法監聽上傳進度,如下:

<code>RxHttp.postForm(

"/service/..."

) .addFile(

"file1"

,

new

File(

"xxx/1.png"

)) .addFile(

"file2"

,

new

File(

"xxx/2.png"

)) .upload(progress -> {

int

currentProgress = progress.getProgress();

long

currentSize = progress.getCurrentSize();

long

totalSize = progress.getTotalSize(); }, AndroidSchedulers.mainThread()) .asString() .subscribe(s -> { }, throwable -> { }); 複製代碼/<code>

可以看到,跟上傳的代碼相比,我們僅僅是使用了asUpload(Consumer, Scheduler)方法替換asString()方法,第一個參數是進度監聽接口,每當進度有更新時,都會回調該接口,第二個參數是指定回調的線程,這裡我們指定了在UI線程中回調。

3.8.2、下載

下載使用asDownload(String)方法,傳入本地路徑即可

<code>   

String

destPath = getExternalCacheDir() +

"/"

+ System.currentTimeMillis() +

".apk"

; RxHttp.get(

"http://update.9158.com/miaolive/Miaolive.apk"

) .asDownload(destPath) .subscribe(s -> { }, throwable -> { }); 複製代碼/<code>

3.8.3、帶進度下載

帶進度下載使用asDownload(String,Consumer,Scheduler)方法

<code>   
String destPath = getExternalCacheDir() + 

"/"

+ System.currentTimeMillis() +

".apk"

; RxHttp.

get

(

"http://update.9158.com/miaolive/Miaolive.apk"

) .asDownload(destPath, progress -> {

int

currentProgress = progress.getProgress();

long

currentSize = progress.getCurrentSize();

long

totalSize = progress.getTotalSize(); }, AndroidSchedulers.mainThread()) .subscribe(s -> { }, throwable -> { }); 複製代碼/<code>

3.8.4、斷點下載

斷點下載相較於下載,僅需要調用setRangeHeader(long startIndex, long endIndex)方法傳入開始及結束位置即可(結束位置不傳默認為文件末尾),其它沒有任何差別

<code>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 -> { }, throwable -> { }); 複製代碼/<code>

3.8.5、帶進度斷點下載

帶進度斷點下載相較於帶進度下載僅需要調用setRangeHeader方法傳入開始及結束位置即可(結束位置不傳默認為文件末尾),其它沒有任何差別

<code>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 -> {

int

currentProgress = progress.getProgress();

long

currentSize = progress.getCurrentSize();

long

totalSize = progress.getTotalSize(); }, AndroidSchedulers.mainThread()) .subscribe(s -> { }, throwable -> { }); 複製代碼/<code>

注:上面帶進度斷點下載中,返回的進度會從0開始,如果需要銜接上次下載的進度,則調用``setRangeHeader(long startIndex, long endIndex, boolean connectLastProgress)`方法第三個參數傳入true即可,默認為false,如下:

<code>String destPath = getExternalCacheDir() + 

"/"

+

"Miaobo.apk"

;

long

length =

new

File(destPath).length(); RxHttp.

get

(

"http://update.9158.com/miaolive/Miaolive.apk"

) .setRangeHeader(length,

true

) .asDownload(destPath, progress -> {

int

currentProgress = progress.getProgress();

long

currentSize = progress.getCurrentSize();

long

totalSize = progress.getTotalSize(); }, AndroidSchedulers.mainThread()) .subscribe(s -> { }, throwable -> { }); 複製代碼/<code>

3.9、超時設置

3.9.1、設置全局超時

RxHttp內部默認的讀、寫、連接超時時間均為10s,如需修改,請自定義OkHttpClient對象,如下:

<code> 
OkHttpClient client = 

new

OkHttpClient.Builder() .connectTimeout(

15

, TimeUnit.SECONDS) .readTimeout(

15

, TimeUnit.SECONDS) .writeTimeout(

15

, TimeUnit.SECONDS) .build(); RxHttp.init(client); 複製代碼/<code>

3.9.2、為單個請求設置超時

為單個請求設置超時,使用的是RxJava的timeout(long timeout, TimeUnit timeUnit)方法,如下:

<code>

RxHttp

.get

(

"/service/..."

)

.asString

()

.timeout

(

5

, TimeUnit.SECONDS)

.as

(RxLife.asOnMain(this))

.subscribe

(s -> { }, (OnError) error -> { }); 複製代碼/<code>

注:這裡設置的總超時時間要小於全局讀、寫、連接超時時間之和,否則無效

3.10、設置Converter

3.10.1、設置全局Converter

<code>IConverter converter = FastJsonConverter.

create

(); RxHttp.setConverter(converter) 複製代碼/<code>

3.10.2、為請求設置單獨的Converter

首先需要在任意public類中通過@Converter註解聲明Converter,如下:

<code>

public

class

RxHttpManager

{ (name =

"XmlConverter"

)

public

static

IConverter xmlConverter = XmlConverter.create(); } 複製代碼/<code>

然後,rebuild 一下項目,就在自動在RxHttp類中生成setXmlConverter()方法,隨後就可以調用此方法為單個請求指定Converter,如下:

<code>

RxHttp

.get

(

"/service/..."

)

.setXmlConverter

()

.asObject

(NewsDataXml.class)

.as

(RxLife.asOnMain(this))

.subscribe

(dataXml -> { }, (OnError) error -> { }); 複製代碼/<code>

3.11、請求加解密

3.11.1、加密

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

3.11.2、解密

有些時候,請求會返回一大串的密文,此時就需要將密文轉化為明文,直接來看代碼,如下:

<code> 
RxHttp.setResultDecoder(

new

Function

<

String

,

String

>() {

public

String

apply(

String

s) throws Exception {

String

plaintext = decode(s);

return

plaintext; } }); 複製代碼/<code>

很簡單,通過RxHttp.setResultDecoder(Function)靜態方法,傳入一個接口對象,此接口會在每次請求成功的時候被回調,並傳入請求返回的密文,只需要將密文解密後返回即可。

然而,有些請求是不需求解密的,此時就可以調用setDecoderEnabled(boolean)方法,並傳入false即可,如下:

<code>

RxHttp

.get

(

"/service/..."

)

.setDecoderEnabled

(false)

.asString

()

.subscribe

(s -> { }, (OnError) error -> { }); 複製代碼/<code>

3.12、指定請求/回調線程

RxHttp默認在Io線程執行請求,也默認在Io線程回調,即默認在同一Io線程執行請求並回調,當然,我們也可以指定請求/回調所在線程。

3.12.1、指定請求所在線程

我們可以調用一些列subscribeXxx方法指定請求所在線程,如下:

<code> 

RxHttp

.get

(

"/service/..."

)

.subscribeOnCurrent

()

.asString

()

.subscribe

();

subscribeOnIo

()

subscribeOnSingle

()

subscribeOnNewThread

()

subscribeOnComputation

()

subscribeOnTrampoline

()

subscribeOn

(Scheduler) 複製代碼/<code>

以上使用的皆是RxJava的線程調度器,不熟悉的請自行查閱相關資料,這裡不做詳細介紹。

3.12.2、指定回調所在線程

指定回調所在線程,依然使用RxJava的線程調度器,如下:

<code> 

RxHttp

.get

(

"/service/..."

)

.asString

()

.observeOn

(AndroidSchedulers.mainThread())

.subscribe

(s -> { }, throwable -> { }); 複製代碼/<code>

3.13、 Retrofit用戶

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

<code>

public

class

HttpWrapper

{

public

static Observable> getStudent(int page) {

return

RxHttp.

get

(

"/service/..."

) .add(

"page"

, page) .asList(Student

.

class

);

} } HttpWrapper.getStudent(

1

) .

as

(RxLife.asOnMain(

this

)) .subscribe(students -> { }, throwable -> { }); 複製代碼/<code>

很簡單,封裝的時候返回Observable對象即可。

還有的同學問,我們獲取列表的接口,頁碼是和url拼接在一起的,Retrofit可以通過佔位符,那RxHttp又如何實現?簡單,如下:

<code>

public

class

HttpWrapper

{

public

static

Observable

getStudent

(

int

page)

{

return

RxHttp.get(

"/service/%d/..."

, page) .asObject(Student

.

class

)

; }

public

static

Observable

getStudent

(

int

page,

int

count)

{

return

RxHttp.get(

"/service/%1$d/%2$d/..."

, page, count) .asObject(Student

.

class

)

; } } 複製代碼/<code>

這一點跟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的大致工作流程


這篇12731字的RxHttp Http請求框架,看完為何會如此銷魂,建議收藏


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

4.2、Param

首先,附上一張Param類的繼承關係圖

下面將從上往下對上圖中的類做個簡單的介紹:


這篇12731字的RxHttp Http請求框架,看完為何會如此銷魂,建議收藏

  • 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對象和一系列靜態方法,我們來簡單看下:

<code>

public

final

class

HttpSender

{

private

static

OkHttpClient mOkHttpClient;

public

static

void

init

(OkHttpClient okHttpClient)

{

if

(mOkHttpClient !=

null

)

throw

new

IllegalArgumentException(

"OkHttpClient can only be initialized once"

); mOkHttpClient = okHttpClient; }

public

static

Response

execute

(@NonNull Param param)

throws

IOException

{

return

newCall(param).execute(); }

static

Call

newCall

(Param param)

throws

IOException

{

return

newCall(getOkHttpClient(), param); }

static

Call

newCall

(OkHttpClient client, Param param)

throws

IOException

{ param = RxHttpPlugins.onParamAssembly(param);

if

(param

instanceof

IUploadLengthLimit) { ((IUploadLengthLimit) param).checkLength(); } Request request = param.buildRequest(); LogUtil.log(request);

return

client.newCall(request); } } 複製代碼/<code>

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

4.4、Parser

先看下Parser繼承結構圖


這篇12731字的RxHttp Http請求框架,看完為何會如此銷魂,建議收藏


這裡對上圖中的類做個簡單的介紹


  • Parser:接口類,裡面定義了一個T onParse(Response)方法,輸入Response對象,輸出實體類對象T
  • AbstractParser:抽象類,裡面沒有任何具體實現,主要作用是在構造方法內獲取泛型類型
  • SimpleParser:是一個萬能的解析器,可以解析任意數據結構,RxHttp內置的大部分asXxx方法,內部就是通過該解析器實現的
  • ListParser:是一個列表解析器,可以解析任意列表數據,內置asList(Class)方法,就是通過該解析器實現的
  • MapParser:是一個Map解析器,可以解析任意Map數據類型,內置的asMap系列方法,就是通過該解析器實現的
  • BitmapParser:是一個Bitmap解析器,通過該解析器可以獲得一個Bitmap對象,asBitmap()方法內部就是通過該解析器實現的
  • DownloadParser:文件下載解析器,用於文件下載,內置的一系列asDownload方法就是通過該解析器實現的

5、擴展

5.1、自定義Parser

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

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

SimPleParser

<code>

public

class

SimpleParser

<

T

>

extends

AbstractParser

<

T

>

{

public

T

onParse

(Response response)

throws

IOException

{

return

convert(response, mType); } } 複製代碼/<code>

可以看到,SimpleParser除了構造方法,就剩一個onParser方法,該方法是在Parser接口中定義的,再來看看具體的實現convert(Response, Type),這個方法也是在Parser接口中定義的,並且有默認的實現,如下:

<code>

public

interface

Parser

<

T

>

{

T

onParse

(@NonNull Response response)

throws

IOException

;

default

R

convert

(Response response, Type type)

throws

IOException

{ ResponseBody body = ExceptionHelper.throwIfFatal(response);

boolean

onResultDecoder = isOnResultDecoder(response); LogUtil.log(response, onResultDecoder,

null

); IConverter converter = getConverter(response);

return

converter.convert(body, type, onResultDecoder); } } 複製代碼/<code>

可以看到,非常的簡單,輸入Response對象和泛型類型Type,內部就通過IConverter接口轉換為我們期望的實體類對象並返回。

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

<code>

public

class

ListParser

<

T

>

extends

AbstractParser

<

List

<

T

>>

{

public

List

onParse

(Response response)

throws

IOException

{

final

Type type = ParameterizedTypeImpl.get(List

.

class

,

mType

)

;

return

convert(response, type); } } 複製代碼/<code>

可以看到,跟SimpleParser解析器幾乎是一樣的實現,不同是的,這裡將我們輸入的泛型T與List組拼為一個新的泛型類型,最終返回List對象。

現在,我們就可以來自定義Parser了,先來自定義ResponseParser,用來處理Response數據類型,先看看數據結構:

<code>

public

class

Response

{

private

int

code;

private

String msg;

private

T data; } 複製代碼/<code>

自定義ResponseParser代碼如下:

<code> 

public

class

ResponseParser

<

T

>

extends

AbstractParser

<

T

>

{

protected

ResponseParser() {

super

(); }

public

ResponseParser(Type type) {

super

(type); }

public

T onParse(okhttp3.Response response) throws IOException {

final

Type type = ParameterizedTypeImpl.

get

(Response

.

class

,

mType); //獲取泛型類型

Response

data

= convert(response, type); T t =

data

.getData();

if

(

data

.getCode() !=

200

|| t ==

null

) {

throw

new ParseException(String.valueOf(

data

.getCode()),

data

.getMsg(), response); }

return

t; } } 複製代碼/<code>

以上代碼,需要注意兩個地方

第一,我們在類開頭使用了@Parser註解,該註解有兩個參數,如下:

  • name 代表解析器的名稱,這裡取名為Response,於是在RxHttp類就會有asResponse(Class)方法(命名方式為:as + name屬性的值);
  • wrappers 可以把他理解為泛型T的包裝類,需要傳入Class[]數組對象,這裡我們傳入了{List.class, PageList.class}這兩個類,於是就會生成asResponseList(Class)及asResponsePageList(Class)方法。(命名方式為:as +name屬性的值+Class類名)

注:PageList類需要自己定義,用於加載分頁數據,Demo裡有這個類

第二,我們在if語句裡,對code做了判斷,非200或者data為空時,就拋出異常,並帶上了code及msg字段,所以我們在異常回調的地方就能拿到這兩個字段

此時,我們就可以通過這3個方法,直接拿到T、List、PageList類型數據,並且對code做了統一的判斷,直接來看看如何使用這個解析器

<code> 

RxHttp

.postForm

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.asResponse

(Student.class)

.subscribe

(student -> { }, throwable -> { });

RxHttp

.postForm

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.asParser

(new ResponseParser(){})

.subscribe

(student -> { }, throwable -> { }); 複製代碼/<code>

以上兩種方式,除了寫法上的區別,其它都一樣,相信大家都會選擇第一種方式,不僅寫法簡單,還降低了耦合。

5.2、自定義Param

自定義Param,想較於自定義Parser,要更加的簡單,我們只需根據自己的需求,繼承NoBodyParam、FormParam、JsonParam等,增加或者重寫方法即可,比如我們有以下3種情況,需要自定義Param,如下:

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

5.2.1、postForm請求加密

這種情況,我們需要繼承FormParam,並重寫getRequestBody()方法,如下:

<code> (methodName = 

"postEncryptForm"

)

public

class

PostEncryptFormParam

extends

FormParam

{

public

PostEncryptFormParam

(String url)

{

super

(url, Method.POST); }

public

RequestBody

getRequestBody

()

{ List keyValuePairs = getKeyValuePairs(); String encryptStr =

"加密後的字符串"

; addHeader(

"encryptStr"

, encryptStr);

return

super

.getRequestBody(); } } 複製代碼/<code>

5.2.2、postJson請求加密

這種情況,我們需要繼承JsonParam,也重寫getRequestBody()方法,如下:

<code> (methodName = 

"postEncryptJson"

)

public

class

PostEncryptJsonParam

extends

JsonParam

{

public

PostEncryptJsonParam

(String url)

{

super

(url, Method.POST); }

public

RequestBody

getRequestBody

()

{ Map params = getParams(); String encryptStr =

"加密後的字符串"

;

return

RequestBody.create(MEDIA_TYPE_JSON, encryptStr); } } 複製代碼/<code>

5.2.3、自定義API

我們繼承FormParam,並新增兩個test方法`,如下:

<code> (methodName = 

"postTestForm"

)

public

class

PostTestFormParam

extends

FormParam

{

public

PostTestFormParam

(String url)

{

super

(url, Method.POST); }

public

PostTestFormParam

test

(

long

a,

float

b)

{

return

this

; }

public

PostTestFormParam

test1

(String s,

double

b)

{

return

this

; } } 複製代碼/<code>

5.2.4、使用自定義的Param

同樣的問題,我們怎麼用這3個自定義的Param呢?我想大多數人在類名前發現類@Param註解,併為Param取了別名。那這個又有什麼作用呢? 答案揭曉,只要在自定的Param上使用了@Param註解,並取了別名,就會在RxHttp類自動生成一個跟別名一樣的方法,在上面我們自定義了3個Param,並分別取別名為postEncryptForm、postEncryptJson、postTestForm,此時就會在RxHttp類中生成postEncryptForm(String)、postEncryptJson(String)、postTestForm(String)這3個方法,我們在RxHttp這個類中來看下:

<code>  

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)); } 複製代碼/<code>

發請求時,只需要調用對應的方法就好,如:

<code> 

RxHttp

.postEncryptForm

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.asString

()

.subscribe

(s-> { }, throwable -> { });

RxHttp

.postEncryptJson

(

"/service/..."

)

.add

(

"key"

,

"value"

)

.asString

()

.subscribe

(s-> { }, throwable -> { }); 複製代碼/<code>

那我自定義的API如何調用呢,so easy!!!!,選擇對應的請求方法後,就可以直接調用,如下:

<code> 

RxHttp

.postTestJson

(

"/service/..."

)

.test

(

100

L,

99.99

F)

.test1

(

"testKey"

,

88.88

D)

.add

(

"key"

,

"value"

)

.asString

()

.subscribe

(s-> { }, throwable -> { }); 複製代碼/<code>

5.3、自定義Converter

RxHttp內部默認使用來GsonConverter,並且額外提供了4個Converter,如下:

<code> 
implementation 

'com.rxjava.rxhttp:converter-jackson:1.4.2'

implementation

'com.rxjava.rxhttp:converter-fastjson:1.4.2'

implementation

'com.rxjava.rxhttp:converter-protobuf:1.4.2'

implementation

'com.rxjava.rxhttp:converter-simplexml:1.4.2'

複製代碼/<code>

5.3.1、自定義TestConverter

即使這樣,RxHttp也無法保證滿足所有的業務需求,為此,我們可以選擇自定義Converter,自定義Converter需要繼承IConverter接口,如下:

<code>

public

class

TestConverter

implements

IConverter

{

public

T

convert

(ResponseBody body, Type type,

boolean

onResultDecoder)

throws

IOException

{

return

null

; }

public

RequestBody

convert

(T value)

throws

IOException

{

return

null

; } } 複製代碼/<code>

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

5.3.2、怎麼用Converter

請查看本文3.10章節----設置Converter

6、小技巧

在這教大家一個小技巧,由於使用RxHttp發送請求都遵循請求三部曲,故我們可以在android studio 設置代碼模版,如下

如圖設置好後,寫代碼時,輸入rp,就會自動生成模版,如下:

這篇12731字的RxHttp Http請求框架,看完為何會如此銷魂,建議收藏


7、小結

到這,RxHttp常用功能介紹完畢,你會發現,一切都是那麼的美好,無論你是get、post、加密請求、自定義解析器,還是文件上傳/下載/進度監聽等等,皆遵循請求三部曲。特別是對Response類型數據處理,可以說是天衣無縫,我們無需每次都判斷code,直接就可以拿到T,簡直了。。。

文章不易,如果大家喜歡這篇文章,或者對你有幫助希望大家多多,點贊,轉發,關注 哦。文章會持續更新的。絕對乾貨!!!



分享到:


相關文章: