RxHttp 一條鏈發送請求之強大的數據解析功能(二)

前言

在上一文中,咱們對RxHttp作了一個總體的介紹,文章一經發表後,就收到了廣大讀者衆多不一樣的聲音,有對個人確定,也有對RxHttp提出改進的建議,更有讀者直接指出了個人不足,爲此,我收穫了不少,讓我對不少東西都有了新的認知,我想這就是不少人堅持寫做的緣由,由於這裏,能夠相互學習,相互交流以彌補本身的不足。因此我要感謝大家,是大家給我了動力讓我繼續寫做,我會堅持寫一些有養分的文章。php

因爲剛開始寫做,上一文有不少地方寫的不夠好,讓很多讀者走了冤枉路;收到讀者給我提出了改進的建議後,我加班加點將RxHttp 版本升級到了1.0.2,主要增長了設置baseUrl的功能,我想這是目前市面上最優雅的設置方法。 爲此我對上一文作了不少修改,歡迎新老讀者打臉 RxHttp 一條鏈發送請求,新一代Http請求神器(一)java

簡介

數據解析器Parser在RxHttp擔任着一個很重要的角色,它的做用的將Http返回的數據,解析成咱們想要的任意對象,能夠用Json、DOM等任意數據解析方式。目前RxHttp提供了三個解析器,分別是SimpleParserListParserDownloadParser,若是這3個解析器不能知足咱們的業務開發,就能夠自定義解析器,下面我詳細介紹。git

首先咱們先看看Parser的內部結構github

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 對應的數據結構以下post

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的源碼 this

在這裏插入圖片描述
咱們具體看 onParser方法,能夠看到。

  • 首先經過將Http請求返回的Response(注意,此Response類是OkHttp內部的類,並不上咱們上面定義的類)對象,拿到Http的請求結果,爲String對象
  • 而後就拿到咱們傳入的泛型類型判斷是不是String類型,若是是,則直接將結果返回,不然就經過Json將結果解析成咱們傳入的泛型對象
  • 最後對泛型對象作判斷,若是爲空,就表明解析失敗,咱們拋出異常(這裏的異常會被RxJava的onError觀察者接收),不然返回泛型對象

到這,我想你應該知道SimpleParser解析器的做用類,它就是將Http請求返回的結果直接解析成咱們想要的任意對象。

自問:你說SimpleParser能將數據解析成任意對象,而asObject(Class<T> type)操做符傳入的是一個Class<T>類型,而對於List對象,只能傳入List.classList裏面的泛型我怎麼傳入呢?又該如何實現呢? 自答:若是想獲得一個list<T>對象,經過asObject操做符確實沒辦法實現,可是SimpleParser卻能實現,咱們能夠直接new 出一個SimpleParser對象,而且傳入一個List<T>便可,咱們假設要獲取學生的集合,以下:

RxHttp.get("/service/...")
        .asParser(new SimpleParser<List<Student>>() {})
        .as(RxLife.asOnMain(this))  //感知生命週期,並在主線程回調
        .subscribe(students -> { //這裏students 即爲List<Student>對象
            //成功回調
        }, throwable -> {
            //失敗回調
        });
複製代碼

能夠看到,咱們直接使用asParser操做符,並傳入咱們new出來的SimpleParser對象,最後在觀察者就能拿到List<Student>對象。 到這,有讀者會有疑問,咱們new出來的SimpleParser對象,爲啥要使用匿名內部類呢?不使用不行嗎?能夠確定的回答不行。若是new SimpleParser<List<Student>>()這樣書寫,編譯器會報錯,爲何呢?眼尖的你可能發現了,SimpleParser無參的構造方法是protected關鍵字修飾的,那爲啥要用protected關鍵字修飾呢?由於不用protected關鍵字修飾,SimpleParser內部就拿不到泛型的具體類型,若是你再要問爲何,那你就須要瞭解一些泛型了,這個跟Gson庫裏面的TypeToken類是同一個道理,能夠查看個人另外一片文章Android、Java泛型掃盲

上面SimpleParser咱們是經過匿名內部類new出來的,而後咱們知道,內部類都會持有外部類的引用,若是外部類是一個Activity,就有可能會有內存泄漏的危險(若是使用了RxLife就不會有這種危險),並且,這種寫法本人也不是很喜歡。爲此,有沒有什麼辦法來避免此類問題呢?

有,那就是經過ListParser解析器

ListParser

ListParser的做用是,將Http返回的結果,用Json解析成List<T>對象,源碼以下:

在這裏插入圖片描述
代碼跟SimpleParser差很少,不在詳細講解。不一樣的是這裏使用了 ParameterizedTypeImpl類來處理泛型,這個類的用法及原理,也查看個人另外一片文章 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對象。

接下來咱們看看RxHttp提供的最後一個解析器DownloadParser

DownloadParser

DownloadParser的做用是將Http返回的輸入流寫到文件中,即文件下載

在這裏插入圖片描述
這個好理解,就不仔細講解了,有一點要的說的,此解析器是支持斷點下載,咱們來看看如何實現斷點下載,而且帶進度回調

//斷點下載,帶進度
public void breakpointDownloadAndProgress() {
    String destPath = getExternalCacheDir() + "/" + "Miaobo.apk";
    long length = new File(destPath).length();
    RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
            .setRangeHeader(length)  //設置開始下載位置,結束位置默認爲文件末尾
            .asDownloadProgress(destPath,length) //若是須要銜接上次的下載進度,則須要傳入上次已下載的字節數
            .observeOn(AndroidSchedulers.mainThread()) //主線程回調
            .doOnNext(progress -> {
                //下載進度回調,0-100,僅在進度有更新時纔會回調
                int currentProgress = progress.getProgress(); //當前進度 0-100
                long currentSize = progress.getCurrentSize(); //當前已下載的字節大小
                long totalSize = progress.getTotalSize();     //要下載的總字節大小
            })
            .filter(Progress::isCompleted)//過濾事件,下載完成,才繼續往下走
            .map(Progress::getResult) //到這,說明下載完成,拿到Http返回結果並繼續往下走
            .as(RxLife.asOnMain(this)) //加入感知生命週期的觀察者
            .subscribe(s -> { //s爲String類型
                //下載成功,處理相關邏輯
            }, throwable -> {
                //下載失敗,處理相關邏輯
            });
}
複製代碼

跟帶進度回調的下載代碼差很少,上面也有註釋,就不在講解了。

自定義解析器

在上面的介紹的3個解析中,SimpleParser能夠說是萬能的,任何數據結構,只要你建好對應的Bean類,都可以正確解析,就是要咱們去建n個Bean類,甚至這些Bean類,可能不少都是能夠抽象化的。例如,大部分Http返回的數據結構均可以抽象成下面的Bean類

public class Data<T> {
    private int    code;
    private String msg;
    private T      data;
    //這裏省略get、set方法
}
複製代碼

假設,Data裏面的T是一個學生對象,咱們要拿到此學生信息,就能夠這麼作

RxHttp.get(http://www.......) //這裏get,表明Get請求
        .asParser(new SimpleParser<Data<Student>>() {}) //這裏泛型傳入Data<Student>
        .observeOn(AndroidSchedulers.mainThread()) //主線程回調
        .map(Data::getData) //經過map操做符獲取Data裏面的data字段
        .as(RxLife.asOnMain(this))
        .subscribe(student -> {
            //這裏的student,即Data裏面的data字段內容
        }, throwable -> {
            //Http請求出現異常
        });
複製代碼

以上代碼有3個缺點

  • 仍是經過SimpleParse匿名內部類實現的,前面說過,這種方式有可能形成內存泄漏,並且寫法上不是很優雅
  • 下游首先拿到的是一個Data<Student>對象,隨後使用map操做符從Data<Student>拿到Student對象傳給下游觀察者
  • 無法統一對Data裏面的code字段作驗證

DataParser

那麼有什麼優雅的辦法解決呢?答案就是自定義解析器。咱們來定一個DataParser解析器,以下:

在這裏插入圖片描述
代碼跟SimpleParser類差很少,好處以下

  • DataParser自動爲咱們作了一層過濾,咱們能夠直接拿到T對象,而再也不使用map操做符了
  • 內部能夠對code字段作統一判斷,根據不一樣的code,拋出不一樣的異常,作到統一的錯誤處理機制(這裏拋出的異常會被下游的onError觀察者接收)
  • 當codo正確時,就表明了數據正確,下游的onNext觀察者就能收到事件
  • 避免了使用匿名內部類

此時,咱們就能夠以下實現:

RxHttp.get("http://www...") //這裏get,表明Get請求
        .asDataParser(Student.class)  //此方法是經過註解生成的
        .as(RxLife.asOnMain(this))
        .subscribe(student -> {
            //這裏的student,即Data裏面的data字段內容
        }, throwable -> {
            //Http請求出現異常
            String msg = throwable.getMessage(); //Data裏面的msg字段或者其餘異常信息
            String code = throwable.getLocalizedMessage(); //Data裏面的code字段,若是有傳入的話
        });
複製代碼

注:咱們在定義DataParser時,使用了註解@Parser(name = "DataParser"),故在RxHttp類裏有asDataParser方法,註解使用請查看RxHttp 一條鏈發送請求之註解處理器 Generated API(四)

而後,若是Data裏面T是一個List<T>又該怎麼辦呢?咱們也許能夠這樣:

RxHttp.get("http://www...") //這裏get,表明Get請求
        .asParser(new DataParser<List<Student>>() {})  
        .as(RxLife.asOnMain(this))
        .subscribe(students -> {
            //這裏的students,爲List<Student>對象
        }, throwable -> {
            //Http請求出現異常
            String msg = throwable.getMessage(); //Data裏面的msg字段或者其餘異常信息
            String code = throwable.getLocalizedMessage(); //Data裏面的code字段,若是有傳入的話
        });
複製代碼

又是經過匿名內部類實現的,心累,有沒有更優雅的方式?有,仍是自定義解析器,咱們來定義一個DataListParser解析器

DataListParser

在這裏插入圖片描述
代碼都差很少,就不在講解了,直接看怎麼用:

RxHttp.get("http://www...") //這裏get,表明Get請求
        .asDataListParser(Student.class)  //此方法是經過註解生成的
        .as(RxLife.asOnMain(this))
        .subscribe(students -> {
            //這裏的students,爲List<Student>對象
        }, throwable -> {
            //Http請求出現異常
            String msg = throwable.getMessage(); //Data裏面的msg字段或者其餘異常信息
            String code = throwable.getLocalizedMessage(); //Data裏面的code字段,若是有傳入的話
        });
複製代碼

注: asDataListParser方法也是經過註解生成的 咱們最後來看一個問題

{
    "code": 0,
    "msg": "",
    "data": {
        "totalPage": 0,
        "list": []
    }
}
複製代碼

這種數據,咱們又該如何解析呢?首先,咱們再定一個Bean類叫PageList,以下:

public class PageList<T> {
    private int     totalPage;
    private List<T> list;
    //省略get/set方法
}
複製代碼

此Bean類,對於的是data字段的數據結構,機智的你確定立刻想到了用DataParser如何實現,以下:

RxHttp.get("http://www...") //這裏get,表明Get請求
        .asParser(new DataParser<PageList<Student>>(){})
        .as(RxLife.asOnMain(this))
        .subscribe(pageList -> {
            //這裏的pageList,即爲PageList<Student>類型
        }, throwable -> {
            //Http請求出現異常
            String msg = throwable.getMessage(); //Data裏面的msg字段或者其餘異常信息
            String code = throwable.getLocalizedMessage(); //Data裏面的code字段,若是有傳入的話
        });
    }
複製代碼

好吧,又是匿名內部類,仍是乖乖自定義解析器吧。咱們定義一個DataPageListParser解析器,以下:

DataPageListParser

在這裏插入圖片描述
繼續看看怎麼用

RxHttp.get("http://www...") //這裏get,表明Get請求
        .asDataPageListParser(Student.class)  //此方法是經過註解生成的
        .as(RxLife.asOnMain(this))
        .subscribe(pageList -> {
            //這裏的pageList,即爲PageList<Student>類型
        }, throwable -> {
            //Http請求出現異常
            String msg = throwable.getMessage(); //Data裏面的msg字段或者其餘異常信息
            String code = throwable.getLocalizedMessage(); //Data裏面的code字段,若是有傳入的話
        });
複製代碼

注: asDataPageListParser方法依然是經過註解生成的。

到這,仔細觀察你會發現,咱們定一個三個解析器DataParser、DataListParser及DataPageListParser,代碼其實都差很少,用法也差很少,無非就輸出的不同。

小結

本篇文章,給你們介紹了RxHttp自定的三個解析器SimpleParserListParserDownloadParser他們的用法及內部實現,後面又對常見的數據結構帶領你們自定義了3個解析器,分別是DataParser、DataListParser及DataPageListParser,相信有了這個6個解析器,就能應對大多數場景了。若是還有場景不能實現,看完本篇文章自定義解析對你來講也是很是容易的事情了。

自問: 爲啥不將自定義的三個解析器DataParser、DataListParser及DataPageListParser封裝進RxHttp? 自答: 由於這3個解析器都涉及到了具體的業務需求,每一個開發者的業務邏輯均可能不同,故不能封裝進RxHttp庫裏。

最後,本文若是有寫的不對的地方,請廣大讀者指出。 若是以爲我寫的不錯,記得給我點贊RxHttp

更過詳情請查看RxHttp系列其它文章

RxHttp 一條鏈發送請求之強大的Param類(三)

RxHttp 一條鏈發送請求之註解處理器 Generated API(四)

轉載請註明出處,謝謝🙏

相關文章
相關標籤/搜索