溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時間:2020-02-24 06:46:55 來源:網(wǎng)絡(luò) 閱讀:170 作者:wx5e047ce5e12b4 欄目:移動開發(fā)

1、前言

RxHttp在今年4月份一經(jīng)推出,就受到了廣大Android 開發(fā)者的喜愛,截止本文發(fā)表在github上已有1100+star,為此,我自己也建個RxHttp&RxLife 的群(群號:378530627)目前群里也有將近120號人,里面有不少小伙伴提了很多有價值的創(chuàng)意,才使得RxHttp一直堅持走到了現(xiàn)在,在此,感謝大家的喜愛。

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

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

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

2、簡介

RxHttp是基于OkHttp的二次封裝,并與RxJava做到無縫銜接,一條鏈就能發(fā)送任意請求。主要優(yōu)勢如下:

1. 支持Gson、Xml、ProtoBuf、FastJson等第三方數(shù)據(jù)解析工具

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

3. 支持在Activity/Fragment/View/ViewModel/任意類中,自動關(guān)閉請求

4. 支持統(tǒng)一加解密,且可對單個請求設(shè)置是否加解密

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

6. 史上最優(yōu)雅的實(shí)現(xiàn)文件上傳/下載及進(jìn)度的監(jiān)聽,且支持?jǐn)帱c(diǎn)下載

7. 史上最優(yōu)雅的對錯誤統(tǒng)一處理,且不打破Lambda表達(dá)式

8. 史上最優(yōu)雅的處理多個BaseUrl及動態(tài)BaseUrl

9. 史上最優(yōu)雅的處理網(wǎng)絡(luò)緩存

10. 30秒即可上手,學(xué)習(xí)成本極低

gradle依賴

implementation 'com.rxjava.rxhttp:rxhttp:1.3.6'
//注解處理器,生成RxHttp類,即可一條鏈發(fā)送請求
annotationProcessor 'com.rxjava.rxhttp:rxhttp-compiler:1.3.6'
//管理RxJava及生命周期,Activity/Fragment 銷毀,自動關(guān)閉未完成的請求
implementation 'com.rxjava.rxlife:rxlife:1.1.0'

//非必須 根據(jù)自己需求選擇Converter  RxHttp默認(rèn)內(nèi)置了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 全網(wǎng)Http緩存最優(yōu)解

3、使用

3.1、準(zhǔn)備工作

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

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

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

3.2、配置默認(rèn)的BaseUrl

通過@DefaultDomain注解配置默認(rèn)域名,如下:

public class Url {
    @DefaultDomain //設(shè)置為默認(rèn)域名
    public static String baseUrl = "https://www.wanandroid.com/";
}

此步驟是非必須的,這里先介紹@DefaultDomain注解的用法,更多有關(guān)域名的介紹,請查看本文3.6章節(jié)----多域名/動態(tài)域名

3.3、請求三部曲

先來看看如何發(fā)送一個最簡單的請求,如下

RxHttp.get("http://...")  //第一步, 通過get、postXxx、putXxx等方法,確定請求類型         
    .asString()           //第二步, 通過asXxx系列方法,確定返回數(shù)據(jù)類型    
    .subscribe(s -> {     //第三步, 訂閱回調(diào)(此步驟同RxJava訂閱觀察者)
        //請求成功                                         
    }, throwable -> {                                  
        //請求失敗                                         
    });                                                

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

任意請求,任意返回數(shù)據(jù)類型,皆遵循請求三部曲

任意請求,任意返回數(shù)據(jù)類型,皆遵循請求三部曲

任意請求,任意返回數(shù)據(jù)類型,皆遵循請求三部曲

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

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

RxHttp內(nèi)部共提供了14個請求方法,如下:

RxHttp.get(String)              //get請求    參數(shù)拼接在url后面
RxHttp.head(String)             //head請求   參數(shù)拼接在url后面
RxHttp.postForm(String)         //post請求   參數(shù)以{application/x-www-form-urlencoded}形式提交
RxHttp.postJson(String)         //post請求   參數(shù)以{application/json; charset=utf-8}形式提交,發(fā)送Json對象
RxHttp.postJsonArray(String)    //post請求   參數(shù)以{application/json; charset=utf-8}形式提交,發(fā)送Json數(shù)組
RxHttp.putForm(String)          //put請求    參數(shù)以{application/x-www-form-urlencoded}形式提交
RxHttp.putJson(String)          //put請求    參數(shù)以{application/json; charset=utf-8}形式提交,發(fā)送Json對象
RxHttp.putJsonArray(String)     //put請求    參數(shù)以{application/json; charset=utf-8}形式提交,發(fā)送Json數(shù)組
RxHttp.patchForm(String)        //patch請求  參數(shù)以{application/x-www-form-urlencoded}形式提交
RxHttp.patchJson(String)        //patch請求  參數(shù)以{application/json; charset=utf-8}形式提交,發(fā)送Json對象
RxHttp.patchJsonArray(String)   //patch請求  參數(shù)以{application/json; charset=utf-8}形式提交,發(fā)送Json數(shù)組
RxHttp.deleteForm(String)       //delete請求 參數(shù)以{application/x-www-form-urlencoded}形式提交
RxHttp.deleteJson(String)       //delete請求 參數(shù)以{application/json; charset=utf-8}形式提交,發(fā)送Json對象
RxHttp.deleteJsonArray(String)  //delete請求 參數(shù)以{application/json; charset=utf-8}形式提交,發(fā)送Json數(shù)組

以上14個請求方法你會發(fā)現(xiàn),其實(shí)就6個類型,分別對應(yīng)是Get、Head、Post、Put、Patch、Delete方法,只是其中Post、Put、Patch、Delete各有3個方法有不同形式的提交方式,只需要根據(jù)自己的需求選擇就好。

如以上方法還不能滿足你的需求,我們還可以通過@Param注解自定義請求方法,有關(guān)注解的使用,本文后續(xù)會詳細(xì)介紹。

注:當(dāng)調(diào)用xxxForm方法發(fā)送請求時,通過setMultiForm()方法或者調(diào)用addFile(String, File)添加文件時,內(nèi)部會自動將參數(shù)以{multipart/form-data}方式提交

添加參數(shù)/請求頭

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

RxHttp.get("/service/...")       //發(fā)送get請求
    .add("key", "value")         //添加參數(shù)
    .addAll(new HashMap<>())     //通過Map添加多個參數(shù)
    .addHeader("deviceType", "android")     //添加請求頭
    ...

任意請求,都可調(diào)用以上3個方法添加參數(shù)/請求頭,當(dāng)然,在不同的請求方式下,也會有不同的addXxx方法供開發(fā)者調(diào)用。如下:

//postJson請求方法下會有更多addAll等方法可供調(diào)用
RxHttp.postJson("/service/...") //發(fā)送post Json請求
    .addAll(new JsonObject())   //通過json對象添加多個參數(shù)
    .addAll("{\"height\":180,\"weight\":70}") //通過json字符串添加多個參數(shù)
    ...

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

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

3.3.2、第二部曲:確定返回數(shù)據(jù)類型

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

RxHttp.postForm("/service/...")  //發(fā)送post表單請求
    .add("key", "value")         //添加參數(shù),可調(diào)用多次
    .asObject(Student.class)    //返回Student類型
    .subscribe(student -> {   
        //請求成功,這里就能拿到 Student對象               
    }, throwable -> {         
        //請求失敗                
    });    

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

RxHttp.postForm("/service/...")  //發(fā)送post表單請求
    .add("key", "value")         //添加參數(shù),可調(diào)用多次
    .asList(Student.class)       //返回List<Student>類型
    .subscribe(students -> {   
        //請求成功,這里就能拿到 Student對象列表               
    }, throwable -> {         
        //請求失敗                
    });    

解析Response&lt;T&gt;類型數(shù)據(jù)

然而,現(xiàn)實(shí)開發(fā)中,大多數(shù)人的接口,返回的數(shù)據(jù)結(jié)構(gòu)都類似下面的這個樣子

public class Response<T> {
    private int    code;
    private String msg;
    private T      data;
    //這里省略get、set方法
}

對于這種數(shù)據(jù)結(jié)構(gòu),按傳統(tǒng)的寫法,每次都要對code做判斷,如果有100個請求,就要判斷100次,真的會逼死強(qiáng)迫癥患者。

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

RxHttp.postForm("/service/...")   //發(fā)送post表單請求
    .add("key", "value")          //添加參數(shù),可調(diào)用多次
    .asResponse(Student.class)    //返回Student類型
    .subscribe(student -> {   
        //請求成功,這里能拿到 Student對象               
    }, throwable -> {         
        //請求失敗                
    });    

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

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

更多時候,我們的列表數(shù)據(jù)是分頁的,類似下面的數(shù)據(jù)結(jié)構(gòu)

{
    "code": 0,
    "msg": "",
    "data": {
        "totalPage": 0,
        "list": []
    }
}

此時,調(diào)用RxHttp的asResponsePageList(Class&lt;T&gt;)方法依然可以完美解決,如下:

RxHttp.postForm("/service/...")   //發(fā)送post表單請求
    .add("key", "value")          //添加參數(shù),可調(diào)用多次
    .asResponsePageList(Student.class)    //返回PageList<Student>類型
    .subscribe(pageList -> {   
        //請求成功,這里能拿到PageList<Student>列表對象 
       int totalPage = pageList.getTotalPage();   //總頁數(shù)
       List<Student> students = pageList.getData();  //單頁列表數(shù)據(jù)        
    }, throwable -> {         
        //請求失敗                
    });    

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

  • 你的code在哪里判斷的?
  • 我的code是100或者其它值才代表正確,怎么改?
  • 我的Response&lt;T&gt;類里面的字段名,跟你的都不一樣,怎么該?
  • 你這成功的時候直接返回Response&lt;T&gt;里面的T,那我還要拿到code做其他的判斷,執(zhí)行不同業(yè)務(wù)邏輯,怎么辦?

這里可以先告訴大家,asResponse(Class&lt;T&gt;)、asResponseList(Class&lt;T&gt;)、asResponsePageList(Class&lt;T&gt;)這3個方法并不是RxHttp內(nèi)部提供的,而是通過自定義解析器生成,里面的code判斷、Response&lt;T&gt;類都是開發(fā)者自定義的,如何自定義解析器,請查看本文5.1章節(jié)----自定義Parser。

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

RxHttp.postForm("/service/...")   //發(fā)送post表單請求
    .add("key", "value")          //添加參數(shù),可調(diào)用多次
    .asResponse(Student.class)    //返回Student類型
    .subscribe(student -> {   
        //請求成功,這里能拿到 Student對象               
    }, (OnError) error -> {     //注意,這里要用OnError接口,其中error是一個ErrorInfo對象  
        //失敗回調(diào)
        //拿到code字段,此時就可以對code做判斷,執(zhí)行不同的業(yè)務(wù)邏輯 
        int code = error.getErrorCode();     
        String errorMsg = error.getErrorMsg()  //拿到msg字段             
    });    

注:上面的OnError接口并非是RxHttp內(nèi)部提供的,而是自定義的,在Demo里可以找到

以上介紹的5個asXxx方法,可以說基本涵蓋80%以上的業(yè)務(wù)場景,接下來我們看看RxHttp都提供了哪些asXxx方法,如下:RxHttp  讓你眼前一亮的Http請求框架cdn.xitu.io/2019/12/9/16ee64d31d028d95?w=1348&h=832&f=png&s=238055">
RxHttp內(nèi)部共提供了23asXXX方法,其中:

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

duang、duang、duang !!! 劃重點(diǎn),這里我可以告訴大家,其實(shí)前面的14個方法,最終都是通過asParser(Parser&lt;T&gt;)方法實(shí)現(xiàn)的,具體實(shí)現(xiàn)過程,這里先跳過,后續(xù)會詳細(xì)講解。

3.3.3、第三部曲:訂閱回調(diào)

這一步就很簡單了,在第二部曲中,asXxx方法會返回Observable&lt;T&gt;對象,沒錯,就是RxJava內(nèi)部的Observable&lt;T&gt;對象,此時我們便可通過subscribe系列方法訂閱回調(diào),如下:

//不處理任何回調(diào)
RxHttp.postForm("/service/...")   //發(fā)送post表單請求
    .add("key", "value")          //添加參數(shù),可調(diào)用多次
    .asResponseList(Student.class)    //返回List<Student>類型
    .subscribe();    //不訂閱任何回調(diào)

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

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

//等等,省略

另外,我們還可以訂閱請求開始/結(jié)束的回調(diào),如下:

RxHttp.get("/service/...")
    .asString()
    .observeOn(AndroidSchedulers.mainThread())
    .doOnSubscribe(disposable -> {
        //請求開始,當(dāng)前在主線程回調(diào)
    })
    .doFinally(() -> {
        //請求結(jié)束,當(dāng)前在主線程回調(diào)
    })
    .as(RxLife.as(this))  //感知生命周期
    .subscribe(pageList -> {
        //成功回調(diào),當(dāng)前在主線程回調(diào)
    }, (OnError) error -> {
        //失敗回調(diào),當(dāng)前在主線程回調(diào)
    });

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

3.4、初始化

//設(shè)置debug模式,默認(rèn)為false,設(shè)置為true后,發(fā)請求,過濾"RxHttp"能看到請求日志
RxHttp.setDebug(boolean debug)
//非必須,只能初始化一次,第二次將拋出異常
RxHttp.init(OkHttpClient okHttpClient)
//或者,調(diào)試模式下會有日志輸出
RxHttp.init(OkHttpClient okHttpClient, boolean debug)

此步驟是非必須的,如需要添加攔截器等其他業(yè)務(wù)需求,則可調(diào)用init方法進(jìn)行初始化,不初始化或者傳入null即代表使用默認(rèn)OkHttpClient對象,建議在Application中初始化,默認(rèn)的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能最大化滿足自身的業(yè)務(wù);二來,隨著RxHttp版本的升級,默認(rèn)的OkHttpClient可能會發(fā)生變化(雖然可能性很小),故建議自定義OkHttpClient對象傳入RxHttp。

3.5、公共參數(shù)/請求頭

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

RxHttp.setOnParamAssembly(new Function() {
    @Override
    public Param apply(Param p) { //此方法在子線程中執(zhí)行,即請求發(fā)起線程
        Method method = p.getMethod();
        if (method.isGet()) {     //可根據(jù)請求類型添加不同的參數(shù)
        } else if (method.isPost()) {
        }
        return p.add("versionName", "1.0.0")//添加公共參數(shù)
                .addHeader("deviceType", "android"); //添加公共請求頭
    }
});

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

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

RxHttp.get("/service/...")       //get請求 
    .setAssemblyEnabled(false)   //設(shè)置是否添加公共參數(shù)/頭部,默認(rèn)為true    
    .asString()                  //返回字符串?dāng)?shù)據(jù)    
    .subscribe(s -> {            //這里的s為String類型
        //請求成功                                         
    }, throwable -> {                                  
        //請求失敗                                         
    });                                                

3.6、多域名/動態(tài)域名

3.6.1、多域名

現(xiàn)實(shí)開發(fā)中,我們經(jīng)常會遇到多個域名的情況,其中1個為默認(rèn)域名,其它為非默認(rèn)域名,對于這種情況,RxHttp提供了@DefaultDomain()、@Domain()這兩個注解來標(biāo)明默認(rèn)域名和非默認(rèn)域名,如下:

public class Url {
    @DefaultDomain() //設(shè)置為默認(rèn)域名
    public static String baseUrl = "https://www.wanandroid.com/"

    @Domain(name = "BaseUrlBaidu") //非默認(rèn)域名,并取別名為BaseUrlBaidu
    public static String baidu = "https://www.baidu.com/";

    @Domain(name = "BaseUrlGoogle") //非默認(rèn)域名,并取別名為BaseUrlGoogle
    public static String google = "https://www.google.com/";
}

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

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

//使用默認(rèn)域名,則無需添加任何額外代碼
//此時 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種指定域名的方式,按優(yōu)先級排名分別是:手動輸入域名 > 指定非默認(rèn)域名 > 使用默認(rèn)域名。

3.6.2、動態(tài)域名

現(xiàn)實(shí)開發(fā)中,也會有動態(tài)域名切換的需求,如域名被封、或者需要根據(jù)服務(wù)端下發(fā)的域名去配置,這對于RxHttp來說簡直就是 so easy !!! 我們只需要對BaseUrl重新賦值,此時發(fā)請求便會立即生效,如下:

//此時 url = "https://www.wanandroid.com/service/..."
RxHttp.get("/service/...")
    .asString()  
    .subscribe();

Url.baseUrl = "https://www.qq.com"; //動態(tài)更改默認(rèn)域名,改完立即生效,非默認(rèn)域名同理
//此時 url = "https://www.qq.com/service/..."
RxHttp.get("/service/...")
    .asString()  
    .subscribe();

3.7、關(guān)閉請求

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

3.7.1、自動關(guān)閉請求

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

//以下代碼均在FragmentActivty/Fragment中調(diào)用

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

//kotlin用戶,請使用life或lifeOnMain方法,如下:
RxHttp.postForm("/service/...")
    .asString()
    .life(this) //頁面銷毀、自動關(guān)閉請求
    .subscribe();
    //或者
RxHttp.postForm("/service/...")
    .asString()
    .lifeOnMain(this) //頁面銷毀、自動關(guān)閉請求 并且在主線程回調(diào)觀察者
    .subscribe();

上面的thisLifecycleOwner接口對象,我們的FragmentActivity/Fragment均實(shí)現(xiàn)了這個接口,所有我們在FragmentActivity/Fragment中可以直接傳this。
RxLife不了解的同學(xué)請查看RxLife 史上最優(yōu)雅的管理RxJava生命周期,這里不詳細(xì)講解。

3.7.2、手動關(guān)閉請求

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

//訂閱回調(diào),可以拿到Disposable對象
Disposable disposable = RxHttp.get("/service/...")
    .asString()  
    .subscribe(s -> { 
       //成功回調(diào)
    }, throwable -> {
       //失敗回調(diào)
    });

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

3.8、文件上傳/下載/進(jìn)度監(jiān)聽

RxHttp可以非常優(yōu)雅的實(shí)現(xiàn)上傳/下載及進(jìn)度的監(jiān)聽,是騾子是馬,拉出來溜溜

3.8.1上傳

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

RxHttp.postForm("/service/...") //發(fā)送Form表單形式的Post請求  
    .addFile("file1", new File("xxx/1.png"))  //添加單個文件      
    .addFile("fileList", new ArrayList<>())   //通過List對象,添加多個文件     
    .asString()                                      
    .subscribe(s -> {                              
        //上傳成功                                     
    }, throwable -> {                              
        //上傳失敗                                     
    });                                            

通過asUpload系列方法監(jiān)聽上傳進(jìn)度,如下:

RxHttp.postForm("/service/...") //發(fā)送Form表單形式的Post請求                                    
    .addFile("file1", new File("xxx/1.png"))                                         
    .addFile("file2", new File("xxx/2.png"))                                         
    .asUpload(progress -> {                                                          
        //上傳進(jìn)度回調(diào),0-100,僅在進(jìn)度有更新時才會回調(diào)                                                  
        int currentProgress = progress.getProgress(); //當(dāng)前進(jìn)度 0-100                   
        long currentSize = progress.getCurrentSize(); //當(dāng)前已上傳的字節(jié)大小                   
        long totalSize = progress.getTotalSize();     //要上傳的總字節(jié)大小                    
    }, AndroidSchedulers.mainThread())   //指定回調(diào)(進(jìn)度/成功/失敗)線程,不指定,默認(rèn)在請求所在線程回調(diào)                                           
    .subscribe(s -> {                                                                
        //上傳成功                                                                       
    }, throwable -> {                                                                
        //上傳失敗                                                                       
    });                                                                              

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

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 -> {
        //下載成功,回調(diào)文件下載路徑
    }, throwable -> {
        //下載失敗
    });

3.8.3、帶進(jìn)度下載

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

  //文件存儲路徑
String destPath = getExternalCacheDir() + "/" + System.currentTimeMillis() + ".apk";
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
    .asDownload(destPath, progress -> {
        //下載進(jìn)度回調(diào),0-100,僅在進(jìn)度有更新時才會回調(diào),最多回調(diào)101次,最后一次回調(diào)文件存儲路徑
        int currentProgress = progress.getProgress(); //當(dāng)前進(jìn)度 0-100
        long currentSize = progress.getCurrentSize(); //當(dāng)前已下載的字節(jié)大小
        long totalSize = progress.getTotalSize();     //要下載的總字節(jié)大小
    }, AndroidSchedulers.mainThread()) //指定主線程回調(diào)
    .subscribe(s -> {//s為String類型,這里為文件存儲路徑
        //下載完成,處理相關(guān)邏輯
    }, throwable -> {
        //下載失敗,處理相關(guān)邏輯
    });

3.8.4、斷點(diǎn)下載

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

String destPath = getExternalCacheDir() + "/" + "Miaobo.apk";
long length = new File(destPath).length(); //已下載的文件長度
RxHttp.get("http://update.9158.com/miaolive/Miaolive.apk")
    .setRangeHeader(length)  //設(shè)置開始下載位置,結(jié)束位置默認(rèn)為文件末尾
    .asDownload(destPath)
    .subscribe(s -> { //s為String類型
        //下載成功,處理相關(guān)邏輯
    }, throwable -> {
        //下載失敗,處理相關(guān)邏輯
    });

3.8.5、帶進(jìn)度斷點(diǎn)下載

帶進(jìn)度斷點(diǎn)下載相較于帶進(jìn)度下載僅需要調(diào)用setRangeHeader方法傳入開始及結(jié)束位置即可(結(jié)束位置不傳默認(rèn)為文件末尾),其它沒有任何差別

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

注:上面帶進(jìn)度斷點(diǎn)下載中,返回的進(jìn)度會從0開始,如果需要銜接上次下載的進(jìn)度,則調(diào)用asDownload(String,long,Consumer,Scheduler)方法傳入上次已經(jīng)下載好的長度(第二個參數(shù)),如下:

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

3.9、超時設(shè)置

3.9.1、設(shè)置全局超時

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

//設(shè)置讀、寫、連接超時時間為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、為單個請求設(shè)置超時

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

RxHttp.get("/service/...")
    .asString()
    .timeout(5, TimeUnit.SECONDS)//設(shè)置總超時時間為5s
    .as(RxLife.asOnMain(this))  //感知生命周期,并在主線程回調(diào)
    .subscribe(pageList -> {
        //成功回調(diào)
    }, (OnError) error -> {
        //失敗回調(diào)
    });

注:這里設(shè)置的總超時時間要小于全局讀、寫、連接超時時間之和,否則無效

3.10、設(shè)置Converter

3.10.1、設(shè)置全局Converter

IConverter converter = FastJsonConverter.create();
RxHttp.setConverter(converter)

3.10.2、為請求設(shè)置單獨(dú)的Converter

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

public class RxHttpManager {
    @Converter(name = "XmlConverter") //指定Converter名稱
    public static IConverter xmlConverter = XmlConverter.create();
}

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

RxHttp.get("/service/...")
    .setXmlConverter()   //指定使用XmlConverter,不指定,則使用全局的Converter
    .asObject(NewsDataXml.class)
    .as(RxLife.asOnMain(this))  //感知生命周期,并在主線程回調(diào)
    .subscribe(dataXml -> {
        //成功回調(diào)
    }, (OnError) error -> {
        //失敗回調(diào)
    });

3.11、請求加解密

3.11.1、加密

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

3.11.2、解密

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

//設(shè)置數(shù)據(jù)解密/解碼器                                               
RxHttp.setResultDecoder(new Function<String, String>() {
    //每次請求成功,都會回調(diào)這里,并傳入請求返回的密文   
    @Override                                              
    public String apply(String s) throws Exception {   
        String plaintext = decode(s);   //將密文解密成明文,解密邏輯自己實(shí)現(xiàn)
        return plaintext;    //返回明文                                   
    }                                                      
});                                                        

很簡單,通過RxHttp.setResultDecoder(Function&lt;String, String&gt;)靜態(tài)方法,傳入一個接口對象,此接口會在每次請求成功的時候被回調(diào),并傳入請求返回的密文,只需要將密文解密后返回即可。

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

RxHttp.get("/service/...")
    .setDecoderEnabled(false)  //設(shè)置本次請求不需要解密,默認(rèn)為true
    .asString()
    .subscribe(pageList -> {
        //成功回調(diào)
    }, (OnError) error -> {
        //失敗回調(diào)
    });

3.12、指定請求/回調(diào)線程

RxHttp默認(rèn)在Io線程執(zhí)行請求,也默認(rèn)在Io線程回調(diào),即默認(rèn)在同一Io線程執(zhí)行請求并回調(diào),當(dāng)然,我們也可以指定請求/回調(diào)所在線程。

3.12.1、指定請求所在線程

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

//指定請求所在線程,需要在第二部曲前任意位置調(diào)用,第二部曲后調(diào)用無效
RxHttp.get("/service/...")
    .subscribeOnCurrent() //指定在當(dāng)前線程執(zhí)行請求,即同步執(zhí)行,
    .asString()  
    .subscribe();

//其它subscribeXxx方法
subscribeOnIo()   //RxHttp默認(rèn)的請求線程
subscribeOnSingle()
subscribeOnNewThread()
subscribeOnComputation()
subscribeOnTrampoline()
subscribeOn(Scheduler) //自定義請求線程

以上使用的皆是RxJava的線程調(diào)度器,不熟悉的請自行查閱相關(guān)資料,這里不做詳細(xì)介紹。

3.12.2、指定回調(diào)所在線程

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

//指定回調(diào)所在線程,需要在第二部曲后調(diào)用
RxHttp.get("/service/...")
    .asString()  
    .observeOn(AndroidSchedulers.mainThread()) //指定在主線程回調(diào)
    .subscribe(s -> { //s為String類型,主線程回調(diào)
        //成功回調(diào)
    }, throwable -> {
        //失敗回調(diào)
    });

3.13、 Retrofit用戶

時常會有童鞋問我,我是Retrofit用戶,喜歡把接口寫在一個類里,然后可以直接調(diào)用,RxHttp如何實(shí)現(xiàn)?其實(shí),這個問題壓根就不是問題,在介紹第二部曲的時候,我們知道,使用asXxx方法后,就會返回Observable&lt;T&gt;對象,因此,我們就可以這樣實(shí)現(xiàn):

public class HttpWrapper {

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

//隨后在其它地方就可以直接調(diào)用
HttpWrapper.getStudent(1)
    .as(RxLife.asOnMain(this))  //主線程回調(diào),并在頁面銷毀自動關(guān)閉請求(如果還未關(guān)閉的話)
    .subscribe(students -> { //學(xué)生列表
        //成功回調(diào)
    }, throwable -> {
        //失敗回調(diào)
    });

很簡單,封裝的時候返回Observable&lt;T&gt;對象即可。

還有的同學(xué)問,我們獲取列表的接口,頁碼是和url拼接在一起的,Retrofit可以通過占位符,那RxHttp又如何實(shí)現(xiàn)?簡單,如下:

public class HttpWrapper {

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

    //多個占位符
    public static Observable<Student> getStudent(int page, int count) {
        return RxHttp.get("/service/%1$d/%2$d/...", page, count)  //使用標(biāo)準(zhǔn)的占位符協(xié)議
            .asObject(Student.class);
    }
}

這一點(diǎn)跟Retrofit不同,Retrofit是通過注解指定占位符的,而RxHttp是使用標(biāo)準(zhǔn)的占位符,我們只需要在url中聲明占位符,隨后在傳入url的后面,帶上對應(yīng)的參數(shù)即可。

4、原理剖析

4.1、工作流程

在RxHttp有4個重要的角色,分別是:

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

為此,我畫了一個流程圖,可以直觀的了解到RxHttp的大致工作流程
RxHttp  讓你眼前一亮的Http請求框架

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

4.2、Param

首先,附上一張Param類的繼承關(guān)系圖
RxHttp  讓你眼前一亮的Http請求框架
下面將從上往下對上圖中的類做個簡單的介紹:

  • IHeaders:接口類,里面定義了一系列addHeader方法
  • IParam:接口類,里面定義了add(String,Object)、addAll(Map)等方法,
  • IRequest:接口類,里面定義了一系列g(shù)etXxx方法,通過這些方法最終構(gòu)建一個Request對象
  • Param:接口類,是一個空接口,繼承了前面3個接口,里面有一系列靜態(tài)方法可以獲取到Param的具體實(shí)現(xiàn)類
  • AbstractParam:Param接口的抽象實(shí)現(xiàn)類,實(shí)現(xiàn)了Param接口的所有方法
  • IFile:接口類,里面定義了一系列addFile方法
  • IUploadLengthLimit:接口類,里面就定義了一個checkLength()方法,用于限制文件上傳大小
  • NoBodyParam:Param的具體實(shí)現(xiàn)類,Get、Head請求就是通過該類去實(shí)現(xiàn)的
  • JsonParam:Param的具體實(shí)現(xiàn)類,調(diào)用RxHttp.xxxJson(String)請求方法時,內(nèi)部就是通過該類去實(shí)現(xiàn)的
  • JsonArrayParam:Param的具體實(shí)現(xiàn)類,調(diào)用RxHttp.xxxJsonArray(String)請求方法時,內(nèi)部就是通過該類去實(shí)現(xiàn)的
  • FormParam:Param的具體實(shí)現(xiàn)類,同時又實(shí)現(xiàn)了IFile、IUploadLengthLimit兩個接口,調(diào)用RxHttp.xxxForm(String)請求方法時,內(nèi)部就是通過該類去實(shí)現(xiàn)的

4.3、HttpSender

HttpSender可以把它理解為請求發(fā)送者,里面聲明OkHttpClient對象和一系列靜態(tài)方法,我們來簡單看下:

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對象同步執(zhí)行一個請求
    public static Response execute(@NonNull Param param) throws IOException {
        return newCall(param).execute();
    }

    static Call newCall(Param param) throws IOException {
        return newCall(getOkHttpClient(), param);
    }
    //所有的請求,最終都會調(diào)此方法拿到Call對象,然后執(zhí)行請求
    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);
    }

    //省略了部分方法
}

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

4.4、Parser

先看下Parser繼承結(jié)構(gòu)圖
RxHttp  讓你眼前一亮的Http請求框架
這里對上圖中的類做個簡單的介紹

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

5、擴(kuò)展

5.1、自定義Parser

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

源碼永遠(yuǎn)是最好的學(xué)習(xí)方式,在學(xué)習(xí)自定義Parser前,我們不妨先看看內(nèi)置的Parser是如何實(shí)現(xiàn)的

SimPleParser

public class SimpleParser<T> extends AbstractParser<T> {

    //省略構(gòu)造方法
    @Override
    public T onParse(Response response) throws IOException {
        return convert(response, mType);
    }
}

可以看到,SimpleParser除了構(gòu)造方法,就剩一個onParser方法,該方法是在Parser接口中定義的,再來看看具體的實(shí)現(xiàn)convert(Response, Type),這個方法也是在Parser接口中定義的,并且有默認(rèn)的實(shí)現(xiàn),如下:

public interface Parser<T> {

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

    //對Http返回的結(jié)果,轉(zhuǎn)換成我們期望的實(shí)體類對象
    default <R> R convert(Response response, Type type) throws IOException {
        ResponseBody body = ExceptionHelper.throwIfFatal(response);  //這里內(nèi)部會判斷code<200||code>=300 時,拋出異常
        boolean onResultDecoder = isOnResultDecoder(response); //是否需要對返回的數(shù)據(jù)進(jìn)行解密
        LogUtil.log(response, onResultDecoder, null);
        IConverter converter = getConverter(response);        //取出轉(zhuǎn)換器
        return converter.convert(body, type, onResultDecoder); //對數(shù)據(jù)進(jìn)場轉(zhuǎn)換
    }
    //省略若干方法
}

可以看到,非常的簡單,輸入Response對象和泛型類型Type,內(nèi)部就通過IConverter接口轉(zhuǎn)換為我們期望的實(shí)體類對象并返回。

到這,我想大家應(yīng)該就多少有點(diǎn)明白了,自定義Parser,無非就是繼承AbstractParser,然后實(shí)現(xiàn)onParser方法即可,那我們來驗證一下,我們來看看內(nèi)置ListParser是不是這樣實(shí)現(xiàn)的,如下:

public class ListParser<T> extends AbstractParser<List<T>> {

    //省略構(gòu)造方法
    @Override
    public List<T> onParse(Response response) throws IOException {
        final Type type = ParameterizedTypeImpl.get(List.class, mType); //拿到泛型類型
        return convert(response, type);
    }
}

可以看到,跟SimpleParser解析器幾乎是一樣的實(shí)現(xiàn),不同是的,這里將我們輸入的泛型T與List組拼為一個新的泛型類型,最終返回List&lt;T&gt;對象。

現(xiàn)在,我們就可以來自定義Parser了,先來自定義ResponseParser,用來處理Response&lt;T&gt;數(shù)據(jù)類型,先看看數(shù)據(jù)結(jié)構(gòu):

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

    //省略構(gòu)造方法
    @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) {//這里假設(shè)code不等于0,代表數(shù)據(jù)不正確,拋出異常
            throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
        }
        return t;
    }
}

可以看到,非常的簡單,首先將我們輸入泛型和自定義的Response&lt;T&gt;類組拼成新的泛型類型,隨后通過convert(Response, Type)方法得到Response&lt;T&gt;對象,接著又對code及T做了判斷,如果不正確就拋出異常,最后返回T。

估計這里有人會問,我怎么用這個解析器呢?相信不少小伙伴以及發(fā)現(xiàn)了,我們在ResponseParser類名上面用了@Parser注解,只要用了該注解,就會在RxHttp自動生成asXxx(Class&lt;T&gt;)方法,其中Xxx就是我們在@Parser注解中為解析器取的別名,這里取別名為Response,所以便會在RxHttp類中自動生成asResponse(Class&lt;T&gt;)方法,如下:

  public <T> Observable<T> asResponse(Class<T> type) {
    return asParser(new ResponseParser(type));
  }

可以看到,該方法內(nèi)部又調(diào)用了asParser(Parser&lt;T&gt;)方法,并傳入了ResponseParser,因此,我們有兩種方式使用自定義的ResponseParser,如下:

//第一種方式,使用@parser注解生成的asResponse方法
RxHttp.postForm("/service/...")   //發(fā)送post表單請求
    .add("key", "value")          //添加參數(shù),可調(diào)用多次
    .asResponse(Student.class)    //返回Student類型
    .subscribe(student -> {   
        //請求成功,這里能拿到 Student對象               
    }, throwable -> {         
        //請求失敗                
    });   

//第二種方式,直接使用asParser(Parser<T>)方法
RxHttp.postForm("/service/...")   //發(fā)送post表單請求
    .add("key", "value")          //添加參數(shù),可調(diào)用多次
    .asParser(new ResponseParser<Student>(){})    //返回Student類型
    .subscribe(student -> {   
        //請求成功,這里能拿到 Student對象               
    }, throwable -> {         
        //請求失敗                
    });  

以上兩種方式,除了寫法上的區(qū)別,其它都一樣,用哪種,看個人喜好,但還是建議使用第一種方式,不僅寫法簡單,也降低了耦合。

這里最后再貼上ResponseListParser、ResponsePageListParser的源碼,原理都是一樣的,代碼實(shí)現(xiàn)也差不多,就不再詳解
ResponseListParser

@Parser(name = "ResponseList")
public class ResponseListParser<T> extends AbstractParser<List<T>> {

    //省略構(gòu)造方法
    @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,說明數(shù)據(jù)不正確,拋出異常
            throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
        }
        return list;
    }
}

ResponsePageListParser

@Parser(name = "ResponsePageList")
public class ResponsePageListParser<T> extends AbstractParser<PageList<T>> {

    //省略構(gòu)造方法
    @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,說明數(shù)據(jù)不正確,拋出異常
            throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
        }
        return pageList;
    }
}

5.2、自定義Param

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

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

這種情況,我們需要繼承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() {
        //這里拿到你添加的所有參數(shù)
        List<KeyValuePair> keyValuePairs = getKeyValuePairs();
        String encryptStr = "加密后的字符串";  //根據(jù)上面拿到的參數(shù),自行實(shí)現(xiàn)加密邏輯
        addHeader("encryptStr", encryptStr);
        return super.getRequestBody();
    }
}
5.2.2、postJson請求加密

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

@Param(methodName = "postEncryptJson")
public class PostEncryptJsonParam extends JsonParam {

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

    @Override
    public RequestBody getRequestBody() {
        //這里拿到你添加的所有參數(shù)
        Map<String, Object> params = getParams();
        String encryptStr = "加密后的字符串";  //根據(jù)上面拿到的參數(shù),自行實(shí)現(xiàn)解密邏輯
        return RequestBody.create(MEDIA_TYPE_JSON, encryptStr);  //發(fā)送加密后的字符串
    }
}
5.2.3、自定義API

我們繼承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) {
        //這里的業(yè)務(wù)邏輯自行實(shí)現(xiàn)
        return this;
    }

    public PostEncryptFormParam test1(String s, double b) {
        //這里的業(yè)務(wù)邏輯自行實(shí)現(xiàn)
        return this;
    }
}
5.2.4、使用自定義的Param

同樣的問題,我們怎么用這3個自定義的Param呢?我想大多數(shù)人在類名前發(fā)現(xiàn)類@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));
  }

發(fā)請求時,只需要調(diào)用對應(yīng)的方法就好,如:

//發(fā)送加密的postForm請求
RxHttp.postEncryptForm("/service/...")   
    .add("key", "value")          //添加參數(shù),可調(diào)用多次
    .asString()                  //返回String類型
    .subscribe(s-> {   
        //請求成功    
    }, throwable -> {         
        //請求失敗                
    });  

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

那我自定義的API如何調(diào)用呢,so easy!!!!,選擇對應(yīng)的請求方法后,就可以直接調(diào)用,如下:

//發(fā)送加密的postJson請求
RxHttp.postTestJson("/service/...")   
    .test(100L, 99.99F)          //調(diào)用自定義的API
    .test1("testKey", 88.88D)    //調(diào)用自定義的API
    .add("key", "value")         //添加參數(shù),可調(diào)用多次
    .asString()                  //返回String類型
    .subscribe(s-> {   
        //請求成功    
    }, throwable -> {         
        //請求失敗                
    });  

5.3、自定義Converter

RxHttp內(nèi)部默認(rèn)使用來GsonConverter,并且額外提供了4個Converter,如下:

//非必須 根據(jù)自己需求選擇Converter  RxHttp默認(rèn)內(nèi)置了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也無法保證滿足所有的業(yè)務(wù)需求,為此,我們可以選擇自定義Converter,自定義Converter需要繼承IConverter接口,如下:

public class TestConverter implements IConverter {

    /**
     * 請求成功后會被回調(diào)
     * @param body             ResponseBody
     * @param type             泛型類型
     * @param onResultDecoder  是否需要對結(jié)果進(jìn)行解碼/解密
     */
    @Override
    public <T> T convert(ResponseBody body, Type type, boolean onResultDecoder) throws IOException {
        //自行實(shí)現(xiàn)相關(guān)邏輯
        return null;
    }

    /**
     * json請求前會被回調(diào),需要自行根據(jù)泛型T創(chuàng)建RequestBody對象,并返回
     */
    @Override
    public <T> RequestBody convert(T value) throws IOException {
        //自行實(shí)現(xiàn)相關(guān)邏輯
        return null;
    }
}

以上兩個convert方法根據(jù)自身業(yè)務(wù)需求自行實(shí)現(xiàn),可以參考RxHttp提供FastJsonConverter、SimpleXmlConverter等Converter

5.3.2、怎么用Converter

請查看本文3.10章節(jié)----設(shè)置Converter

6、小技巧

在這教大家一個小技巧,由于使用RxHttp發(fā)送請求都遵循請求三部曲,故我們可以在android studio 設(shè)置代碼模版,如下RxHttp  讓你眼前一亮的Http請求框架
如圖設(shè)置好后,寫代碼時,輸入rp,就會自動生成模版,如下:
RxHttp  讓你眼前一亮的Http請求框架

7、小結(jié)

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

最后,喜歡的,請給本文點(diǎn)個贊,如果可以,還請給個star,創(chuàng)作不易,感激不盡。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI