//订阅成功与失败回调
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、初始化
//设置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。
3.5、公共参数/请求头
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、多域名/动态域名
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”))
.addFile(“file2”, new File(“xxx/2.png”))
.upload(progress -> {
//上传进度回调,0-100,仅在进度有更新时才会回调
int currentProgress = progress.getProgress(); //当前进度 0-100
long currentSize = progress.getCurrentSize(); //当前已上传的字节大小
long totalSize = progress.getTotalSize(); //要上传的总字节大小
}, AndroidSchedulers.mainThread()) //指定回调(进度/成功/失败)线程,不指定,默认在请求所在线程回调
.asString()
.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开始,如果需要衔接上次下载的进度,则调用``setRangeHeader(long startIndex, long endIndex, boolean connectLastProgress)`方法第三个参数传入true即可,默认为false,如下:
String destPath = getExternalCacheDir() + “/” + “Miaobo.apk”;
long length = new File(destPath).length(); //已下载的文件长度
RxHttp.get(“http://update.9158.com/miaolive/Miaolive.apk”)
.setRangeHeader(length, true) //设置开始下载位置,结束位置默认为文件末尾
.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 -> {
//下载失败,处理相关逻辑
});
复制代码
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();
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、请求加解密
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(s -> {
//成功回调
}, (OnError) error -> {
//失败回调
});
复制代码
3.12、指定请求/回调线程
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 -> {
//失败回调
});
复制代码
3.13、 Retrofit用户
时常会有童鞋问我,我是Retrofit用户,喜欢把接口写在一个类里,然后可以直接调用,RxHttp如何实现?其实,这个问题压根就不是问题,在介绍第二部曲的时候,我们知道,使用asXxx方法后,就会返回Observable<T>
对象,因此,我们就可以这样实现:
public class HttpWrapper {
public static Observable<List> 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 getStudent(int page) {
return RxHttp.get(“/service/%d/…”, page) //使用标准的占位符协议
.asObject(Student.class);
}
//多个占位符
public static Observable getStudent(int page, int count) {
return RxHttp.get(“/service/%1
d
/
d/%2
d/d/…”, page, count) //使用标准的占位符协议
.asObject(Student.class);
}
}
复制代码
这一点跟Retrofit不同,Retrofit是通过注解指定占位符的,而RxHttp是使用标准的占位符,我们只需要在url中声明占位符,随后在传入url的后面,带上对应的参数即可。
4、原理剖析
4.1、工作流程
在RxHttp有4个重要的角色,分别是:
- Param:RxHttp类中所有添加的参数/请求头/文件都交由它处理,它最终目的就是为了构建一个Request对象
- HttpSender :它负责从Param对象中拿到Request对象,从而执行请求,最终返回Response对象
- Parser:它负责将HttpSender返回的Response对象,解析成我们期望的实体类对象,也就是泛型T
- RxHttp:它像一个管家,指挥前面3个角色做事情,当然,它也有自己的事情要做,比如:请求线程的调度,BaseUrl的处理、允许开发者自定义API等等
为此,我画了一个流程图,可以直观的了解到RxHttp的大致工作流程
我想应该很好理解,RxHttp要做的事情,就是把添加的参数/请求头等全部丢给Param处理,自己啥事也不敢;随后将Param交给HttpSender,让它去执行请求,执行完毕,返回Response对象;接着又将Response对象丢给Parser去做数据解析工作,并返回实体类对象T;最后,将T通过回调传给开发者,到此,一个请求就处理完成。
4.2、Param
首先,附上一张Param类的继承关系图
下面将从上往下对上图中的类做个简单的介绍:
- IHeaders:接口类,里面定义了一系列addHeader方法
- IParam:接口类,里面定义了add(String,Object)、addAll(Map)等方法,
- IRequest:接口类,里面定义了一系列getXxx方法,通过这些方法最终构建一个Request对象
- Param:接口类,是一个空接口,继承了前面3个接口,里面有一系列静态方法可以获取到Param的具体实现类
- AbstractParam:Param接口的抽象实现类,实现了Param接口的所有方法
- IFile:接口类,里面定义了一系列addFile方法
- IUploadLengthLimit:接口类,里面就定义了一个checkLength()方法,用于限制文件上传大小
- NoBodyParam:Param的具体实现类,Get、Head请求就是通过该类去实现的
- JsonParam:Param的具体实现类,调用RxHttp.xxxJson(String)请求方法时,内部就是通过该类去实现的
- JsonArrayParam:Param的具体实现类,调用RxHttp.xxxJsonArray(String)请求方法时,内部就是通过该类去实现的
- FormParam:Param的具体实现类,同时又实现了IFile、IUploadLengthLimit两个接口,调用RxHttp.xxxForm(String)请求方法时,内部就是通过该类去实现的
4.3、HttpSender
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对象,就可以发送一个请求。
4.4、Parser
先看下Parser继承结构图
这里对上图中的类做个简单的介绍
- Parser:接口类,里面定义了一个
T onParse(Response)
方法,输入Response对象,输出实体类对象T - AbstractParser:抽象类,里面没有任何具体实现,主要作用是在构造方法内获取泛型类型
- SimpleParser:是一个万能的解析器,可以解析任意数据结构,RxHttp内置的大部分asXxx方法,内部就是通过该解析器实现的
- ListParser:是一个列表解析器,可以解析任意列表数据,内置
asList(Class<T>)
方法,就是通过该解析器实现的 - MapParser:是一个Map解析器,可以解析任意Map数据类型,内置的asMap系列方法,就是通过该解析器实现的
- BitmapParser:是一个Bitmap解析器,通过该解析器可以获得一个Bitmap对象,asBitmap()方法内部就是通过该解析器实现的
- DownloadParser:文件下载解析器,用于文件下载,内置的一系列asDownload方法就是通过该解析器实现的
5、扩展
5.1、自定义Parser
前面第二部曲中,我们介绍了一系列asXxx方法,通过该系列方法可以很方便的指定数据返回类型,特别是自定义的asResponse(Class<T>)
、asResponseList(Class<T>)
、asResponsePageList(Class<T>)
这3个方法,将Reponse<T>
类型数据,处理的简直不要太完美,下面我们就来看看如何自定义Parser。
源码永远是最好的学习方式,在学习自定义Parser前,我们不妨先看看内置的Parser是如何实现的
SimPleParser
public class SimpleParser extends AbstractParser {
//省略构造方法
@Override
public T onParse(Response response) throws IOException {
return convert(response, mType);
}
}
复制代码
可以看到,SimpleParser除了构造方法,就剩一个onParser方法,该方法是在Parser接口中定义的,再来看看具体的实现convert(Response, Type)
,这个方法也是在Parser接口中定义的,并且有默认的实现,如下:
public interface Parser {
//输入Response 输出T
T onParse(@NonNull Response response) throws IOException;
//对Http返回的结果,转换成我们期望的实体类对象
default 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 extends AbstractParser<List> {
//省略构造方法
@Override
public List 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 {
private int code;
private String msg;
private T data;
//这里省略get、set方法
}
复制代码
自定义ResponseParser代码如下:
@Parser(name = “Response”, wrappers = {List.class, PageList.class})
public class ResponseParser extends AbstractParser {
//注意,以下两个构造方法是必须的
protected ResponseParser() { super(); }
public ResponseParser(Type type) { super(type); }
@Override
public T onParse(okhttp3.Response response) throws IOException {
final Type type = ParameterizedTypeImpl.get(Response.class, mType); //获取泛型类型
Response data = convert(response, type);
T t = data.getData(); //获取data字段
if (data.getCode() != 200 || t == null) {//这里假设code不等于200,代表数据不正确,抛出异常
throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
}
return t;
}
}
复制代码
以上代码,需要注意两个地方
第一,我们在类开头使用了@Parser
注解,该注解有两个参数,如下:
-
name
代表解析器的名称,这里取名为Response
,于是在RxHttp
类就会有asResponse(Class<T>)
方法(命名方式为:as +name
属性的值); -
wrappers
可以把他理解为泛型T
的包装类,需要传入Class[]
数组对象,这里我们传入了{List.class, PageList.class}
这两个类,于是就会生成asResponseList(Class<T>)
及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请求
}
@Override
public RequestBody getRequestBody() {
//这里拿到你添加的所有参数
List keyValuePairs = getKeyValuePairs();
String encryptStr = “加密后的字符串”; //根据上面拿到的参数,自行实现加密逻辑
addHeader(“encryptStr”, encryptStr);
return super.getRequestBody();
}
}
复制代码
5.2.2、postJson请求加密
这种情况,我们需要继承JsonParam,也重写getRequestBody()方法,如下:
@Param(methodName = “postEncryptJson”)
public class PostEncryptJsonParam extends JsonParam {
public PostEncryptJsonParam(String url) {
super(url, Method.POST);
}
@Override
public RequestBody getRequestBody() {
//这里拿到你添加的所有参数
Map<String, Object> params = getParams();
String encryptStr = “加密后的字符串”; //根据上面拿到的参数,自行实现解密逻辑
return RequestBody.create(MEDIA_TYPE_JSON, encryptStr); //发送加密后的字符串
}
}
复制代码
5.2.3、自定义API
我们继承FormParam,并新增两个test方法`,如下:
@Param(methodName = “postTestForm”)
public class PostTestFormParam extends FormParam {
public PostTestFormParam(String url) {
super(url, Method.POST);
}
public PostTestFormParam test(long a, float b) {
//这里的业务逻辑自行实现
return this;
}
public PostTestFormParam test1(String s, double b) {
//这里的业务逻辑自行实现
return this;
}
}
复制代码
5.2.4、使用自定义的Param
同样的问题,我们怎么用这3个自定义的Param呢?我想大多数人在类名前发现类@Param
注解,并为Param取了别名。那这个又有什么作用呢? 答案揭晓,只要在自定的Param上使用了@Param
注解,并取了别名,就会在RxHttp类自动生成一个跟别名一样的方法,在上面我们自定义了3个Param,并分别取别名为postEncryptForm、postEncryptJson、postTestForm,此时就会在RxHttp类中生成postEncryptForm(String)
、postEncryptJson(String)
、postTestForm(String)
这3个方法,我们在RxHttp这个类中来看下:
public static RxHttpKaTeX parse error: Expected '}', got 'EOF' at end of input: …turn new RxHttpPostEncryptFormParam(new PostEncryptFormParam(url));
}
public static RxHttpKaTeX parse error: Expected '}', got 'EOF' at end of input: …turn new RxHttpPostEncryptJsonParam(new PostEncryptJsonParam(url));
}
public static RxHttpKaTeX parse error: Expected '}', got 'EOF' at end of input: …turn new RxHttpPostTestFormParam(new PostTestFormParam(url));
}
复制代码
发请求时,只需要调用对应的方法就好,如:
//发送加密的postForm请求
RxHttp.postEncryptForm(“/service/…”)
.add(“key”, “value”) //添加参数,可调用多次
.asString() //返回String类型
.subscribe(s-> {
//请求成功
}, throwable -> {
//请求失败
});
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/1c3572cf4918c1c3dbb016fdd9da7c80.jpeg)
最后送福利了,现在关注我可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-xs4B6Tan-1713500065459)]
[外链图片转存中…(img-JPhbHyvH-1713500065460)]
[外链图片转存中…(img-nRDxVqwn-1713500065462)]
[外链图片转存中…(img-81DeROfg-1713500065463)]
[外链图片转存中…(img-dogF3T5E-1713500065465)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/1c3572cf4918c1c3dbb016fdd9da7c80.jpeg)
最后送福利了,现在关注我可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿
[外链图片转存中…(img-prQOGOxr-1713500065465)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!