前言
在上一文中,我們對RxHttp做了一個整體的介紹,文章一經發表後,就收到了廣大讀者眾多不同的聲音,有對我的肯定,也有對RxHttp提出改進的建議,更有讀者直接指出了我的不足,為此,我收穫了很多,讓我對很多東西都有了新的認知,我想這就是很多人堅持寫作的原因,因為這裡,可以相互學習,相互交流以彌補自己的不足。所以我要感謝你們,是你們給我了動力讓我繼續寫作,我會堅持寫一些有營養的文章。
簡介
資料解析器Parser
在RxHttp擔任著一個很重要的角色,它的作用的將Http返回的資料,解析成我們想要的任意物件,可以用Gson、SampleXml、ProtoBuf、FastJson等第三方資料解析工具。目前RxHttp提供了5個解析器,分別是SimpleParser
、ListParser
、MapParser
、BitmapParser
及DownloadParser
,如果這5個解析器不能滿足我們的業務開發,還可以自定義解析器,下面我詳細介紹。
首先我們先看看Parser的內部結構
public interface Parser<T> {
/**
* 資料解析
* @param response Http執行結果
* @return 解析後的物件型別
* @throws IOException 網路異常、解析異常
*/
T onParse(@NonNull Response response) throws IOException;
}
複製程式碼
可以看到,Parser就是一個介面類,並且裡面只有一個方法,輸入Http請求返回的Response
物件,輸出我們傳入的泛型T
,如果我們要自定義解析器,就必須要實現此介面。
在上一文中,我們對Parser做了簡單的介紹,我們來回顧一下。
內建解析器
SimpleParser
我們拿淘寶獲取IP的介面作為測試介面http://ip.taobao.com/service/getIpInfo.php?ip=63.223.108.42
對應的資料結構如下
public class Response {
private int code;
private Address data;
//省略set、get方法
class Address {
//為簡單起見,省略了部分欄位
private String country; //國家
private String region; //地區
private String city; //城市
//省略set、get方法
}
}
複製程式碼
開始傳送請求
RxHttp.get("http://ip.taobao.com/service/getIpInfo.php") //Get請求
.add("ip", "63.223.108.42")//新增引數
.addHeader("accept", "*/*") //新增請求頭
.addHeader("connection", "Keep-Alive")
.addHeader("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)")
.asObject(Response.class) //這裡返回Observable<Response> 物件
.as(RxLife.asOnMain(this)) //感知生命週期,並在主執行緒回撥
.subscribe(response -> {
//成功回撥
}, throwable -> {
//失敗回撥
});
複製程式碼
上面程式碼中使用了asObject
操作符,並傳入Response.class
,此時觀察者就能能拿到Response物件,那麼它是如何實現的呢?它內部就是用了SimpleParser解析器,我們點進去看看
public <T> Observable<T> asObject(Class<T> type) {
return asParser(SimpleParser.get(type));
}
複製程式碼
果然它使用了SimpleParser解析器,那我們就來看看SimpleParser的原始碼
public class SimpleParser<T> extends AbstractParser<T> {
//省略構造方法
@Override
public T onParse(Response response) throws IOException {
return convert(response, mType);
}
}
複製程式碼
額。。onParser
方法只呼叫了父類的convert方法,繼續看原始碼:
public interface Parser<T> {
default <R> 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);
}
}
複製程式碼
我們具體看onParser
方法,可以看到。
首先通過Response物件拿到ResponseBody,隨後拿到一個IConverter介面物件,然後將ResponseBody物件轉換為我們期望的泛型物件。
到這,我想你應該知道SimpleParser
解析器的作用類,它就是將Http請求返回的結果直接解析成我們想要的任意物件。
如果我們要獲取List<Student>
物件呢,則可以直接new出一個SimpleParser物件,如下:
RxHttp.get("/service/...")
.asParser(new SimpleParser<List<Student>>() {})
.as(RxLife.asOnMain(this)) //感知生命週期,並在主執行緒回撥
.subscribe(students -> { //這裡students 即為List<Student>物件
//成功回撥
}, throwable -> {
//失敗回撥
});
複製程式碼
可以看到,我們直接使用asParser
操作符,並傳入我們new出來的SimpleParser物件,最後在觀察者就能拿到List<Student>
物件。
到這,也許有人會說,這種new出來的寫法一點都不友好,程式碼看起來不簡潔,有沒有更簡單的寫法。
有,那就是通過ListParser解析器
ListParser
ListParser的作用是,將Http返回的結果,解析成List<T>
物件,原始碼如下:
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差不多,不在詳細講解。不同的是這裡使用了ParameterizedTypeImpl
類來包裝了一個List<Student>
型別,這個類的用法及原理,也檢視我的另一片文章Android、Java泛型掃盲
我們直接看看通過ListParser
如何拿到List<T>
物件,如下
RxHttp.get("/service/...")
.asList(Student.class)
.as(RxLife.asOnMain(this)) //感知生命週期,並在主執行緒回撥
.subscribe(students -> { //這裡students 即為List<Student>物件
//成功回撥
}, throwable -> {
//失敗回撥
});
複製程式碼
可以看到,直接使用asList
操作符,傳入Student.class
即可,它內部就是通過ListParser.get(Student.class)
獲取的ListParser物件。
MapParser
除了能直接獲取List物件外,我們還可以通過MapParser直接獲取一個Map物件,如下:
RxHttp.get("/service/...")
.asMap(String.class, Student.class)
.as(RxLife.asOnMain(this)) //感知生命週期,並在主執行緒回撥
.subscribe(map -> { //這裡map 即為Map<String, Student>物件
//成功回撥
}, throwable -> {
//失敗回撥
});
複製程式碼
可以看到,通過asMap(Class<K> kType, Class<V> vType)
方法,不僅能得一個map物件,還能指定map裡面得泛型型別。
到這,相信你也能猜到BitmapParser、DownloadParser這兩個解析器如何使用了。是的,通過asBitmap
、asDownload
操作符,就能獲取到一個Bitmap物件和執行下載操作,這裡就不再講解了。
自定義解析器
在上面的介紹的3個解析中,SimpleParser可以說是萬能的,任何資料結構,只要你建好對應的Bean類,都能夠正確解析,就是要我們去建n個Bean類,甚至這些Bean類,可能很多都是可以抽象化的。例如,大部分Http返回的資料結構都可以抽象成下面的Bean類
public class Response<T> {
private int code;
private String msg;
private T data;
//這裡省略get、set方法
}
複製程式碼
假設,Response裡面的T是一個學生物件,我們要拿到此學生資訊,就可以這麼做
RxHttp.get(http://www.......) //這裡get,代表Get請求
.asParser(new SimpleParser<Response<Student>>() {}) //這裡泛型傳入Response<Student>
.observeOn(AndroidSchedulers.mainThread()) //主執行緒回撥
.map(Response::getData) //通過map操作符獲取Response裡面的data欄位
.as(RxLife.asOnMain(this))
.subscribe(student -> {
//這裡的student,即Response裡面的data欄位內容
}, throwable -> {
//Http請求出現異常
});
複製程式碼
以上程式碼有3個缺點
- 還是通過SimpleParse匿名內部類實現的,前面說過,這種方式有可能造成記憶體洩漏,而且寫法上不是很優雅
- 下游首先拿到的是一個
Response<Student>
物件,隨後使用map
操作符從Response<Student>
拿到Student物件傳給下游觀察者 - 沒法統一對
Response
裡面的code欄位做驗證
ResponseParser
那麼有什麼優雅的辦法解決呢?答案就是自定義解析器。我們來定一個ResponseParser解析器,如下:
@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 (t == null && mType == String.class) {
/*
* 考慮到有些時候服務端會返回:{"errorCode":0,"errorMsg":"關注成功"} 類似沒有data的資料
* 此時code正確,但是data欄位為空,直接返回data的話,會報空指標錯誤,
* 所以,判斷泛型為String型別時,重新賦值,並確保賦值不為null
*/
t = (T) data.getMsg();
}
if (data.getCode() != 0 || t == null) {//code不等於0,說明資料不正確,丟擲異常
throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
}
return t;
}
}
複製程式碼
程式碼跟SimpleParser類差不多,好處如下
- ResponseParser自動為我們做了一層過濾,我們可以直接拿到T物件,而不再使用map操作符了
- 內部可以對code欄位做統一判斷,根據不同的code,丟擲不同的異常,做到統一的錯誤處理機制(這裡丟擲的異常會被下游的onError觀察者接收)
- 當codo正確時,就代表了資料正確,下游的onNext觀察者就能收到事件
- 避免了使用匿名內部類
此時,我們就可以如下實現:
RxHttp.get("http://www...") //這裡get,代表Get請求
.asResponse(Student.class) //此方法是通過註解生成的
.as(RxLife.asOnMain(this))
.subscribe(student -> {
//這裡的student,即Response裡面的data欄位內容
}, throwable -> {
//Http請求出現異常
String msg = throwable.getMessage(); //Response裡面的msg欄位或者其他異常資訊
String code = throwable.getLocalizedMessage(); //Response裡面的code欄位,如果有傳入的話
});
複製程式碼
注:
我們在定義ResponseParser時,使用了註解@Parser(name = "Response")
,故在RxHttp類裡有asResponse
方法,註解使用請檢視RxHttp 一條鏈傳送請求之註解處理器 Generated API(四)
然後,如果Data裡面T是一個List<T>
又該怎麼辦呢?我們也許可以這樣:
RxHttp.get("http://www...") //這裡get,代表Get請求
.asParser(new ResponseParser<List<Student>>() {})
.as(RxLife.asOnMain(this))
.subscribe(students -> {
//這裡的students,為List<Student>物件
}, throwable -> {
//Http請求出現異常
String msg = throwable.getMessage(); //Response裡面的msg欄位或者其他異常資訊
String code = throwable.getLocalizedMessage(); //Response裡面的code欄位,如果有傳入的話
});
複製程式碼
又是通過匿名內部類實現的,心累,有沒有更優雅的方式?有,還是自定義解析器,我們來定義一個ResponseListParser解析器
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;
}
}
複製程式碼
程式碼都差不多,就不在講解了,直接看怎麼用:
RxHttp.get("http://www...") //這裡get,代表Get請求
.asResponseList(Student.class) //此方法是通過註解生成的
.as(RxLife.asOnMain(this))
.subscribe(students -> {
//這裡的students,為List<Student>物件
}, throwable -> {
//Http請求出現異常
String msg = throwable.getMessage(); //Response裡面的msg欄位或者其他異常資訊
String code = throwable.getLocalizedMessage(); //Response裡面的code欄位,如果有傳入的話
});
複製程式碼
注:
asResponseList
方法也是通過註解生成的
我們最後來看一個問題
{
"code": 0,
"msg": "",
"data": {
"totalPage": 0,
"list": []
}
}
複製程式碼
這種資料,我們又該如何解析呢?首先,我們再定一個Bean類叫PageList,如下:
public class PageList<T> {
private int totalPage;
private List<T> list;
//省略get/set方法
}
複製程式碼
此Bean類,對於的是data欄位的資料結構,機智的你肯定馬上想到了用ResponseParser如何實現,如下:
RxHttp.get("http://www...") //這裡get,代表Get請求
.asParser(new ResponseParser<PageList<Student>>(){})
.as(RxLife.asOnMain(this))
.subscribe(pageList -> {
//這裡的pageList,即為PageList<Student>型別
}, throwable -> {
//Http請求出現異常
String msg = throwable.getMessage(); //Response裡面的msg欄位或者其他異常資訊
String code = throwable.getLocalizedMessage(); //Response裡面的code欄位,如果有傳入的話
});
}
複製程式碼
好吧,又是匿名內部類,還是乖乖自定義解析器吧。我們定義一個ResponsePageListParser解析器,如下:
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;
}
}
複製程式碼
繼續看看怎麼用
RxHttp.get("http://www...") //這裡get,代表Get請求
.asResponsePageList(Student.class) //此方法是通過註解生成的
.as(RxLife.asOnMain(this))
.subscribe(pageList -> {
//這裡的pageList,即為PageList<Student>型別
}, throwable -> {
//Http請求出現異常
String msg = throwable.getMessage(); //Response裡面的msg欄位或者其他異常資訊
String code = throwable.getLocalizedMessage(); //Response裡面的code欄位,如果有傳入的話
});
複製程式碼
注:
asResponsePageList
方法依然是通過註解生成的。
到這,仔細觀察你會發現,我們定一個三個解析器ResponseParser、ResponseListParser及ResponsePageListParser,程式碼其實都差不多,用法也差不多,無非就輸出的不一樣。
小結
本篇文章,給大家介紹了RxHttp自定的三個解析器SimpleParser
、ListParser
及DownloadParser
他們的用法及內部實現,後面又對常見的資料結構帶領大家自定義了3個解析器,分別是ResponseParser、ResponseListParser及ResponsePageListParser,相信有了這個6個解析器,就能應對大多數場景了。如果還有場景不能實現,看完本篇文章自定義解析對你來說也是非常容易的事情了。
自問:
為啥不將自定義的三個解析器ResponseParser、ResponseListParser及ResponsePageListParser封裝進RxHttp?
自答:
因為這3個解析器都涉及到了具體的業務需求,每個開發者的業務邏輯都可能不一樣,故不能封裝進RxHttp庫裡。
最後,本文如果有寫的不對的地方,請廣大讀者指出。 如果覺得我寫的不錯,記得給我點贊RxHttp
更過詳情請檢視RxHttp系列其它文章
RxHttp 一條鏈傳送請求之註解處理器 Generated API(四)
轉載請註明出處,謝謝?