1、前言
距离上一文RxHttp 让你眼前一亮的Http请求框架的发表,已过去两周,文章一经发表,RxHttp就收获了众多的好评,Github上Star数,也正式突破了1000,这对于我来说,是非常有动力的事情,感谢大家的支持,我再接再厉。
也有很多人跟说我,希望RxHttp能支持协程和缓存这两大功能,这不,非常好用的缓存功能就来了,看完本文,相信你会再一次爱上RxHttp。另外,协程也已经在路上了,这个需要多一点的时间,大家耐心等待。
如果你喜欢RxHttp,欢迎进QQ群交流:378530627 (RxHttp&RxLife交流群)
gradle依赖
1、必选
将jitpack
添加到项目的build.gradle
文件中,如下:
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
注:RxHttp 2.6.0版本起,已全面从JCenter迁移至jitpack
//使用kapt依赖rxhttp-compiler时必须
apply plugin: 'kotlin-kapt'
android {
//必须,java 8或更高
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
def rxhttp_version = '3.2.6'
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation "com.github.liujingxing.rxhttp:rxhttp:$rxhttp_version"
kapt "com.github.liujingxing.rxhttp:rxhttp-compiler:$rxhttp_version" //生成RxHttp类,纯Java项目,请使用annotationProcessor代替kapt
}
2、可选
android {
kapt {
arguments {
//依赖了RxJava时,rxhttp_rxjava参数为必须,传入RxJava版本号
arg("rxhttp_rxjava", "3.1.6")
arg("rxhttp_package", "rxhttp") //指定RxHttp类包名,非必须
}
}
//如果项目未集成kotlin,通过javaCompileOptions方法传参,在defaultConfig标签下
annotationProcessorOptions {
arguments = [
rxhttp_rxjava: '3.1.6',
rxhttp_package: 'rxhttp'
]
}
}
dependencies {
//rxjava2 (RxJava2/Rxjava3二选一,使用asXxx方法时必须)
implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'com.github.liujingxing.rxlife:rxlife-rxjava2:2.2.2' //管理RxJava2生命周期,页面销毁,关闭请求
//rxjava3
implementation 'io.reactivex.rxjava3:rxjava:3.1.6'
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'com.github.liujingxing.rxlife:rxlife-rxjava3:2.2.2' //管理RxJava3生命周期,页面销毁,关闭请求
//非必须,根据自己需求选择 RxHttp默认内置了GsonConverter
implementation "com.github.liujingxing.rxhttp:converter-serialization:$rxhttp_version"
implementation "com.github.liujingxing.rxhttp:converter-fastjson:$rxhttp_version"
implementation "com.github.liujingxing.rxhttp:converter-jackson:$rxhttp_version"
implementation "com.github.liujingxing.rxhttp:converter-moshi:$rxhttp_version"
implementation "com.github.liujingxing.rxhttp:converter-protobuf:$rxhttp_version"
implementation "com.github.liujingxing.rxhttp:converter-simplexml:$rxhttp_version"
}
2、设置全局缓存策略
通过RxHttpPlugins.setCache(File, long, CacheMode, long)
静态方法,设置全局缓存策略,如下:
public void initRxHttpCahce(Concent contet) {
//设置缓存目录为:Android/data/{app包名目录}/cache/RxHttpCache
File cacheDir = new File(context.getExternalCacheDir(), "RxHttpCache");
//设置最大缓存为10M,缓存有效时长为60秒
RxHttpPlugins.setCache(cacheDir, 10 * 1024 * 1024, CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE, 60 * 1000);
}
以上代码,通过setCache
方法,配置了以下4个参数:
cacheDir
:缓存存储目录,路径为:Android/data/{app包名目录}/cache/RxHttpCache
maxSize
:缓存最大Size,最大为10M,超过这个size,内部会根据LRU算法,将最少使用的缓存自动清除
CacheMode
:全局缓存模式,这里为,先请求网络,失败后,再读取缓存
cacheVaildTime
:全局缓存有效时长,为60秒
其中第三个参数,就是设置全局缓存模式。
RxHttp内部共提供了5种缓存模式,如下:
-
ONLY_NETWORK
该模式下,仅请求网络,不处理缓存;这是RxHttp默认的缓存模式
-
ONLY_CACHE
该模式下,仅读取缓存,读取成功,直接返回;否则直接抛出异常
-
NETWORK_SUCCESS_WRITE_CACHE
该模式下,直接请求网络,若请求成功,则写入缓存并返回;否则直接返回
-
READ_CACHE_FAILED_REQUEST_NETWORK
该模式下,先读取缓存,读取成功,直接返回;否则将请求网络(请求成功,写入缓存)
-
REQUEST_NETWORK_FAILED_READ_CACHE
该模式下,先请求网络,请求成功,写入缓存并返回;否则读取缓存
3、设置单个请求的缓存策略
通过Rxhttp#setCache(CacheMode)
为单个请求设置单独的缓存策略,如下:
RxHttp.get("/service/...")
.setCacheValidTime(10 * 1000) //当前请求缓存有效期为10秒
.setCache(CacheMode.READ_CACHE_FAILED_REQUEST_NETWORK) //当前请求先读取缓存,失败后,再请求网络
.toObservableString()
.subscribe(s -> {
//成功回调
}, throwable -> {
//失败回调
});
以上代码,为单个请求设置了单独的缓存策略,其中有效时长为10秒,缓存模式为先读取缓存,失败后,再请求网络。如果不设置,将使用全局的缓存策略。
注:设置单独的缓存模式/缓存有效时长前,一定要设置缓存存储目录和缓存最大Size,否则无效
4、关于CacheKey
CacheKey在RxHttp的缓存模块中,是一个很重要的角色,内部缓存的读写、删除都是通过CacheKey来操作的。对于不相同的请求,要保证CacheKey的唯一性
;对于相同的请求,要保证CacheKey的静态性
,这两个特性非常重要,一定要保证,否则缓存将变得毫无意义。
为啥这么说,请往下看。
4.1、内部CacheKey的生成规则
RxHttp内部会根据一定规则,为每个请求,都生成一个具有唯一性的CacheKey,规则如下:
NoBodyParam:
将添加的参数拼接在url后面,如:CacheKey = url?key=value&key1=value1... ,Get、Head类型请求遵循这个规则
FormParam:
同NoBodyParam,将添加的参数拼接在url后面,如:CacheKey = url?key=value&key1=value1... , xxxForm类型请求遵循这个规则
JsonParam:
将添加的参数转换成Json字符串jsonStr后,添加到url后面,如:CacheKey = url?json=jsonStr , xxxJson类型请求遵循这个规则
JsonArrayParam:
同JsonParam,将添加的参数转换成Json字符串jsonStr后,添加到url后面,如:CacheKey = url?json=jsonStr, xxxJsonArray类型请求遵循这个规则
以上4个规则,可在对应的Param类中getCacheKey()
方法查看
4.2、自定义CacheKey
如果以上规则不能满足你的业务需求,RxHttp还提供了两种自定义CacheKey的方式,如下:
1、通过RxHttp#setCacheKey(String)
方法,为单个请求指定CacheKey
RxHttp.get("/service/...")
.setCacheKey("自定义的CacheKey") //自定义CacheKey
.toObservableString()
.subscribe(s -> {
//成功回调
}, throwable -> {
//失败回调
});
2、通过自定义Param,并重写getCacheKey()方法,为某一类请求制定CacheKey的生成规则,如下:
@Param(methodName = "postEncryptForm")
public class PostEncryptFormParam extends FormParam {
public PostEncryptFormParam(String url) {
super(url, Method.POST);
}
@Override
public String getCacheKey() {
String cacheKey = null;
String simpleUrl = getSimpleUrl(); //拿到Url
Headers headers = getHeaders();//拿到添加的请求头
List<KeyValuePair> keyValuePairs = getQueryParam(); //拿到添加的参数
cacheKey = "根据url/请求头/参数,自定义规则,生成CacheKey";
return cacheKey;
}
}
注:自定义CacheKey,一定要保证CacheKey的唯一性,若重复,将覆盖已存在的缓存
4.3、唯一性
唯一性,就是要保证不同的请求都具有不同的CacheKey。若相同,就会造成缓存错乱
。
举个例子:
有A、B两个不同的请求,CacheKey、缓存模式均一样,缓存模式为READ_CACHE_FAILED_REQUEST_NETWORK
,即先读缓存,失败后,再请求网络。
此时,A先发请求,步骤为:读缓存失败—>请求网络—>请求成功,写入缓存—>结束;
随后B发起请求,步骤为:读缓存成功—>结束;
因为A和B的CacheKey是一样的,所以B读缓存时,读到的就是A的。而且,如果B的缓存模式为REQUEST_NETWORK_FAILED_READ_CACHE,此时,如果B请求成功,写缓存时,因为CacheKey一样,就会把A的缓存覆盖掉,下次A读取缓存时,读的就是B的缓存,这就是我说的缓存错乱
。
4.4、静态性
何为静态性?我对它的定义是:同一个请求,发送多次,要保证CacheKey是一致的,否则,缓存将毫无意义。
试想,我们有这样一种情况,每次发送请求,都要带上当前的时间戳,如下:
RxHttp.get("/service/...")
.add("currentTime", System.currentTimeMillis())
.setCache(CacheMode.READ_CACHE_FAILED_REQUEST_NETWORK) //当前请求先读取缓存,失败后,再请求网络
.toObservableString()
.subscribe(s -> {
//成功回调
}, throwable -> {
//失败回调
});
上面的代码,每次发起请求,时间戳都不一样,就导致内部每次生成的CacheKey也不一样,从而造成每次都会写入相同的缓存而不覆盖,更严重的是,每次读取缓存都会失败,那我们如何避免这种情况呢?有,前面介绍的,自定义Cachekey就能解决这个问题,那还有没有更简单的办法呢?也有,往下看。
剔除不参与CacheKey组拼的参数
我们可以调用RxHttpPlugins.setExcludeCacheKeys(String... keys)
方法,设置不参与CacheKey组拼的参数,即剔除这些参数。对于上面的问题,就可以这么做:
RxHttpPlugins.setExcludeCacheKeys("currentTime") //可变参数,可传入多个key
此时,发送请求,当前时间戳就不会参与CacheKey的组拼,从而保证了每次发起请求,CacheKey都是一样的,这就是我说的静态性
5、扩展
到这,也许有人会问我,我有这么一种业务场景:打开app,列表先展示缓存的数据,随后再请求网络,拉取最新的数据,用RxHttp如何实现?
对于这种场景,我们需要这样一种缓存模式,即:先读取缓存,不管成功与否,都继续请求网络。然而,RxHttp的5中缓存模式中,没有这样一种模式,怎么办?既然提出来了,肯定有办法,通过两种模式的组合来实现这个场景,如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
//发送一个仅读取缓存的请求
requestData(CacheMode.ONLY_CACHE);
//接着在发送一个请求网络,成功后写缓存的请求
requestData(CacheMode.NETWORK_SUCCESS_WRITE_CACHE);
}
//获取数据
public void requestData(CacheMode cacheMode) {
RxHttp.get("/service/...")
.setCacheMode(cacheMode)
.toObservableString()
.to(RxLife.toMain(this)) //感知生命周期,并在主线程回调
.subscribe(s -> {
//成功回调
if (cacheMode == CacheMode.ONLY_CACHE) {
//缓存读取成功回调
} else if (cacheMode == CacheMode.NETWORK_SUCCESS_WRITE_CACHE) {
//请求网络成功回调
}
}, throwable -> {
});
}
}
上面代码中,我们发送了两个请求,两个请求分别为CacheMode.ONLY_CACHE
、CacheMode.NETWORK_SUCCESS_WRITE_CACHE
这两种缓存模式,随后,在成功回调里,根据缓存模式的不同,执行不同的业务逻辑即可。
那RxHttp为什么不增加一种缓存模式,直接支持这种业务场景呢?原因有2个:
1、如果直接支持的话,就必须要增加一个缓存读取成功的回调,这样会破坏RxHttp现有的代码结构
2、个人感觉,组合的方式,比起增加一个缓存读取成功的回调,代码更加的简洁,学习成本更低
当然,对于直接支持,后续如果想到更好的方案,一定会加入,如果你有好方案,记得分享。
6、小结
其实,前面讲了这么做,RxHttp缓存功能的使用只需要2步的,
- 第一步,设置一个缓存存储目录和缓存最大Size,如果需要,还可以设置全局缓存模式和缓存有效时长
- 第二步,发请求时,设置对应的缓存模式即可,如果使用全局缓存模式,这一步还可以跳过
最后,喜欢的,请给本文点个赞,如果可以,还请给个star,创作不易,感激不尽。🙏🙏🙏
协程正在路上,需要多一点的时间,请耐心等待。。