RxJava线程切换的问题


专业版在前几个版本一直存在一个问题,就是在首页切换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直接使用缓存,页面展示出来,但其他的请求数据还没有获取到,这样展示出来也会有问题,对于其他的请求我们只能自己写缓存,这样确实不是一个好的解决方法,更好的解决方法还在寻找。



本作品由 Tony Zhang 创作,采用 CC BY-NC-SA 3.0 许可协议 进行许可。

Comments