2024年鸿蒙最新RxHttp 让你眼前一亮的Http请求框架(1),面试下载

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

.subscribe(pageList -> {   
    //请求成功,这里能拿到PageList<Student>列表对象 
   int totalPage = pageList.getTotalPage();   //总页数
   List<Student> students = pageList.getData();  //单页列表数据        
}, throwable -> {         
    //请求失败                
});    

到这,估计很多人会问我:


* 你的code在哪里判断的?
* 我的code是100或者其它值才代表正确,怎么改?
* 我的`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`方法,如,`asInteger、asBoolean、asLong、asBitmap、asList、asMap`等等,它们最终都是通过`asParser(Parser<T>)`方法实现的,具体实现过程,这里先跳过,后续会详细讲解。


#### 3.3.3、第三部曲:订阅回调


这一步就很简单了,在第二部曲中,asXxx方法会返回`Observable<T>`对象,没错,就是RxJava内部的`Observable<T>`对象,此时我们便可通过`subscribe`系列方法订阅回调,如下:



//不处理任何回调
RxHttp.postForm(“/service/…”) //发送post表单请求
.add(“key”, “value”) //添加参数,可调用多次
.asResponseList(Student.class) //返回List类型
.subscribe(); //不订阅任何回调

//仅订阅成功回调
RxHttp.postForm(“/service/…”) //发送post表单请求
.add(“key”, “value”) //添加参数,可调用多次
.asResponseList(Student.class) //返回List类型
.subscribe(students -> {
//请求成功,这里能拿到List列表对象
});

//订阅成功与失败回调
RxHttp.postForm(“/service/…”) //发送post表单请求
.add(“key”, “value”) //添加参数,可调用多次
.asResponseList(Student.class) //返回List类型
.subscribe(students -> {
//请求成功,这里能拿到List列表对象
}, throwable -> {
//请求失败
});

//等等,省略


另外,我们还可以订阅请求开始/结束的回调,如下:



RxHttp.get(“/service/…”)
.asString()
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(disposable -> {
//请求开始,当前在主线程回调
})
.doFinally(() -> {
//请求结束,当前在主线程回调
})
.as(RxLife.as(this)) //感知生命周期
.subscribe(s -> {
//成功回调,当前在主线程回调
}, (OnError) error -> {
//失败回调,当前在主线程回调
});


到这,请求三部曲介绍完毕,接着,将介绍其它常用的功能


### 3.4、初始化



RxHttpPlugins.init(OkHttpClient) //自定义OkHttpClient对象
.setDebug(boolean) //是否开启调试模式,开启后,logcat过滤RxHttp,即可看到整个请求流程日志
.setCache(File, long, CacheMode) //配置缓存目录,最大size及缓存模式
.setExcludeCacheKeys(String…) //设置一些key,不参与cacheKey的组拼
.setResultDecoder(Function) //设置数据解密/解码器,非必须
.setConverter(IConverter) //设置全局的转换器,非必须
.setOnParamAssembly(Function); //设置公共参数/请求头回调


此步骤是非必须的,如需要添加拦截器等其他业务需求,则可调用`init`方法进行初始化,不初始化或者传入`null`即代表使用默认OkHttpClient对象,建议在Application中初始化,默认的OkHttpClient对象在RxHttpPlugins类中可以找到,如下:



//Default OkHttpClient object in RxHttp
private static OkHttpClient getDefaultOkHttpClient() {
SSLParams sslParams = HttpsUtils.getSslSocketFactory();
return new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager)
.hostnameVerifier((hostname, session) -> true)
.build();
}


虽然初始化是非必须的,但是建议大家传入自定义的OkHttpClient对象,一来,自定义的OkHttpClient能最大化满足自身的业务;二来,随着RxHttp版本的升级,默认的OkHttpClient可能会发生变化(虽然可能性很小),故建议自定义OkHttpClient对象传入RxHttp。


### 3.5、公共参数/请求头


RxHttp支持为所有的请求添加公共参数/请求头,如下:



RxHttpPlugins
.init(okHttpClient)
.setOnParamAssembly(new Function() {
@Override
public Param apply(Param p) { //此方法在UI线程执行,请勿执行耗时操作
Method method = p.getMethod();
if (method.isGet()) { //可根据请求类型添加不同的参数
} else if (method.isPost()) {
}
return p.add(“versionName”, “1.0.0”)//添加公共参数
.addHeader(“deviceType”, “android”); //添加公共请求头
}
});


我们需要在`RxHttp`初始化的地方,通过`setOnParamAssembly`方法设置公共参数接口回调,此时每次发起请求,都会回调该接口。


当然,如果希望某个请求不回调该接口,即不添加公共参数/请求头,则可以调用`setAssemblyEnabled(boolean)`方法,并传入false即可,如下:



RxHttp.get(“/service/…”) //get请求
.setAssemblyEnabled(false) //设置是否添加公共参数/头部,默认为true
.asString() //返回字符串数据
.subscribe(s -> { //这里的s为String类型
//请求成功
}, throwable -> {
//请求失败
});


### 3.6、多域名/动态域名


**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();


### 3.7、关闭请求


我们知道,在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(); //没有结束,则关闭请求
}


### 3.8、文件上传/下载/进度监听


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 -> {
//上传失败
});


通过upload系列方法监听上传进度,如下:



RxHttp.postForm(“/service/…”) //发送Form表单形式的Post请求
.addFile(“file1”, new File(“xxx/1.png”))
.addFiles(“file2”, new File(“xxx/2.png”))
.upload(AndroidSchedulers.mainThread(), progress -> {
//上传进度回调,0-100,仅在进度有更新时才会回调
int currentProgress = progress.getProgress(); //当前进度 0-100
long currentSize = progress.getCurrentSize(); //当前已上传的字节大小
long totalSize = progress.getTotalSize(); //要上传的总字节大小
})
.asString()
.subscribe(s -> {
//上传成功
}, throwable -> {
//上传失败
});


可以看到,跟上传的代码相比,我们仅仅增加了`upload`方法,第一个参数是指定回调的线程,这里我们指定了在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 -> {
//下载失败
});


如果你想监听下载进度,调用`asDownload(String, Scheduler, Consumer<Progress>)`方法,传入回调线程及回调接口即可,如下:



//文件存储路径
String destPath = getExternalCacheDir() + “/” + System.currentTimeMillis() + “.apk”;
RxHttp.get(“http://update.9158.com/miaolive/Miaolive.apk”)
.asDownload(destPath, AndroidSchedulers.mainThread(), progress -> { //第二个参数指定主线程回调
//下载进度回调,0-100,仅在进度有更新时才会回调,最多回调101次,最后一次回调文件存储路径
int currentProgress = progress.getProgress(); //当前进度 0-100
long currentSize = progress.getCurrentSize(); //当前已下载的字节大小
long totalSize = progress.getTotalSize(); //要下载的总字节大小
})
.subscribe(s -> {//s为String类型,这里为文件存储路径
//下载完成,处理相关逻辑
}, throwable -> {
//下载失败,处理相关逻辑
});


**3.8.3、断点下载**


`断点下载`相较于`下载`,仅需要调用`asAppendDownload`方法即可,其它没有任何差别



String destPath = getExternalCacheDir() + “/” + “test.apk”;
RxHttp.get(“http://update.9158.com/miaolive/Miaolive.apk”)
.asAppendDownload(destPath)
.subscribe(s -> { //s为String类型
//下载成功,处理相关逻辑
}, throwable -> {
//下载失败,处理相关逻辑
});


当然,此时你想监听下载进度,也是可以的,调用`asAppendDownload(String, Scheduler, Consumer<Progress>)`方法,传入回调线程及回调接口即可,如下:



String destPath = getExternalCacheDir() + “/” + “test.apk”;
RxHttp.get(“http://update.9158.com/miaolive/Miaolive.apk”)
.asAppendDownload(destPath, AndroidSchedulers.mainThread(), progress -> {
//下载进度回调,0-100,仅在进度有更新时才会回调
int currentProgress = progress.getProgress(); //当前进度 0-100
long currentSize = progress.getCurrentSize(); //当前已下载的字节大小
long totalSize = progress.getTotalSize(); //要下载的总字节大小
}) //指定主线程回调
.subscribe(s -> { //s为String类型
//下载成功,处理相关逻辑
}, throwable -> {
//下载失败,处理相关逻辑
});


### 3.9、超时设置


**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(s -> {
//成功回调
}, (OnError) error -> {
//失败回调
});


**注:这里设置的总超时时间要小于全局读、写、连接超时时间之和,否则无效**


### 3.10、设置Converter


**3.10.1、设置全局Converter**



IConverter converter = FastJsonConverter.create();
RxHttpPlugins.init(OkHttpClient)
.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
.asClass(NewsDataXml.class)
.as(RxLife.asOnMain(this)) //感知生命周期,并在主线程回调
.subscribe(dataXml -> {
//成功回调
}, (OnError) error -> {
//失败回调
});


### 3.11、请求加解密


**3.11.1、加密**


请求加密,需要自定义Param,非常简单,详情请查看本文5.2章节----自定义Param


**3.11.2、解密**


有些时候,请求会返回一大串的密文,此时就需要将密文转化为明文,直接来看代码,如下:



//设置数据解密/解码器
RxHttpPlugins.init(OkHttpClient)
.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(s -> {
//成功回调
}, (OnError) error -> {
//失败回调
});


### 3.12、同步请求/指定回调线程


RxHttp默认在Io线程执行请求,也默认在Io线程回调,即默认在同一Io线程执行请求并回调,当然,我们也可以指定请求/回调所在线程。


**3.12.1、同步请求**


RxHttp默认在IO线程发起请求,即异步请求,如果需要同步请求,调用`setSync`方法即可,如下:



//指定请求所在线程,需要在第二部曲前任意位置调用,第二部曲后调用无效
RxHttp.get(“/service/…”)
.setSync() //同步执行,
.asString()
.subscribe();


以上使用的皆是RxJava的线程调度器,不熟悉的请自行查阅相关资料,这里不做详细介绍。


**3.12.2、指定回调所在线程**


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



//指定回调所在线程,需要在第二部曲后调用
RxHttp.get(“/service/…”)
.asString()
.observeOn(AndroidSchedulers.mainThread()) //指定在主线程回调
.subscribe(s -> { //s为String类型,主线程回调
//成功回调
}, throwable -> {
//失败回调
});


### 3.13、 Retrofit用户


时常会有童鞋问我,我是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)  //使用标准的占位符协议
        .asClass(Student.class);
}

//多个占位符
public static Observable<Student> getStudent(int page, int count) {
    return RxHttp.get("/service/%1$d/%2$d/...", page, count)  //使用标准的占位符协议
        .asClass(Student.class);
}

}


这一点跟Retrofit不同,Retrofit是通过注解指定占位符的,而RxHttp是使用标准的占位符,我们只需要在url中声明占位符,随后在传入url的后面,带上对应的参数即可。


## 4、原理剖析


RxHttp使用到当下流行的注解处理器工具(Annotation Processing Tool,以下简称APT),像知名的**Eventbus**、**ButterKnife**、**Dagger2**、**Glide**以及**Jetpack**库里非常好用**Room**数据库框架,都使用到了APT,它能够在编译时检索注解信息,通过**Javapoet**框架生成Java类、方法等相关代码(想生成Kotlin相关代码,使用**kotlinpoet**),并因此在运行时做到零性能损耗。


那么,APT给RxHttp带来了哪些优势?RxHttp又是如何使用APT的?继续往下看


说起APT,大家脑海里第一个想到的可能是解耦,没错,解耦是它的一大优势,其实它还有一个更大有优势,那就是根据配置,生成不同的代码逻辑;比如在RxHttp中,默认是不依赖RxJava的,但是如果你需要使用`RxHttp + RxJava`方式发送请求,就可以在`annotationProcessorOptions`标签中的`rxhttp_rxjava`参数来配置RxJava大版本,可传入`RxJava2`或`RxJava3`,内部根据传入的RxJava版本,生成不同的代码,这样就可做到一套代码同时兼通`RxJava2`和`RxJava3`,如果后续出了`RxJava4`、`RxJava5`等新版本,一样可以兼容,而且非常简单。


在`RxHttp v2.4.2`以下版本中,对`OkHttp`的兼容,也使用了该方式去适配okhttp 各个版本,为此,RxHttp适配了OkHttp `v3.12.0`到`v4.9.0`(截止2020/12/27最新版本)中的任一版本(v4.3.0除外,该版本有一个bug,导致无法适配),因此,使用`RxHttp`,完全不用担心okhttp版本冲突问题。


同时兼容`RxJava`、`OkHttp`不同版本,这就是APT带给RxHttp的第一大优势。


RxHttp是如何使用APT?在RxHttp中,一共定义了6个注解,如下:


* **@DefaultDomain**:用它来指定默认的baseUrl,只能使用一次
* **@Domain**:指定非默认的baseUrl,可使用多次
* **@Parser**: 指定自定义的解析器,可使用多次,这个非常强大,可在解析器里写自己数据解析逻辑,并返回任意类型的数据,完美解决服务端返回的数据不规范问题
* **@Param**:指定自定义的Param,可使用多次,发送统一加密请求时用到
* **@OkClient**:为不同请求配置不同的OkHttpClient对象,可多次使用
* **@Converter**:为不同请求配置不同的Converter对象,可多次使用


***注:以上6个注解的具体使用方式,请查看[RxHttp 注解使用]( )***


RxHttp的注解处理器是`rxhttp-compiler`,它首要任务就是生成RxHttp类,其次就是检索以上6个注解,生成对应的类及方法,这就使得,无论我们如何去自定义,写请求代码时,始终遵循请求三部曲,如我们要发送统一加密的请求,就可以直接使用`@Param`注解生成的方法,如下:



//发送加密的post表单请求,方法名可通过@Param注解随意指定
val student = RxHttp.postEncryptForm(“/service/…”)
.add(“key”, “value”)
.toClass()
.await()


其它5个注解带来的优势就不一一介绍了,总之就是另一大优势,**解耦**,使得任意请求,皆遵循请求三部曲


***RxHttp工作流程***


接下来,讲讲RxHttp的工作流程,有5个重要的角色,分别是:


* **RxHttp**:这是最重要的一个角色,所以请求的唯一入口,内部持有一个Param对象,它的职责是,请求参数/请求头/BaseUrl的处理,请求线程的调度,提供注解生成的方法等等,最终的使命就是通过Param构建一个`okhttp3.Request`对象,随后在构建一个`okhttp3.Call`对象,并把Call对象丢给`Observable`或`IAwait`,然后由`Observable`或`IAwait`真正的去执行请求
* **Param**:它的职责是处理请求参数/请求头/url等一切用来构建`okhttp3.Request`需要的东西,最终使命就是构建`okhttp3.Request`对象,它被RxHttp类所持有,RxHttp把构建`okhttp3.Request`对象所需要的东西,都交给Param去实现的
* **IAwiat**:结合协程发请求时,真正执行网络请求的对象,具体实现类为`AwaitImpl`,它内部持有`Parser`对象,请求返回后,将`okhttp3.Response`丢给`Parser`去解析,并返回解析后的对象
* **Observable**:结合RxJava发送请求时,真正执行网络请求的对象,具体实现类有`ObservableCallExecute`、`ObservableCallEnqueue`、`ObservableParser`,分别用于同步请求、异步请求及请求返回后对`okhttp3.Response`对象的数据解析,`ObservableParser`内部持有`Parser`对象,具体的解析工作都交给`Parser`
* **Parser**:负责数据解析工作,将数据解析成我们想要的数据类型,这是一个接口对象,内部只有`onParse(response: Response): T`这一个方法,具体实现类有4个: `SimpleParser`、`StreamParser`、`SuspendStreamParser`、`BitmapParser`,第一个为万能的解析器,内部的`asClass/toClss`方法,就是通过它去实现的;第二第三是下载文件时用的的解析器,区别前者是结合RxJava下载的,后者是结合协程下载的;最后一个是解析`Bitmap`对象用的,`asBitmap/toBitmap`就是通过它去实现的


工作流程图如下:


![](https://img-blog.csdnimg.cn/img_convert/fc53d08879563e664fddaa888db22bda.png)


## 5、扩展


### 5.1、自定义Parser


前面第二部曲中,我们介绍了一系列asXxx方法,通过该系列方法可以很方便的指定数据返回类型,特别是自定义的`asResponse(Class<T>)`、`asResponseList(Class<T>)`、`asResponsePageList(Class<T>)`这3个方法,将`Reponse<T>`类型数据,处理的简直不要太完美,下面我们就来看看如何自定义Parser。


源码永远是最好的学习方式,在学习自定义Parser前,我们不妨先看看内置的Parser是如何实现的


**SimPleParser**



open class SimpleParser : TypeParser {
protected constructor() : super()
constructor(type: Type) : super(type)

@Throws(IOException::class)
override fun onParse(response: Response): T {
    return response.convert(types[0])
}

}


其中,`convert(Type)`是`okhttp3.Response`的扩展方法,定义如下:



@Throws(IOException::class)
fun Response.convert(type: Type): R {
val body = ExceptionHelper.throwIfFatal(this)
val needDecodeResult = OkHttpCompat.needDecodeResult(this)
LogUtil.log(this, null)
val converter = OkHttpCompat.getConverter(this)
return converter!!.convert(body, type, needDecodeResult)
}


该方法的目的就是把服务器返回的数据,转换为我们期望的实体类对象。


到这,我想大家应该就多少有点明白了,自定义Parser,无非就是继承`TypeParser`,然后实现onParser方法即可,现在,我们来自定义ResponseParser,用来处理`Response<T>`数据类型,先看看数据结构:



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


自定义ResponseParser代码如下:



@Parser(name = “Response”, wrappers = [PageList::class])
open class ResponseParser : TypeParser {
//注意,以下两个构造方法是必须的
protected constructor() : super()
constructor(type: Type) : super(type)

@Throws(IOException::class)
override fun onParse(response: okhttp3.Response): T {
    val data: Response<T> = response.convertTo(Response::class, *types)
    val t = data.data //获取data字段
    if (data.code != 200 || t == null) {
        throw ParseException(data.code.toString(), data.msg, response)
    }
    return t
}

}


以上代码,需要注意两个地方


第一,我们在类开头使用了`@Parser`注解,该注解有两个参数,如下:


* `name` 代表解析器的名称,这里取名为`Response`,于是在`RxHttp`类就会有`asResponse(Class<T>)`方法(命名方式为:as + `name`属性的值);
* `wrappers` 可以把他理解为泛型`T`的包装类,需要传入`Class[]`数组对象,这里我们传入了`{PageList.class}`这两个类,于是就会生成 `asResponsePageList(Class<T>)`方法。(命名方式为:as +`name`属性的值+`Class`类名)


`注:PageList类需要自己定义,用于加载分页数据,Demo里有这个类`


第二,我们在`if`语句里,对code做了判断,非200或者data为空时,就抛出异常,并带上了code及msg字段,所以我们在异常回调的地方就能拿到这两个字段


此时,我们就可以通过这3个方法,直接拿到`T`、`List<T>`、`PageList<T>`类型数据,并且对code做了统一的判断,直接来看看如何使用这个解析器



//第一种方式,使用@parser注解生成的asResponse方法
RxHttp.postForm(“/service/…”) //发送post表单请求
.add(“key”, “value”) //添加参数,可调用多次
.asResponse(Student.class) //返回Student类型
//.asResponseList(Student.class) //返回List类型
//.asResponsePageList(Student.class) //返回PageList类型
.subscribe(student -> {
//请求成功,这里能拿到 Student对象
}, throwable -> {
//请求失败
});

//第二种方式,直接使用asParser(Parser)方法
RxHttp.postForm(“/service/…”) //发送post表单请求
.add(“key”, “value”) //添加参数,可调用多次
.asParser(new ResponseParser(){}) //返回Student类型
.subscribe(student -> {
//请求成功,这里能拿到 Student对象
}, throwable -> {
//请求失败
});


以上两种方式,除了写法上的区别,其它都一样,相信大家都会选择第一种方式,不仅写法简单,还降低了耦合。


### 5.2、自定义Param


自定义Param,想较于自定义Parser,要更加的简单,我们只需根据自己的需求,继承NoBodyParam、FormParam、JsonParam等,增加或者重写方法即可,比如我们有以下3种情况,需要自定义Param,如下:


* postForm请求,需要将所有添加的参数,拼接在一起,随后加密,最后将加密的字符串添加到请求头中
* postJson请求,需要将所有的参数,也就是json字符串加密后再发送出去
* 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请求

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

//请求失败
});


以上两种方式,除了写法上的区别,其它都一样,相信大家都会选择第一种方式,不仅写法简单,还降低了耦合。


### 5.2、自定义Param


自定义Param,想较于自定义Parser,要更加的简单,我们只需根据自己的需求,继承NoBodyParam、FormParam、JsonParam等,增加或者重写方法即可,比如我们有以下3种情况,需要自定义Param,如下:


* postForm请求,需要将所有添加的参数,拼接在一起,随后加密,最后将加密的字符串添加到请求头中
* postJson请求,需要将所有的参数,也就是json字符串加密后再发送出去
* 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请求

[外链图片转存中…(img-BiRM9Wzd-1715743917487)]
[外链图片转存中…(img-8d5wFu6Y-1715743917488)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值