给 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,方便其使用。

相关文章
|
4天前
|
前端开发 Java 测试技术
Java一分钟之Spring MVC:构建Web应用
【5月更文挑战第15天】Spring MVC是Spring框架的Web应用模块,基于MVC模式实现业务、数据和UI解耦。常见问题包括:配置DispatcherServlet、Controller映射错误、视图解析未设置、Model数据传递遗漏、异常处理未配置、依赖注入缺失和忽视单元测试。解决这些问题可提升代码质量和应用性能。注意配置`web.xml`、`@RequestMapping`、`ViewResolver`、`Model`、`@ExceptionHandler`、`@Autowired`,并编写测试用例。
51 3
|
18小时前
|
缓存 移动开发 Android开发
构建高效Android应用:从内存优化到电池寿命
【5月更文挑战第18天】在移动开发领域,一个优秀的Android应用不仅要拥有流畅的用户界面和丰富的功能,更要在设备资源有限的前提下保持高效运行。本文将探讨Android应用开发中关键的性能优化策略,包括内存使用优化、CPU使用减少和电池寿命延长等方面。通过分析常见的性能瓶颈和提供实用的解决方案,帮助开发者打造更高效、更受欢迎的Android应用。
|
1天前
|
移动开发 Android开发 UED
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第17天】 在移动开发领域,尤其是针对Android平台,性能优化和流畅的用户体验始终是开发者追求的目标。Kotlin作为一种现代的编程语言,自引入Android开发以来,其简洁、安全和互操作性的特点受到广泛欢迎。特别是Kotlin协程的推出,为解决Android平台上的并发编程问题提供了新的思路。本文将深入探讨Kotlin协程的核心优势,并通过实例展示如何在Android应用中有效利用协程来提高响应性和稳定性,从而改善整体的用户体验。
|
1天前
|
移动开发 API Android开发
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第17天】在移动开发领域,性能优化和流畅的用户体验一直是开发者追求的目标。针对Android平台,Kotlin语言凭借其简洁性和功能丰富性成为了许多开发者的首选。其中,Kotlin协程作为异步编程的强大工具,为处理并发任务提供了轻量级的解决方案。本文深入探讨了Kotlin协程的核心优势,并通过实例分析其在Android开发中的应用,旨在帮助开发者提升应用的性能和响应能力。
|
1天前
|
移动开发 Android开发 开发者
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第17天】 在移动开发领域,尤其是针对Android平台,性能优化和流畅的用户体验始终是开发者追求的目标。近年来,Kotlin语言因其简洁性和功能性而成为Android开发的热门选择。其中,Kotlin协程作为一种轻量级的线程管理方案,为编写异步代码提供了强大支持,使得处理并发任务更加高效和容易。本文将深入探讨Kotlin协程的核心优势,并通过具体实例展示如何在Android应用中有效利用协程来提升性能和用户体验。
|
2天前
|
移动开发 Android开发 UED
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第17天】 在移动开发领域,性能优化和流畅的用户体验始终是核心追求。针对Android平台,Kotlin协程作为一种新兴的轻量级线程管理方案,正逐渐改变开发者对于异步编程和后台任务处理的认识。本文通过深入分析Kotlin协程的原理、优势以及在实际Android应用中的使用案例,揭示了如何利用协程提高应用性能,减少资源消耗,并最终实现更流畅的用户体验。我们将通过一系列实验证据,展示协程如何在不牺牲可读性和可维护性的前提下,提升代码执行效率,并为Android开发社区提供一种新的并发处理范式。
|
2天前
|
移动开发 调度 Android开发
构建高效Android应用:Kotlin协程的全面应用
【5月更文挑战第17天】随着移动开发技术的不断进步,开发者寻求更高效、响应更快的应用程序。在Android平台上,Kotlin作为一种现代编程语言,提供了协程这一强大的并发处理工具。本文深入探讨了如何在Android应用中使用Kotlin协程来提升性能和用户体验,同时保证代码的简洁性和可维护性。我们将分析协程的核心概念,并通过实例展示其在实际开发中的应用。
|
2天前
|
移动开发 安全 Android开发
构建高效Android应用:Kotlin与协程的完美结合
【5月更文挑战第17天】 在移动开发领域,性能优化和流畅的用户体验是关键。对于Android平台而言,Kotlin语言凭借其简洁性和功能安全性成为开发的首选。与此同时,协程作为一种新的并发处理方式,在简化异步编程方面展现出巨大潜力。本文将深入探讨如何通过Kotlin语言以及协程技术,提升Android应用的性能和响应能力,并确保用户界面的流畅性。
|
2天前
|
设计模式 缓存 Java
补齐Android技能树——从AGP构建过程到APK打包过程,安卓rxjava面试
补齐Android技能树——从AGP构建过程到APK打包过程,安卓rxjava面试
|
2天前
|
移动开发 监控 Android开发
构建高效安卓应用:Kotlin 协程的实践与优化
【5月更文挑战第16天】 在移动开发领域,性能优化一直是开发者们追求的重要目标。特别是对于安卓平台来说,由于设备多样性和系统资源的限制,如何提升应用的响应性和流畅度成为了一个关键议题。近年来,Kotlin 语言因其简洁、安全和高效的特点,在安卓开发中得到了广泛的应用。其中,Kotlin 协程作为一种轻量级的并发解决方案,为异步编程提供了强大支持,成为提升安卓应用性能的有效手段。本文将深入探讨 Kotlin 协程在安卓开发中的应用实践,以及通过合理设计和使用协程来优化应用性能的策略。
17 8