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处理网络请求与缓存策略。

相关推荐
我科绝伦(Huanhuan Zhou)16 小时前
MySQL一键升级脚本(5.7-8.0)
android·mysql·adb
VernonJsn16 小时前
使用C++99语言开发ModbusTCP通讯
网络
Tony Bai17 小时前
【Go 网络编程全解】12 本地高速公路:Unix 域套接字与网络设备信息
开发语言·网络·后端·golang·unix
-曾牛17 小时前
深入浅出 SQL 注入
网络·sql·安全·网络安全·渗透测试·sql注入·盲注
想学全栈的菜鸟阿董17 小时前
LangGraph智能体架构核心概念
网络·架构
乐大师17 小时前
手动安装联想打印机2268w驱动
网络·打印机
NewCarRen17 小时前
针对汽车远程无钥匙进入系统的新型重放同步攻击的缓解策略
运维·网络·安全
神的孩子都在歌唱17 小时前
VLAN 是什么?如何用 VLAN 提高网络安全与效率?
网络·安全·web安全
怪兽201417 小时前
Android View, SurfaceView, GLSurfaceView 的区别
android·面试
LRX_19892717 小时前
网络管理员教程(初级)第六版--第5章网络安全及管理
网络·安全·web安全