Android中使用RxJava实现网络请求与缓存策略

前言

在现代Android开发中,网络请求是应用不可或缺的一部分,而良好的缓存策略可以显著提升用户体验。RxJava作为一种响应式编程框架,能够优雅地处理异步操作和事件流。本文将介绍如何结合RxJava实现网络请求与缓存策略。

一、RxJava基础回顾

RxJava的核心概念包括:

  • Observable:被观察者,数据源

  • Observer:观察者,接收数据

  • Operator:操作符,用于转换、过滤、组合数据流

  • Scheduler:调度器,控制线程切换

java

复制代码
Observable.just("Hello RxJava")
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(text -> Log.d("RxJava", text));

二、网络请求实现

1. 使用Retrofit+RxJava进行网络请求

首先添加依赖:

gradle

复制代码
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

定义API接口:

java

复制代码
public interface ApiService {
    @GET("user/{id}")
    Observable<User> getUser(@Path("id") String userId);
}

创建Retrofit实例:

java

复制代码
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
    .build();

ApiService apiService = retrofit.create(ApiService.class);

2. 发起网络请求

java

复制代码
apiService.getUser("123")
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<User>() {
        @Override
        public void onSubscribe(Disposable d) {
            // 可以在这里显示加载进度条
        }

        @Override
        public void onNext(User user) {
            // 处理返回的用户数据
        }

        @Override
        public void onError(Throwable e) {
            // 处理错误
        }

        @Override
        public void onComplete() {
            // 请求完成
        }
    });

三、缓存策略实现

1. 内存缓存 + 磁盘缓存 + 网络请求

我们可以实现一个三级缓存策略:

  1. 首先检查内存缓存

  2. 如果没有,检查磁盘缓存

  3. 如果还是没有,发起网络请求

  4. 将网络请求结果缓存到内存和磁盘

2. 实现缓存管理器

java

复制代码
public class CacheManager {
    private static final String TAG = "CacheManager";
    private static CacheManager instance;
    private LruCache<String, Object> memoryCache;
    private File cacheDir;

    private CacheManager(Context context) {
        // 初始化内存缓存 (这里设置为应用最大内存的1/8)
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final int cacheSize = maxMemory / 8;
        memoryCache = new LruCache<String, Object>(cacheSize) {
            @Override
            protected int sizeOf(String key, Object value) {
                // 计算每个缓存对象的大小
                return value.toString().length() / 1024;
            }
        };
        
        // 初始化磁盘缓存目录
        cacheDir = new File(context.getCacheDir(), "network_cache");
        if (!cacheDir.exists()) {
            cacheDir.mkdirs();
        }
    }

    public static synchronized CacheManager getInstance(Context context) {
        if (instance == null) {
            instance = new CacheManager(context);
        }
        return instance;
    }

    // 内存缓存操作
    public synchronized void putToMemory(String key, Object value) {
        if (memoryCache.get(key) == null && value != null) {
            memoryCache.put(key, value);
        }
    }

    public synchronized Object getFromMemory(String key) {
        return memoryCache.get(key);
    }

    // 磁盘缓存操作
    public void putToDisk(String key, Object value) {
        File file = new File(cacheDir, key);
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(value);
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public Object getFromDisk(String key) {
        File file = new File(cacheDir, key);
        if (!file.exists()) return null;
        
        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
            Object value = ois.readObject();
            ois.close();
            return value;
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 清除缓存
    public void clearMemoryCache() {
        memoryCache.evictAll();
    }

    public void clearDiskCache() {
        File[] files = cacheDir.listFiles();
        if (files != null) {
            for (File file : files) {
                file.delete();
            }
        }
    }
}

3. 结合RxJava实现缓存策略

java

复制代码
public Observable<User> getUserWithCache(String userId) {
    return Observable.concat(
            getFromMemory(userId),
            getFromDisk(userId),
            getFromNetwork(userId))
        .filter(user -> user != null)
        .firstElement()
        .toObservable();
}

private Observable<User> getFromMemory(String userId) {
    return Observable.create(emitter -> {
        User user = (User) CacheManager.getInstance(context).getFromMemory(userId);
        if (user != null) {
            emitter.onNext(user);
        }
        emitter.onComplete();
    });
}

private Observable<User> getFromDisk(String userId) {
    return Observable.create(emitter -> {
        User user = (User) CacheManager.getInstance(context).getFromDisk(userId);
        if (user != null) {
            // 放入内存缓存
            CacheManager.getInstance(context).putToMemory(userId, user);
            emitter.onNext(user);
        }
        emitter.onComplete();
    }).subscribeOn(Schedulers.io());
}

private Observable<User> getFromNetwork(String userId) {
    return apiService.getUser(userId)
        .doOnNext(user -> {
            // 保存到内存和磁盘
            CacheManager.getInstance(context).putToMemory(userId, user);
            CacheManager.getInstance(context).putToDisk(userId, user);
        });
}

4. 使用示例

java

复制代码
getUserWithCache("123")
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(user -> {
        // 更新UI
    }, throwable -> {
        // 处理错误
    });

四、高级缓存策略

1. 缓存过期策略

java

复制代码
public class CacheItem<T> implements Serializable {
    private T data;
    private long timestamp;
    
    public CacheItem(T data) {
        this.data = data;
        this.timestamp = System.currentTimeMillis();
    }
    
    public boolean isExpired(long expiryTime) {
        return (System.currentTimeMillis() - timestamp) > expiryTime;
    }
    
    public T getData() {
        return data;
    }
}

// 修改getFromDisk方法
public Observable<User> getFromDisk(String userId) {
    return Observable.create(emitter -> {
        CacheItem<User> cacheItem = (CacheItem<User>) CacheManager.getInstance(context)
            .getFromDisk(userId);
            
        if (cacheItem != null) {
            if (!cacheItem.isExpired(TimeUnit.MINUTES.toMillis(10))) { // 10分钟过期
                User user = cacheItem.getData();
                CacheManager.getInstance(context).putToMemory(userId, user);
                emitter.onNext(user);
            } else {
                // 缓存过期,删除磁盘缓存
                CacheManager.getInstance(context).removeFromDisk(userId);
            }
        }
        emitter.onComplete();
    }).subscribeOn(Schedulers.io());
}

2. 先显示缓存再更新网络数据

java

复制代码
public Observable<User> getUserWithCacheFirst(String userId) {
    Observable<User> memory = getFromMemory(userId);
    Observable<User> disk = getFromDisk(userId);
    Observable<User> network = getFromNetwork(userId)
        .doOnNext(user -> {
            // 保存到内存和磁盘
            CacheManager.getInstance(context).putToMemory(userId, user);
            CacheManager.getInstance(context).putToDisk(userId, user);
        });
    
    return Observable.concat(memory, disk)
        .filter(user -> user != null)
        .take(1)
        .concatWith(network)
        .distinctUntilChanged();
}

五、错误处理与重试机制

java

复制代码
getUserWithCache("123")
    .retryWhen(throwableObservable -> throwableObservable
        .zipWith(Observable.range(1, 3), (throwable, retryCount) -> {
            if (retryCount < 3 && throwable instanceof IOException) {
                return retryCount;
            }
            throw Exceptions.propagate(throwable);
        })
        .flatMap(retryCount -> Observable.timer((long) Math.pow(2, retryCount), TimeUnit.SECONDS))
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(user -> {
        // 更新UI
    }, throwable -> {
        // 处理错误
    });

六、总结

通过RxJava实现网络请求与缓存策略,我们可以获得以下优势:

  1. 响应式编程:代码更加简洁、可读性更强

  2. 线程管理:轻松处理线程切换

  3. 灵活组合:可以方便地组合多个数据源

  4. 错误处理:提供强大的错误处理机制

  5. 可扩展性:便于添加新的功能,如缓存过期、重试机制等

在实际项目中,可以根据具体需求调整缓存策略,例如:

  • 对实时性要求高的数据可以缩短缓存时间

  • 对不常变化的数据可以延长缓存时间

  • 对重要数据可以实现多级缓存

希望本文能帮助你在Android开发中更好地使用RxJava处理网络请求与缓存策略。

相关推荐
尚久龙11 小时前
安卓学习 之 用户登录界面的简单实现
android·运维·服务器·学习·手机·android studio·安卓
Modu_MrLiu11 小时前
Android实战进阶 - 启动页
android·实战进阶·启动页·倒计时场景
yb0os111 小时前
RPC实战和核心原理学习(一)----基础
java·开发语言·网络·数据结构·学习·计算机·rpc
乱飞的秋天12 小时前
网络编程学习
网络·学习·php
Yuki’12 小时前
网络编程基础
网络
出门吃三碗饭12 小时前
编译器构造:从零手写汇编与反汇编程序(一)
android·汇编
Just_Paranoid12 小时前
【WorkManager】无法在 Direct Boot 模式下初始化
android·jetpack·usermanager·workmanager·directboot
前端小超超12 小时前
如何配置capacitor 打包的安卓app固定竖屏展示?
android·前端·gitee
顾林海13 小时前
探秘Android JVM TI:虚拟机背后的"隐形管家"
android·面试·性能优化
青铜发条13 小时前
【python】python进阶——网络编程
运维·服务器·网络