专业版在前几个版本一直存在一个问题,就是在首页切换Tab的时候每次都会转圈圈,我们已经使用了Retrofit缓存,正常来说读取缓存数据应该很快。转圈圈给我们的感觉是读取缓存很慢,我们开始怀疑Retrofit读取缓存的问题。我们需找到了原因其实是RxJava切换线程的问题,请求缓存从io线程切换回主线程需要等待一定的时间。看一段我们的测试代码。
addSubscription(movieBoardUsecase.requestMainBoard(refresh)
.subscribe(boardTop -> {
time2 = (Formatter.getCurrentMillis() - time);
Log.e("timeretrofit", "" + time2);
}, throwable -> {
}));
time3 = Formatter.getCurrentMillis();
addSubscription(movieBoardUsecase.requestMainBoard(refresh)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(boardTop -> {
Log.e("timeRXthread", "" + (Formatter.getCurrentMillis() - time3 - time2));
getView().setData(boardTop);
}, throwable -> {
}));
第一段我们不切换线程,直接在主线程请求缓存内的数据,打出发出请求再到获取到数据的时间。
第二段我们正常的切换线程请求缓存,然后减去上面在主线程请求缓存的时间,就获取到切换线程所需要的时间。
我们在页面状态由加载状态变成展示状态的时间。
09-07 22:17:49.176 10745-10745/com.sankuai.moviepro E/timeretrofit: 4
09-07 22:17:49.206 10745-10745/com.sankuai.moviepro E/timeRXthread: 26
09-07 22:17:49.207 10745-10745/com.sankuai.moviepro E/status: 35
上面是在模拟器上运行的时间,可能感觉时间很短,模拟器运行速度较快,参考价值不大,但已经能看出切换线程占用了大部分的耗时。我们换用真机,才能说明问题。
09-08 10:24:42.539 26994-26994/com.sankuai.moviepro E/timeretrofit: 7
09-08 10:24:42.639 26994-26994/com.sankuai.moviepro E/timeRXthread: 91
09-08 10:24:42.644 26994-26994/com.sankuai.moviepro E/status: 112
09-08 10:25:25.676 26994-26994/com.sankuai.moviepro E/timeretrofit: 13
09-08 10:25:25.777 26994-26994/com.sankuai.moviepro E/timeRXthread: 88
09-08 10:25:25.779 26994-26994/com.sankuai.moviepro E/status: 116
09-08 10:25:51.392 26994-26994/com.sankuai.moviepro E/timeretrofit: 28
09-08 10:25:51.609 26994-26994/com.sankuai.moviepro E/timeRXthread: 189
09-08 10:25:51.611 26994-26994/com.sankuai.moviepro E/status: 247
三组数据整体时间不是很稳定,但我们已经可以清楚地看出耗时的并不是Retrofit读取缓存的过程,大部分时间都消耗在RxJava切换线程上。
既然我们已经找到了问题想想解决办法吧。我们可以直接想到的有两种方法,但行不行得通要去试试。
1.在请求之前判断是否存在Http缓存。
2.自己增加一层缓存,优先使用内存缓存,避开retrofit发请求。
第一种方法,我们要在发请求之前知道是否存在Http缓存,http缓存的位置是我们自己指定的,专业版的缓存目录是在应用目录下的cache/responses,下面有好多文件,文件名都是经过加密的,都是.0和.1的文件还有一个journal文件,关于这些文件查了一些资料,okhttp缓存浅析
.0的文件里面是header
.1文件里面是返回的具体内容,即json数据
journal文件里面保存的是每一条reponse记录状态。包括读取,删除,写入等动作。
文件名的加密方式是MD5,可以参考okHttp的Cache。
public static FilterInputStream getFromCache(String url) throws Exception {
File cacheDirectory = CacheUtils.getCacheDir(MovieProApplication.getContext(), "responses");
DiskLruCache cache = DiskLruCache.create(FileSystem.SYSTEM, cacheDirectory,
201105, 2, 10 * 1024 * 1024);
cache.flush();
String key = Util.md5Hex(url);
final DiskLruCache.Snapshot snapshot;
try {
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
return null;
}
okio.Source source = snapshot.getSource(1) ;
BufferedSource metadata = Okio.buffer(source);
FilterInputStream bodyIn = new FilterInputStream(metadata.inputStream()) {
@Override
public void close() throws IOException {
snapshot.close();
super.close();
}
};
return bodyIn ;
}
但是弄了半天我们有一个关键的问题,就是匹配缓存使用的是URL,但是我们在Presenter或者Fragment中并不能获取到URL,要修改的话,改动将会很大,所以这一种方法失败。
第二种是我们自己写内存缓存,还是存在问题,我们要把页面所有的数据缓存起来,使用一个Map,问题就是用什么作为获取缓存的匹配原则呢,正常也应该是通过URL,但是URL的方式行不通,我们只能拿到的是Observable,也无从匹配啊。后来我们重新思考了一下问题,我们当初是为了解决转圈圈的问问题,转圈圈的问题是从loaddata开始,在setData的是后结束。我们只需要避开这个过程,也就不会出现转圈圈的问题,但这只是一个比较差的解法,我们只缓存setData的数据,如果loaddata的时候数据不为空我们直接调用setData,但这样,如果页面有其他数据的话我们setData直接使用缓存,页面展示出来,但其他的请求数据还没有获取到,这样展示出来也会有问题,对于其他的请求我们只能自己写缓存,这样确实不是一个好的解决方法,更好的解决方法还在寻找。
网友评论