给 Java 和 Android 构建一个简单的响应式Local Cache

简介: 给 Java 和 Android 构建一个简单的响应式Local Cache

一. 为何要创建这个库



首先,Local Cache 不是类似于 Redis、Couchbase、Memcached 这样的分布式 Cache。Local Cache 适用于在单机环境下,对访问频率高、更新次数少的数据进行存放。因此,Local Cache 不适合存放大量的数据。


Local Cache 特别适合于 App,也适合在 Java 的某些场景下使用。


我们的 App 使用 Retrofit 作为网络框架,并且大量使用 RxJava,因此我考虑创建一个 RxCache 来缓存一些必要的数据。


RxCache 地址:https://github.com/fengzhizi715/RxCache


二. 如何构建 RxCache



2.1 RxCache 的基本方法


对于 Local Cache,最重要是需要有以下的这些方法:

<T> Record<T> get(String key, Type type);
<T> void save(String key, T value);
<T> void save(String key, T value, long expireTime);
boolean containsKey(String key);
Set<String> getAllKeys();
void remove(String key);
void clear();


其中,有一个 save() 方法包含了失效时间的参数expireTime,这对于 Local Cache 是比较重要的一个方法,超过这个时间,这个数据将会失效。


既然是 RxCache,对于获取数据肯定需要类似这样的方法:

<T> Observable<Record<T>> load2Observable(final String key, final Type type) ;
<T> Flowable<Record<T>> load2Flowable(final String key, final Type type);
<T> Single<Record<T>> load2Single(final String key, final Type type);
<T> Maybe<Record<T>> load2Maybe(final String key, final Type type);


也需要一些 Transformer 的方法,将 RxJava 的被观察者进行转换。在 RxCache 中,包含了一些默认的 Transformer 策略,特别是使用 Retrofit 和 RxJava 时,可以考虑结合这些策略来缓存数据。


以 CacheFirstStrategy 为例:

/**
 * 缓存优先的策略,缓存取不到时取接口的数据。
 * Created by tony on 2018/9/30.
 */
public class CacheFirstStrategy implements ObservableStrategy,
        FlowableStrategy,
        MaybeStrategy  {
    @Override
    public <T> Publisher<Record<T>> execute(RxCache rxCache, String key, Flowable<T> source, Type type) {
        Flowable<Record<T>> cache = rxCache.<T>load2Flowable(key, type);
        Flowable<Record<T>> remote = source
                .map(new Function<T, Record<T>>() {
                    @Override
                    public Record<T> apply(@NonNull T t) throws Exception {
                        rxCache.save(key, t);
                        return new Record<>(Source.CLOUD, key, t);
                    }
                });
        return cache.switchIfEmpty(remote);
    }
    @Override
    public <T> Maybe<Record<T>> execute(RxCache rxCache, String key, Maybe<T> source, Type type) {
        Maybe<Record<T>> cache = rxCache.<T>load2Maybe(key, type);
        Maybe<Record<T>> remote = source
                .map(new Function<T, Record<T>>() {
                    @Override
                    public Record<T> apply(@NonNull T t) throws Exception {
                        rxCache.save(key, t);
                        return new Record<>(Source.CLOUD, key, t);
                    }
                });
        return cache.switchIfEmpty(remote);
    }
    @Override
    public <T> Observable<Record<T>> execute(RxCache rxCache, String key, Observable<T> source, Type type) {
        Observable<Record<T>> cache = rxCache.<T>load2Observable(key, type);
        Observable<Record<T>> remote = source
                .map(new Function<T, Record<T>>() {
                    @Override
                    public Record<T> apply(@NonNull T t) throws Exception {
                        rxCache.save(key, t);
                        return new Record<>(Source.CLOUD, key, t);
                    }
                });
        return cache.switchIfEmpty(remote);
    }
}


2.2 Memory


RxCache 包含了两级缓存: Memory 和 Persistence 。


image.png

RxCache.png


Memory:

package com.safframework.rxcache.memory;
import com.safframework.rxcache.domain.Record;
import java.util.Set;
/**
 * Created by tony on 2018/9/29.
 */
public interface Memory {
    <T> Record<T> getIfPresent(String key);
    <T> void put(String key, T value);
    <T> void put(String key, T value, long expireTime);
    Set<String> keySet();
    boolean containsKey(String key);
    void evict(String key);
    void evictAll();
}


它的默认实现 DefaultMemoryImpl 使用 ConcurrentHashMap 来缓存数据。


在 extra 模块还有 Guava Cache、Caffeine 的实现。它们都是成熟的 Local Cache,如果不想使用 DefaultMemoryImpl ,完全可以使用 extra 模块成熟的替代方案。


2.3 Persistence


Persistence 的接口跟 Memory 很类似:

package com.safframework.rxcache.persistence;
import com.safframework.rxcache.domain.Record;
import java.lang.reflect.Type;
import java.util.List;
/**
 * Created by tony on 2018/9/28.
 */
public interface Persistence {
    <T> Record<T> retrieve(String key, Type type);
    <T> void save(String key, T value);
    <T> void save(String key, T value, long expireTime);
    List<String> allKeys();
    boolean containsKey(String key);
    void evict(String key);
    void evictAll();
}


由于,考虑到持久层可能包括 Disk、DB。于是单独抽象了一个 Disk 接口继承 Persistence。


在 Disk 的实现类 DiskImpl 中,它的构造方法注入了 Converter 接口:

public class DiskImpl implements Disk {
    private File cacheDirectory;
    private Converter converter;
    public DiskImpl(File cacheDirectory,Converter converter) {
        this.cacheDirectory = cacheDirectory;
        this.converter = converter;
    }
    ......
}


Converter 接口用于对象储存到文件的序列化和反序列化,目前支持 Gson 和 FastJSON。


Converter 的抽象实现类 AbstractConverter 的构造方法注入了 Encryptor 接口:

public abstract class AbstractConverter implements Converter {
    private Encryptor encryptor;
    public AbstractConverter() {
    }
    public AbstractConverter(Encryptor encryptor) {
        this.encryptor = encryptor;
    }
    ......
}


Encryptor 接口用于将存储到 Disk 上的数据进行加密和解密,目前 RxCache 支持 AES128 和 DES 两种加密方式。不使用 Encryptor 接口,则存储到 Disk 上的数据是明文,也就是一串json字符串。


三. 支持 Java



在 example 模块下,包括了一些常见 Java 使用的例子。


例如,最简单的使用:

import com.safframework.rxcache.RxCache;
import com.safframework.rxcache.domain.Record;
import domain.User;
import io.reactivex.Observable;
import io.reactivex.functions.Consumer;
/**
 * Created by tony on 2018/9/29.
 */
public class Test {
    public static void main(String[] args) {
        RxCache.config(new RxCache.Builder());
        RxCache rxCache = RxCache.getRxCache();
        User u = new User();
        u.name = "tony";
        u.password = "123456";
        rxCache.save("test",u);
        Observable<Record<User>> observable = rxCache.load2Observable("test", User.class);
        observable.subscribe(new Consumer<Record<User>>() {
            @Override
            public void accept(Record<User> record) throws Exception {
                User user = record.getData();
                System.out.println(user.name);
                System.out.println(user.password);
            }
        });
    }
}


带 ExpireTime 的缓存测试:

import com.safframework.rxcache.RxCache;
import com.safframework.rxcache.domain.Record;
import domain.User;
/**
 * Created by tony on 2018/10/5.
 */
public class TestWithExpireTime {
    public static void main(String[] args) {
        RxCache.config(new RxCache.Builder());
        RxCache rxCache = RxCache.getRxCache();
        User u = new User();
        u.name = "tony";
        u.password = "123456";
        rxCache.save("test",u,2000);
        try {
            Thread.sleep(2500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Record<User> record = rxCache.get("test", User.class);
        if (record==null) {
            System.out.println("record is null");
        }
    }
}


跟 Spring 整合并且 Memory 的实现使用 GuavaCacheImpl:

import com.safframework.rxcache.RxCache;
import com.safframework.rxcache.extra.memory.GuavaCacheImpl;
import com.safframework.rxcache.memory.Memory;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
/**
 * Created by tony on 2018/10/5.
 */
@Configurable
public class ConfigWithGuava {
    @Bean
    public Memory guavaCache(){
        return new GuavaCacheImpl(100);
    }
    @Bean
    public RxCache.Builder rxCacheBuilder(){
        return new RxCache.Builder().memory(guavaCache());
    }
    @Bean
    public RxCache rxCache() {
        RxCache.config(rxCacheBuilder());
        return RxCache.getRxCache();
    }
}


测试一下刚才的整合:

import com.safframework.rxcache.RxCache;
import com.safframework.rxcache.domain.Record;
import domain.User;
import io.reactivex.Observable;
import io.reactivex.functions.Consumer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
 * Created by tony on 2018/10/5.
 */
public class TestWithGuava {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithGuava.class);
        RxCache rxCache = ctx.getBean(RxCache.class);
        User u = new User();
        u.name = "tony";
        u.password = "123456";
        rxCache.save("test",u);
        Observable<Record<User>> observable = rxCache.load2Observable("test", User.class);
        observable.subscribe(new Consumer<Record<User>>() {
            @Override
            public void accept(Record<User> record) throws Exception {
                User user = record.getData();
                System.out.println(user.name);
                System.out.println(user.password);
            }
        });
    }
}


四. 支持 Android



为了更好地支持 Android,我还单独创建了一个项目 RxCache4a: https://github.com/fengzhizi715/RxCache4a


它包含了一个基于 LruCache 的 Memory 实现,以及一个基于 MMKV(腾讯开源的key

-value存储框架) 的 Persistence 实现。


我们目前 App 采用了如下的 MVVM 架构来传输数据:

image.png

MVVM.png


未来,希望能够通过 RxCache 来整合 Repository 这一层。


五. 总结



目前,RxCache 完成了大体的框架,初步可用,接下来打算增加一些 Annotation,方便其使用。

相关文章
|
16天前
|
安全 Android开发 iOS开发
Android vs. iOS:构建生态差异与技术较量的深度剖析###
本文深入探讨了Android与iOS两大移动操作系统在构建生态系统上的差异,揭示了它们各自的技术优势及面临的挑战。通过对比分析两者的开放性、用户体验、安全性及市场策略,本文旨在揭示这些差异如何塑造了当今智能手机市场的竞争格局,为开发者和用户提供决策参考。 ###
|
2月前
|
存储 Java Android开发
探索安卓应用开发:构建你的第一个"Hello World"应用
【9月更文挑战第24天】在本文中,我们将踏上一段激动人心的旅程,深入安卓应用开发的奥秘。通过一个简单而经典的“Hello World”项目,我们将解锁安卓应用开发的基础概念和步骤。无论你是编程新手还是希望扩展技能的老手,这篇文章都将为你提供一次实操体验。从搭建开发环境到运行你的应用,每一步都清晰易懂,确保你能顺利地迈出安卓开发的第一步。让我们开始吧,探索如何将一行简单的代码转变为一个功能齐全的安卓应用!
|
3天前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
4天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
15 2
|
10天前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
38 5
|
10天前
|
前端开发 JavaScript 测试技术
Android适合构建中大型项目的架构模式全面对比
Android适合构建中大型项目的架构模式全面对比
25 2
|
16天前
|
开发工具 Android开发 iOS开发
Android vs iOS:构建移动应用时的关键考量####
本文深入探讨了Android与iOS两大移动平台在开发环境、性能优化、用户体验设计及市场策略方面的差异性,旨在为开发者提供决策依据。通过对比分析,揭示两个平台各自的优势与挑战,帮助开发者根据项目需求做出更明智的选择。 ####
|
16天前
|
人工智能 Android开发
1024 云上见 构建AI总结助手,实现智能文档摘要 领罗马仕安卓充电器
1024 云上见 构建AI总结助手,实现智能文档摘要 领罗马仕安卓充电器
44 1
|
19天前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
36 4
|
19天前
|
编解码 Android开发 UED
构建高效Android应用:从内存优化到用户体验
【10月更文挑战第11天】本文探讨了如何通过内存优化和用户体验改进来构建高效的Android应用。介绍了使用弱引用来减少内存占用、懒加载资源以降低启动时内存消耗、利用Kotlin协程进行异步处理以保持UI流畅,以及采用响应式设计适配不同屏幕尺寸等具体技术手段。
42 2