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

相关推荐
死就死在补习班16 分钟前
Android系统源码分析Input - InputReader读取事件
android
死就死在补习班16 分钟前
Android系统源码分析Input - InputChannel通信
android
死就死在补习班17 分钟前
Android系统源码分析Input - 设备添加流程
android
死就死在补习班18 分钟前
Android系统源码分析Input - 启动流程
android
tom4i1 小时前
Launcher3 to Launchpad 01 布局修改
android
雨白1 小时前
OkHttpClient 核心配置详解
android·okhttp
zcz16071278211 小时前
Linux 网络命令大全
linux·运维·网络
淡淡的香烟1 小时前
Android auncher3实现简单的负一屏功能
android
VVVVWeiYee1 小时前
BGP高级特性
运维·服务器·网络
RabbitYao2 小时前
Android 项目 通过 AndroidStringsTool 更新多语言词条
android·python