RxCache 整合 Android 的持久层框架 greenDAO、Room

简介: RxCache 整合 Android 的持久层框架 greenDAO、Room

一. 背景



RxCache 是一个支持 Java 和 Android 的 Local Cache 。


之前的文章给 Java 和 Android 构建一个简单的响应式Local Cache曾详细介绍过它。


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


下图是 rxcache-core 模块的 uml 类图

image.png

rxcache_uml.png


二. 持久层



RxCache 的持久层包括 Disk、DB,分别单独抽象了 Disk、DB 接口并继承 Persistence。


DB 接口:

package com.safframework.rxcache.persistence.db;
import com.safframework.rxcache.persistence.Persistence;
/**
 * Created by tony on 2018/10/14.
 */
public interface DB extends Persistence {
}


RxCache 的持久层,尝试集成 Android 常用的持久层框架。


2.1 集成 greenDAO


greenDAO 是一款开源的面向 Android 的轻便、快捷的 ORM 框架,将 Java 对象映射到 SQLite 数据库。


首先,创建一个缓存实体 CacheEntity ,它包含 id、key、data、timestamp、expireTime。其中 data 是待缓存的对象并转换成 json 字符串。

@Entity
public class CacheEntity {
    @Id(autoincrement = true)
    private Long id;
    public String key;
    public String data;// 对象转换的 json 字符串
    public Long timestamp;
    public Long expireTime;
    ...... // getter 、setter
}


创建一个单例的 DBService ,并提供返回 CacheEntityDao 的方法。其实,crud 的逻辑也可以放在此处。

public class DBService {
    private static final String DB_NAME = "cache.db";
    private static volatile DBService defaultInstance;
    private DaoSession daoSession;
    private DBService(Context context) {
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(context, DB_NAME);
        DaoMaster daoMaster = new DaoMaster(helper.getWritableDatabase());
        daoSession = daoMaster.newSession();
    }
    public static DBService getInstance(Context context) {
        if (defaultInstance == null) {
            synchronized (DBService.class) {
                if (defaultInstance == null) {
                    defaultInstance = new DBService(context.getApplicationContext());
                }
            }
        }
        return defaultInstance;
    }
    public CacheEntityDao getCacheEntityDao(){
        return daoSession.getCacheEntityDao();
    }
}

创建 GreenDAOImpl 实现 DB 接口,实现真正的缓存逻辑。

import com.safframework.rxcache.config.Constant;
import com.safframework.rxcache.domain.Record;
import com.safframework.rxcache.domain.Source;
import com.safframework.rxcache.persistence.converter.Converter;
import com.safframework.rxcache.persistence.converter.GsonConverter;
import com.safframework.rxcache.persistence.db.DB;
import com.safframework.tony.common.utils.Preconditions;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
 * @FileName: com.safframework.rxcache4a.persistence.db.greendao.GreenDAOImpl
 * @author: Tony Shen
 * @date: 2018-10-15 11:50
 * @version: V1.0 <描述当前版本功能>
 */
public class GreenDAOImpl implements DB {
    private CacheEntityDao dao;
    private Converter converter;
    public GreenDAOImpl(CacheEntityDao dao) {
        this(dao,new GsonConverter());
    }
    public GreenDAOImpl(CacheEntityDao dao, Converter converter) {
        this.dao = dao;
        this.converter = converter;
    }
    @Override
    public <T> Record<T> retrieve(String key, Type type) {
        CacheEntity entity = dao.queryBuilder().where(CacheEntityDao.Properties.Key.eq(key)).unique();
        if (entity==null) return null;
        long timestamp = entity.timestamp;
        long expireTime = entity.expireTime;
        T result = null;
        if (expireTime<0) { // 缓存的数据从不过期
            String json = entity.data;
            result = converter.fromJson(json,type);
        } else {
            if (timestamp + expireTime > System.currentTimeMillis()) {  // 缓存的数据还没有过期
                String json = entity.data;
                result = converter.fromJson(json,type);
            } else {        // 缓存的数据已经过期
                evict(key);
            }
        }
        return result != null ? new Record<>(Source.PERSISTENCE, key, result, timestamp, expireTime) : null;
    }
    @Override
    public <T> void save(String key, T value) {
        save(key,value, Constant.NEVER_EXPIRE);
    }
    @Override
    public <T> void save(String key, T value, long expireTime) {
        if (Preconditions.isNotBlanks(key,value)) {
            CacheEntity entity = new CacheEntity();
            entity.setKey(key);
            entity.setTimestamp(System.currentTimeMillis());
            entity.setExpireTime(expireTime);
            entity.setData(converter.toJson(value));
            dao.save(entity);
        }
    }
    @Override
    public List<String> allKeys() {
        List<CacheEntity> list = dao.loadAll();
        List<String> result = new ArrayList<>();
        for (CacheEntity entity:list) {
            result.add(entity.key);
        }
        return result;
    }
    @Override
    public boolean containsKey(String key) {
        List<String> keys = allKeys();
        return Preconditions.isNotBlank(keys) ? keys.contains(key) : false;
    }
    @Override
    public void evict(String key) {
        CacheEntity entity = dao.queryBuilder().where(CacheEntityDao.Properties.Key.eq(key)).unique();
        if (entity!=null) {
            dao.delete(entity);
        }
    }
    @Override
    public void evictAll() {
        dao.deleteAll();
    }
}


2.2 集成 Room


Room 是 Google 开发的一个 SQLite 对象映射库。 使用它来避免样板代码并轻松地将 SQLite 数据转换为 Java 对象。 Room 提供 SQLite 语句的编译时检查,可以返回 RxJava 和 LiveData Observable。


同样,需要先创建一个 CacheEntity,但是不能共用之前的 CacheEntity。因为 Room、greenDAO 使用的 @Entity不同。

@Entity
public class CacheEntity {
    @PrimaryKey(autoGenerate = true)
    private Long id;
    public String key;
    public String data;// 对象转换的 json 字符串
    public Long timestamp;
    public Long expireTime;
    ...... // getter 、setter
}


创建一个 CacheEntityDao 用于 crud 的实现。

import java.util.List;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import static androidx.room.OnConflictStrategy.IGNORE;
/**
 * @FileName: com.safframework.rxcache4a.persistence.db.room.CacheEntityDao
 * @author: Tony Shen
 * @date: 2018-10-15 16:44
 * @version: V1.0 <描述当前版本功能>
 */
@Dao
public interface CacheEntityDao {
    @Query("SELECT * FROM cacheentity")
    List<CacheEntity> getAll();
    @Query("SELECT * FROM cacheentity WHERE `key` = :key LIMIT 0,1")
    CacheEntity findByKey(String key);
    @Insert(onConflict = IGNORE)
    void insert(CacheEntity entity);
    @Delete
    void delete(CacheEntity entity);
    @Query("DELETE FROM cacheentity")
    void deleteAll();
}


创建一个 AppDatabase 表示一个数据库的持有者。

import androidx.room.Database;
import androidx.room.RoomDatabase;
/**
 * @FileName: com.safframework.rxcache4a.persistence.db.room.AppDatabase
 * @author: Tony Shen
 * @date: 2018-10-15 16:40
 * @version: V1.0 <描述当前版本功能>
 */
@Database(entities = {CacheEntity.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract CacheEntityDao cacheEntityDao();
}


最后,创建 RoomImpl 实现 DB 接口,实现真正的缓存逻辑。

import android.content.Context;
import com.safframework.rxcache.config.Constant;
import com.safframework.rxcache.domain.Record;
import com.safframework.rxcache.domain.Source;
import com.safframework.rxcache.persistence.converter.Converter;
import com.safframework.rxcache.persistence.converter.GsonConverter;
import com.safframework.rxcache.persistence.db.DB;
import com.safframework.tony.common.utils.Preconditions;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import androidx.room.Room;
/**
 * @FileName: com.safframework.rxcache4a.persistence.db.room.RoomImpl
 * @author: Tony Shen
 * @date: 2018-10-15 16:46
 * @version: V1.0 <描述当前版本功能>
 */
public class RoomImpl implements DB {
    private AppDatabase db;
    private Converter converter;
    private static final String DB_NAME = "cache";
    public RoomImpl(Context context) {
        this(context,new GsonConverter());
    }
    public RoomImpl(Context context, Converter converter) {
        this.db = Room.databaseBuilder(context, AppDatabase.class, DB_NAME).build();
        this.converter = converter;
    }
    @Override
    public <T> Record<T> retrieve(String key, Type type) {
        CacheEntity entity = db.cacheEntityDao().findByKey(key);
        if (entity==null) return null;
        long timestamp = entity.timestamp;
        long expireTime = entity.expireTime;
        T result = null;
        if (expireTime<0) { // 缓存的数据从不过期
            String json = entity.data;
            result = converter.fromJson(json,type);
        } else {
            if (timestamp + expireTime > System.currentTimeMillis()) {  // 缓存的数据还没有过期
                String json = entity.data;
                result = converter.fromJson(json,type);
            } else {        // 缓存的数据已经过期
                evict(key);
            }
        }
        return result != null ? new Record<>(Source.PERSISTENCE, key, result, timestamp, expireTime) : null;
    }
    @Override
    public <T> void save(String key, T value) {
        save(key,value, Constant.NEVER_EXPIRE);
    }
    @Override
    public <T> void save(String key, T value, long expireTime) {
        if (Preconditions.isNotBlanks(key,value)) {
            CacheEntity entity = new CacheEntity();
            entity.setKey(key);
            entity.setTimestamp(System.currentTimeMillis());
            entity.setExpireTime(expireTime);
            entity.setData(converter.toJson(value));
            db.cacheEntityDao().insert(entity);
        }
    }
    @Override
    public List<String> allKeys() {
        List<CacheEntity> list = db.cacheEntityDao().getAll();
        List<String> result = new ArrayList<>();
        for (CacheEntity entity:list) {
            result.add(entity.key);
        }
        return result;
    }
    @Override
    public boolean containsKey(String key) {
        List<String> keys = allKeys();
        return Preconditions.isNotBlank(keys) ? keys.contains(key) : false;
    }
    @Override
    public void evict(String key) {
        CacheEntity entity = db.cacheEntityDao().findByKey(key);
        if (entity!=null) {
            db.cacheEntityDao().delete(entity);
        }
    }
    @Override
    public void evictAll() {
        db.cacheEntityDao().deleteAll();
    }
}


这两种集成方式,都使用 CacheEntity 的 data 来存储对象转换后的 json 字符串。使用这种方式,可以替换成任何的持久层框架。使得 DB 也可以成为 RxCache  的其中一级缓存。


三. 使用



编写单元测试,看一下集成 greenDAO 的效果。


分别测试多种对象的存储、带 ExpireTime 的存储。

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import com.safframework.rxcache.RxCache;
import com.safframework.rxcache.domain.Record;
import com.safframework.rxcache4a.persistence.db.greendao.CacheEntityDao;
import com.safframework.rxcache4a.persistence.db.greendao.DBService;
import com.safframework.rxcache4a.persistence.db.greendao.GreenDAOImpl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
/**
 * @FileName: com.safframework.rxcache4a.GreenDAOImplTest
 * @author: Tony Shen
 * @date: 2018-10-15 18:51
 * @version: V1.0 <描述当前版本功能>
 */
@RunWith(AndroidJUnit4.class)
public class GreenDAOImplTest {
    Context appContext;
    DBService dbService;
    @Before
    public void setUp() {
        appContext = InstrumentationRegistry.getTargetContext();
        dbService = DBService.getInstance(appContext);
    }
    @Test
    public void testWithObject() {
        CacheEntityDao dao = dbService.getCacheEntityDao();
        GreenDAOImpl impl = new GreenDAOImpl(dao);
        impl.evictAll();
        RxCache.config(new RxCache.Builder().persistence(impl));
        RxCache rxCache = RxCache.getRxCache();
        Address address = new Address();
        address.province = "Jiangsu";
        address.city = "Suzhou";
        address.area = "Gusu";
        address.street = "ren ming road";
        User u = new User();
        u.name = "tony";
        u.password = "123456";
        u.address = address;
        rxCache.save("user",u);
        Record<User> record = rxCache.get("user", User.class);
        assertEquals(u.name, record.getData().name);
        assertEquals(u.password, record.getData().password);
        assertEquals(address.city, record.getData().address.city);
        rxCache.save("address",address);
        Record<Address> record2 = rxCache.get("address", Address.class);
        assertEquals(address.city, record2.getData().city);
    }
    @Test
    public void testWithExpireTime() {
        CacheEntityDao dao = dbService.getCacheEntityDao();
        GreenDAOImpl impl = new GreenDAOImpl(dao);
        impl.evictAll();
        RxCache.config(new RxCache.Builder().persistence(impl));
        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);
        assertNull(record);
    }
}


两个 test case 都顺利通过,表示集成 greenDAO 没有问题。当然,集成 Room 也是一样。


四. 总结



我单独创建了一个项目 RxCache4a 用于整合的 greenDAO、Room 等。


Github 地址: https://github.com/fengzhizi715/RxCache4a


未来,可能对框架增加一些 Annotation,以及增加 Cache 清除的算法。

相关文章
|
3月前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
13天前
|
算法 JavaScript Android开发
|
22天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
88 1
|
2月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
346 3
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
77 8
|
3月前
|
设计模式 Java Android开发
探索安卓应用开发:从新手到专家的旅程探索iOS开发中的SwiftUI框架
【8月更文挑战第29天】本文旨在通过一个易于理解的旅程比喻,带领读者深入探讨安卓应用开发的各个方面。我们将从基础概念入手,逐步过渡到高级技术,最后讨论如何维护和推广你的应用。无论你是编程新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和实用的代码示例。让我们一起开始这段激动人心的旅程吧!
|
3月前
|
Android开发
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单
如何使用Amlogic T972安卓9.0系统上的misc框架来简化驱动程序开发,通过misc框架自动分配设备号并创建设备文件,从而减少代码量并避免设备号冲突。
43 0
基于Amlogic 安卓9.0, 驱动简说(三):使用misc框架,让驱动更简单
|
数据库 Android开发 数据库管理
Android使用Room持久化SQLite
原文链接 前言 Android中提供了SQLite数据库进行数据的持久化 ,并提供了对应API访问数据库,而Room框架提供了SQLite数据访问抽象层,为高效的数据库访问层带来便捷 APP可以缓存用户数据,当APP离线时便从SQLite读取数据,当重...
1760 0
|
5天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
6天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。