说明

在资讯类应用中为了优化用户体验,一般会使用缓存数据。以下代码就是使用rxjava 同时获取网络和缓存数据。网络数据可以覆盖缓存数据,缓存数据无法覆盖网络数据。网络出错不会影响缓存的显示,缓存出错不会影响网络的显示。两则同时出错,则会显示网络错误。使用的时候也很简单。只需要一行代码。

支持只显示一次的请求。

RxDataUtil.getData(net, cache,false)

和网上已有的方法相比,这个方法更好理解。没用特别复杂的操作符。而且没用bug。
下面这篇文章中如果网络和缓存都出错,下游将接受不到事件,界面会一直处于loading中。

RxJava2 实战知识梳理(8) - 使用 publish + merge 优化先加载缓存,再读取网络数据的请求过程

源码

package com.trs.nmip.common.util;

import android.util.Log;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

public class RxDataUtil {

    public static void main() {
        //测试
        Observable<String> net = getNetData(2100, true);
        Observable<String> cache = getCacheData(2000, false);


        Disposable disposable = RxDataUtil.getData(net, cache, true)
                .subscribe(str -> {
                    Log.i("zzz", "UI 显示数据->" + str);
                }, e -> {
                    //显示网络错误
                    Log.i("zzz", "UI 显示错误->" + e.getMessage());
                    e.printStackTrace();
                });
    }

    /**
     * 同时获取网络和缓存数据,网络数据可以覆盖缓存数据,缓存无法覆盖网络
     * 某一个请求出错不会影响另一个
     * 如果两个都出错将显示网络的错误
     * showOnce 为ture的话将会使用网络或者缓存数据,因为是在最后使用take操作符截取了一次请求,
     * 所以不影响网络请求,效果是会从网络上更新数据到okhttp的缓存中。
     *
     * @param net      网络请求
     * @param cache    缓存请求
     * @param showOnce 是否只显示一次,如果为true的话,只会向下游发送一次数据。
     * @param <T>      数据的泛型
     * @return
     */
    public static <T> Observable<T> getData(Observable<T> net, Observable<T> cache, boolean showOnce) {
        Observable<T> observable = Observable.create(new ObservableOnSubscribe<T>() {
            Throwable[] throwables = new Throwable[2];
            int exceptionNumber = 0;
            Disposable disposableCache;
            CompositeDisposable compositeDisposable = new CompositeDisposable();
            Disposable disposableNet;
            int completedNumber = 0;

            @Override
            public void subscribe(ObservableEmitter<T> emitter) throws Exception {
                emitter.setDisposable(compositeDisposable);

                disposableCache = cache
                        .subscribe(t -> {
                                    if (!disposableCache.isDisposed()) {
                                        emitter.onNext(t);
                                    }
                                }, e -> reportError(0, e, emitter)
                                , () -> completed(emitter));
                compositeDisposable.add(disposableCache);
                disposableNet = net
                        .doOnNext(t -> disposableCache.dispose())//如果网络数据先发射,则取消缓存数据,避免缓存覆盖网络
                        .subscribe(t -> {
                                    if (!disposableNet.isDisposed()) {
                                        emitter.onNext(t);
                                    }
                                }
                                , e -> reportError(1, e, emitter),
                                () -> completed(emitter));
                compositeDisposable.add(disposableNet);
            }

            private synchronized void completed(ObservableEmitter<T> emitter) {
                completedNumber++;
                if (completedNumber == 2) {
                    emitter.onComplete();
                }
            }

            private synchronized void reportError(int index, Throwable e, ObservableEmitter<T> emitter) {
                throwables[index] = e;
                exceptionNumber++;
                if (exceptionNumber == 2) {
                    //表示缓存和网络请求都失败了,向下游发生网络错误
                    emitter.onError(throwables[1]);
                }


            }
        });
        return showOnce ? observable.take(1) : observable;
    }


    private static Observable<String> getCacheData(final long useTime, boolean success) {
        return Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> emitter) throws Exception {
                Log.i("zzz", "开始请求缓存数据");
                sleep(useTime);

                if (!success) {
                    emitter.onError(new RuntimeException("缓存错误"));
                    return;
                }
                Log.i("zzz", "开始发送缓存数据");
                emitter.onNext("缓存数据");
                Log.i("zzz", "缓存数据发送完成");
            }
        }).subscribeOn(Schedulers.io());
    }

    private static void sleep(long time) {
        long end = time + System.currentTimeMillis();
        while (true) {
            if (System.currentTimeMillis() > end) {
                break;
            }
        }
    }

    private static Observable<String> getNetData(long useTime, boolean success) {
        return Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> emitter) throws Exception {
                Log.i("zzz", "开始请求网络数据");
                sleep(useTime);
                if (!success) {
                    emitter.onError(new RuntimeException("网络错误"));
                    return;
                }

                Log.i("zzz", "开始发送网络数据");
                emitter.onNext("网络数据");
                Log.i("zzz", "网络数据发送完成");
            }
        }).subscribeOn(Schedulers.io());
    }
}

测试

1.网络先于缓存

网络比缓存快了0.1秒

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_java


结果:UI只显示了网络数据

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_网络数据_02

缓存快于网络

这是最正常的情况,测试参数如下

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_java_03


结果网络可以覆盖缓存数据

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_java_04

网络缓存同时出错

测试参数

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_缓存_05


结果正常

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_网络数据_06

网络出错不会影响缓存

测试参数

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_缓存_07


结果

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_网络数据_08

只显示一次

这种需求在一些不好刷新的地方使用。比如你的项目的一级栏目是通过服务器配置的。你希望同时获取缓存和网络,只要二者有其一就马上显示出来,这样可以减少用户等待的时间。但是你又不希望数据发射两次,因为两次的发射会造成界面大的变动,用户体验不好。但是又希望没有都有网络请求。可以更新缓存数据。下次进来就是最新的数据。

测试,网络和缓存都能成功的情况下,只显示最快的数据源。同时获取网络请求。这次让缓存快一些。

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_java_09


结果,当缓存比较快的时候显示了缓存。网络请求也发送了。如果使用了okhttp自带的缓存,那么你的缓存也就更新了。

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_网络数据_10

让网络更快

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_缓存_11

显示了网络数据,只显示了一次

rxjava合并多个返回值一样的网络请求 rxjava合并网络请求结果_网络请求_12