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 清除的算法。

相关文章
|
4月前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
1月前
|
算法 JavaScript Android开发
|
27天前
|
开发框架 Dart Android开发
安卓与iOS的跨平台开发:Flutter框架深度解析
在移动应用开发的海洋中,Flutter作为一艘灵活的帆船,正引领着开发者们驶向跨平台开发的新纪元。本文将揭开Flutter神秘的面纱,从其架构到核心特性,再到实际应用案例,我们将一同探索这个由谷歌打造的开源UI工具包如何让安卓与iOS应用开发变得更加高效而统一。你将看到,借助Flutter,打造精美、高性能的应用不再是难题,而是变成了一场创造性的旅程。
|
2月前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
136 1
|
3月前
|
前端开发 Java 数据库
💡Android开发者必看!掌握这5大框架,轻松打造爆款应用不是梦!🏆
在Android开发领域,框架犹如指路明灯,助力开发者加速应用开发并提升品质。本文将介绍五大必备框架:Retrofit简化网络请求,Room优化数据库访问,MVVM架构提高代码可维护性,Dagger 2管理依赖注入,Jetpack Compose革新UI开发。掌握这些框架,助你在竞争激烈的市场中脱颖而出,打造爆款应用。
421 3
|
3月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
93 8
|
4月前
|
设计模式 Java Android开发
探索安卓应用开发:从新手到专家的旅程探索iOS开发中的SwiftUI框架
【8月更文挑战第29天】本文旨在通过一个易于理解的旅程比喻,带领读者深入探讨安卓应用开发的各个方面。我们将从基础概念入手,逐步过渡到高级技术,最后讨论如何维护和推广你的应用。无论你是编程新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和实用的代码示例。让我们一起开始这段激动人心的旅程吧!
|
SQL 关系型数据库 API
【Android Jetpack】Room数据库的使用及原理详解
Android Jetpack的出现统一了Android开发生态,各种三方库逐渐被官方组件所取代。Room也同样如此,逐渐取代竞品成为最主流的数据库ORM框架。这当然不仅仅因为其官方身份,更是因为其良
1611 0
|
17天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
40 19
|
17天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
42 14