为了账号安全,请及时绑定邮箱和手机立即绑定

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

一. 背景

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

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

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

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 等。

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

点击查看更多内容
4人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
全栈工程师
手记
粉丝
1.7万
获赞与收藏
594

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消