您好,登錄后才能下訂單哦!
RxHttp在今年4月份一經推出,就受到了廣大Android 開發者的喜愛,截止本文發表在github上已有1100+star,為此,我自己也建個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多個版本,目前用的人也不在少數,可以說很穩定了。
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依賴
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緩存最優解
RxHttp 要求項目使用Java 8,請在 app 的 build.gradle 文件中添加以下代碼
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
此時,再Rebuild一下項目(通過Rebuild生成RxHttp類),就可以開始RxHttp的入坑之旅
通過@DefaultDomain
注解配置默認域名,如下:
public class Url {
@DefaultDomain //設置為默認域名
public static String baseUrl = "https://www.wanandroid.com/";
}
此步驟是非必須的,這里先介紹@DefaultDomain
注解的用法,更多有關域名的介紹,請查看本文3.6章節----多域名/動態域名
先來看看如何發送一個最簡單的請求,如下
RxHttp.get("http://...") //第一步, 通過get、postXxx、putXxx等方法,確定請求類型
.asString() //第二步, 通過asXxx系列方法,確定返回數據類型
.subscribe(s -> { //第三步, 訂閱回調(此步驟同RxJava訂閱觀察者)
//請求成功
}, throwable -> {
//請求失敗
});
是的,不用懷疑,就是這么簡單,重要的事情說3遍
任意請求,任意返回數據類型,皆遵循請求三部曲
任意請求,任意返回數據類型,皆遵循請求三部曲
任意請求,任意返回數據類型,皆遵循請求三部曲
到這,你已經掌握了RxHttp的精髓,我們只需牢記請求三部曲,使用RxHttp就會得心應手。
RxHttp內部共提供了14個請求方法,如下:
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()
方法添加參數/請求頭,如下:
RxHttp.get("/service/...") //發送get請求
.add("key", "value") //添加參數
.addAll(new HashMap<>()) //通過Map添加多個參數
.addHeader("deviceType", "android") //添加請求頭
...
任意請求,都可調用以上3個方法添加參數/請求頭,當然,在不同的請求方式下,也會有不同的addXxx方法供開發者調用。如下:
//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方法,更多方法請下載源碼體驗。
添加好參數/請求頭后,正式進入第二部曲,確定返回數據類型,我們通過asXxx
方法確定返回類型,比如,我們要返回一個Student對象,就可以通過asObject(Class<T>)
方法,如下:
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asObject(Student.class) //返回Student類型
.subscribe(student -> {
//請求成功,這里就能拿到 Student對象
}, throwable -> {
//請求失敗
});
如果要返回Student對象列表,則可以通過asList(Class<T>)
方法,如下:
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asList(Student.class) //返回List<Student>類型
.subscribe(students -> {
//請求成功,這里就能拿到 Student對象列表
}, throwable -> {
//請求失敗
});
解析Response<T>
類型數據
然而,現實開發中,大多數人的接口,返回的數據結構都類似下面的這個樣子
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>)
方法獲取,如下:
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asResponse(Student.class) //返回Student類型
.subscribe(student -> {
//請求成功,這里能拿到 Student對象
}, throwable -> {
//請求失敗
});
如果Response<T>
里面的T代表一個List<Student>
列表對象,則可以通過asResponseList(Class<T>)
方法獲取,如下
RxHttp.postForm("/service/...") //發送post表單請求
.add("key", "value") //添加參數,可調用多次
.asResponseList(Student.class) //返回List<Student>類型
.subscribe(students -> {
//請求成功,這里能拿到List<Student>列表對象
}, throwable -> {
//請求失敗
});
更多時候,我們的列表數據是分頁的,類似下面的數據結構
{
"code": 0,
"msg": "",
"data": {
"totalPage": 0,
"list": []
}
}
此時,調用RxHttp的asResponsePageList(Class<T>)
方法依然可以完美解決,如下:
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 -> {
//請求失敗
});
到這,估計很多人會問我:
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
接口處理錯誤回調即可,如下:
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方法,如下:cdn.xitu.io/2019/12/9/16ee64d31d028d95?w=1348&h=832&f=png&s=238055">
RxHttp內部共提供了23
個asXXX
方法,其中:
asObject
方法;asParser(Parser<T>)
、 asUpload
系列方法及asDownload
系列方法。duang、duang、duang !!! 劃重點,這里我可以告訴大家,其實前面的14個方法,最終都是通過asParser(Parser<T>)
方法實現的,具體實現過程,這里先跳過,后續會詳細講解。
這一步就很簡單了,在第二部曲中,asXxx方法會返回Observable<T>
對象,沒錯,就是RxJava內部的Observable<T>
對象,此時我們便可通過subscribe
系列方法訂閱回調,如下:
//不處理任何回調
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 -> {
//請求失敗
});
//等等,省略
另外,我們還可以訂閱請求開始/結束的回調,如下:
RxHttp.get("/service/...")
.asString()
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> {
//請求開始,當前在主線程回調
})
.doFinally(() -> {
//請求結束,當前在主線程回調
})
.as(RxLife.as(this)) //感知生命周期
.subscribe(pageList -> {
//成功回調,當前在主線程回調
}, (OnError) error -> {
//失敗回調,當前在主線程回調
});
到這,請求三部曲介紹完畢,接著,將介紹其它常用的功能
//設置debug模式,默認為false,設置為true后,發請求,過濾"RxHttp"能看到請求日志
RxHttp.setDebug(boolean debug)
//非必須,只能初始化一次,第二次將拋出異常
RxHttp.init(OkHttpClient okHttpClient)
//或者,調試模式下會有日志輸出
RxHttp.init(OkHttpClient okHttpClient, boolean debug)
此步驟是非必須的,如需要添加攔截器等其他業務需求,則可調用init
方法進行初始化,不初始化或者傳入null
即代表使用默認OkHttpClient對象,建議在Application中初始化,默認的OkHttpClient對象在HttpSender類中可以找到,如下:
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。
RxHttp支持為所有的請求添加公共參數/請求頭,當然,如果你希望某個請求不添加公共參數/請求頭,也是支持的,而且非常簡單。如下:
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即可,如下:
RxHttp.get("/service/...") //get請求
.setAssemblyEnabled(false) //設置是否添加公共參數/頭部,默認為true
.asString() //返回字符串數據
.subscribe(s -> { //這里的s為String類型
//請求成功
}, throwable -> {
//請求失敗
});
3.6.1、多域名
現實開發中,我們經常會遇到多個域名的情況,其中1個為默認域名,其它為非默認域名,對于這種情況,RxHttp提供了@DefaultDomain()
、@Domain()
這兩個注解來標明默認域名和非默認域名,如下:
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()
這兩方法,此時發請求,我們就可以使用指定的域名,如下:
//使用默認域名,則無需添加任何額外代碼
//此時 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重新賦值,此時發請求便會立即生效,如下:
//此時 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();
我們知道,在Activity/Fragment中發起請求,如果頁面銷毀時,請求還未結束,就會有內存泄漏的危險,因此,我們需要在頁面銷毀時,關閉一些還未完成的請求,RxHttp提供了兩種關閉請求的方式,分別是自動+手動。
3.7.1、自動關閉請求
自動關閉請求,需要引入本人開源的另一個庫RxLife,先來看看如何用:
//以下代碼均在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生命周期,這里不詳細講解。
3.7.2、手動關閉請求
手動關閉請求,我們只需要在訂閱回調的時候拿到Disposable對象,通過該對象可以判斷請求是否結束,如果沒有,就可以關閉請求,如下:
//訂閱回調,可以拿到Disposable對象
Disposable disposable = RxHttp.get("/service/...")
.asString()
.subscribe(s -> {
//成功回調
}, throwable -> {
//失敗回調
});
if (!disposable.isDisposed()) { //判斷請求有沒有結束
disposable.dispose(); //沒有結束,則關閉請求
}
RxHttp可以非常優雅的實現上傳/下載及進度的監聽,是騾子是馬,拉出來溜溜
3.8.1上傳
通過addFile系列方法添加文件,如下:
RxHttp.postForm("/service/...") //發送Form表單形式的Post請求
.addFile("file1", new File("xxx/1.png")) //添加單個文件
.addFile("fileList", new ArrayList<>()) //通過List對象,添加多個文件
.asString()
.subscribe(s -> {
//上傳成功
}, throwable -> {
//上傳失敗
});
通過asUpload系列方法監聽上傳進度,如下:
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)
方法,傳入本地路徑即可
//文件存儲路徑
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)
方法
//文件存儲路徑
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)
方法傳入開始及結束位置即可(結束位置不傳默認為文件末尾),其它沒有任何差別
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
方法傳入開始及結束位置即可(結束位置不傳默認為文件末尾),其它沒有任何差別
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)
方法傳入上次已經下載好的長度(第二個參數),如下:
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.1、設置全局超時
RxHttp內部默認的讀、寫、連接超時時間均為10s,如需修改,請自定義OkHttpClient對象,如下:
//設置讀、寫、連接超時時間為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)
方法,如下:
RxHttp.get("/service/...")
.asString()
.timeout(5, TimeUnit.SECONDS)//設置總超時時間為5s
.as(RxLife.asOnMain(this)) //感知生命周期,并在主線程回調
.subscribe(pageList -> {
//成功回調
}, (OnError) error -> {
//失敗回調
});
注:這里設置的總超時時間要小于全局讀、寫、連接超時時間之和,否則無效
3.10.1、設置全局Converter
IConverter converter = FastJsonConverter.create();
RxHttp.setConverter(converter)
3.10.2、為請求設置單獨的Converter
首先需要在任意public類中通過@Converter注解聲明Converter,如下:
public class RxHttpManager {
@Converter(name = "XmlConverter") //指定Converter名稱
public static IConverter xmlConverter = XmlConverter.create();
}
然后,rebuild 一下項目,就在自動在RxHttp類中生成setXmlConverter()
方法,隨后就可以調用此方法為單個請求指定Converter,如下:
RxHttp.get("/service/...")
.setXmlConverter() //指定使用XmlConverter,不指定,則使用全局的Converter
.asObject(NewsDataXml.class)
.as(RxLife.asOnMain(this)) //感知生命周期,并在主線程回調
.subscribe(dataXml -> {
//成功回調
}, (OnError) error -> {
//失敗回調
});
3.11.1、加密
請求加密,需要自定義Param,非常簡單,詳情請查看本文5.2章節----自定義Param
3.11.2、解密
有些時候,請求會返回一大串的密文,此時就需要將密文轉化為明文,直接來看代碼,如下:
//設置數據解密/解碼器
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即可,如下:
RxHttp.get("/service/...")
.setDecoderEnabled(false) //設置本次請求不需要解密,默認為true
.asString()
.subscribe(pageList -> {
//成功回調
}, (OnError) error -> {
//失敗回調
});
RxHttp默認在Io線程執行請求,也默認在Io線程回調,即默認在同一Io線程執行請求并回調,當然,我們也可以指定請求/回調所在線程。
3.12.1、指定請求所在線程
我們可以調用一些列subscribeXxx方法指定請求所在線程,如下:
//指定請求所在線程,需要在第二部曲前任意位置調用,第二部曲后調用無效
RxHttp.get("/service/...")
.subscribeOnCurrent() //指定在當前線程執行請求,即同步執行,
.asString()
.subscribe();
//其它subscribeXxx方法
subscribeOnIo() //RxHttp默認的請求線程
subscribeOnSingle()
subscribeOnNewThread()
subscribeOnComputation()
subscribeOnTrampoline()
subscribeOn(Scheduler) //自定義請求線程
以上使用的皆是RxJava的線程調度器,不熟悉的請自行查閱相關資料,這里不做詳細介紹。
3.12.2、指定回調所在線程
指定回調所在線程,依然使用RxJava的線程調度器,如下:
//指定回調所在線程,需要在第二部曲后調用
RxHttp.get("/service/...")
.asString()
.observeOn(AndroidSchedulers.mainThread()) //指定在主線程回調
.subscribe(s -> { //s為String類型,主線程回調
//成功回調
}, throwable -> {
//失敗回調
});
時常會有童鞋問我,我是Retrofit用戶,喜歡把接口寫在一個類里,然后可以直接調用,RxHttp如何實現?其實,這個問題壓根就不是問題,在介紹第二部曲的時候,我們知道,使用asXxx方法后,就會返回Observable<T>
對象,因此,我們就可以這樣實現:
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又如何實現?簡單,如下:
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的后面,帶上對應的參數即可。
在RxHttp有4個重要的角色,分別是:
為此,我畫了一個流程圖,可以直觀的了解到RxHttp的大致工作流程
我想應該很好理解,RxHttp要做的事情,就是把添加的參數/請求頭等全部丟給Param處理,自己啥事也不敢;隨后將Param交給HttpSender,讓它去執行請求,執行完畢,返回Response對象;接著又將Response對象丟給Parser去做數據解析工作,并返回實體類對象T;最后,將T通過回調傳給開發者,到此,一個請求就處理完成。
首先,附上一張Param類的繼承關系圖
下面將從上往下對上圖中的類做個簡單的介紹:
HttpSender可以把它理解為請求發送者,里面聲明OkHttpClient對象和一系列靜態方法,我們來簡單看下:
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對象,就可以發送一個請求。
先看下Parser繼承結構圖
這里對上圖中的類做個簡單的介紹
T onParse(Response)
方法,輸入Response對象,輸出實體類對象TasList(Class<T>)
方法,就是通過該解析器實現的前面第二部曲中,我們介紹了一系列asXxx方法,通過該系列方法可以很方便的指定數據返回類型,特別是自定義的asResponse(Class<T>)
、asResponseList(Class<T>)
、asResponsePageList(Class<T>)
這3個方法,將Reponse<T>
類型數據,處理的簡直不要太完美,下面我們就來看看如何自定義Parser。
源碼永遠是最好的學習方式,在學習自定義Parser前,我們不妨先看看內置的Parser是如何實現的
SimPleParser
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接口中定義的,并且有默認的實現,如下:
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是不是這樣實現的,如下:
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>
數據類型,先看看數據結構:
public class Response<T> {
private int code;
private String msg;
private T data;
//這里省略get、set方法
}
自定義ResponseParser代碼如下:
//通過@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>)
方法,如下:
public <T> Observable<T> asResponse(Class<T> type) {
return asParser(new ResponseParser(type));
}
可以看到,該方法內部又調用了asParser(Parser<T>)
方法,并傳入了ResponseParser,因此,我們有兩種方式使用自定義的ResponseParser,如下:
//第一種方式,使用@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
@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
@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;
}
}
自定義Param,想較于自定義Parser,要更加的簡單,我們只需根據自己的需求,繼承NoBodyParam、FormParam、JsonParam等,增加或者重寫方法即可,比如我們有以下3種情況,需要自定義Param,如下:
這種情況,我們需要繼承FormParam,并重寫getRequestBody()方法,如下:
@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();
}
}
這種情況,我們需要繼承JsonParam,也重寫getRequestBody()方法,如下:
@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); //發送加密后的字符串
}
}
我們繼承FormParam,并新增兩個test方法`,如下:
@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;
}
}
同樣的問題,我們怎么用這3個自定義的Param呢?我想大多數人在類名前發現類@Param
注解,并為Param取了別名。那這個又有什么作用呢?
答案揭曉,只要在自定的Param上使用了@Param
注解,并取了別名,就會在RxHttp類自動生成一個跟別名一樣的方法,在上面我們自定義了3個Param,并分別取別名為postEncryptForm、postEncryptJson、postTestForm,此時就會在RxHttp類中生成postEncryptForm(String)
、postEncryptJsonString)
、postTestForm(String)
這3個方法,我們在RxHttp這個類中來看下:
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));
}
發請求時,只需要調用對應的方法就好,如:
//發送加密的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!!!!,選擇對應的請求方法后,就可以直接調用,如下:
//發送加密的postJson請求
RxHttp.postTestJson("/service/...")
.test(100L, 99.99F) //調用自定義的API
.test1("testKey", 88.88D) //調用自定義的API
.add("key", "value") //添加參數,可調用多次
.asString() //返回String類型
.subscribe(s-> {
//請求成功
}, throwable -> {
//請求失敗
});
RxHttp內部默認使用來GsonConverter,并且額外提供了4個Converter,如下:
//非必須 根據自己需求選擇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'
即使這樣,RxHttp也無法保證滿足所有的業務需求,為此,我們可以選擇自定義Converter,自定義Converter需要繼承IConverter接口,如下:
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
請查看本文3.10章節----設置Converter
在這教大家一個小技巧,由于使用RxHttp發送請求都遵循請求三部曲,故我們可以在android studio 設置代碼模版,如下
如圖設置好后,寫代碼時,輸入rp,就會自動生成模版,如下:
到這,RxHttp常用功能介紹完畢,你會發現,一切都是那么的美好,無論你是get、post、加密請求、自定義解析器,還是文件上傳/下載/進度監聽等等,皆遵循請求三部曲。特別是對Response<T>
類型數據處理,可以說是天衣無縫,我們無需每次都判斷code,直接就可以拿到T,簡直了。。。
最后,喜歡的,請給本文點個贊,如果可以,還請給個star,創作不易,感激不盡。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。