数据列表离线缓存的另一种方法

Android中很大一部分APP,都是非常“轻”的,这里的“轻”指的是APP的“体量”和业务逻辑。受手机性能的限制和安全的考虑,绝大部分运算以及业务逻辑都放在后台执行,移动端只是一个展示和操作(交互)的平台,所以在开发APP的过程中,推荐尽量使用比较轻度的方法或插件等。
工作这几年了,开发的APP大大小小应该超过20个了,极少使用到数据库,当然这和开发的APP业务相关。今天要说的是使用文件存储方式存储离线缓存列表数据。
先来介绍Android中几种存储方式。

Android提供以下四种存储方式:

SharePreference

SharedPreference是一种轻型的数据存储方式,实际上是基于XML文件存储的“key-value”键值对数据。通常用来存储程序的一些配置信息,例如是否夜间模式,是否接收推送等。其存储在“data/data/程序包名/shared_prefs目录下。

SQLite

SQLite是一个轻量级关系型数据库,主要存储一些缓存数据,例如用户信息,离线展示数据等。

File

文件储存方式,主要存储文件,例如下载下来的apk、图片等

ContentProvider

能够实现跨应用之间的数据操作,例如联系人(各个应用都能申请获取)

另一种思路

现在有个列表数据需要缓存在本地,根据官方推荐肯定是使用数据库存储,但是考虑到这是一个非常简单的需求,存储的数据不需要做修改、联查等(整存整取),可以试试使用文件存储。
下面是封装的文件存储类:

public class CacheList<T>{
    private static final String LOG_TAG             = CacheList.class.getSimpleName();
    private static Map<String, Object>  _lockMap    = new HashMap<>();
    
    private static final String
            LIST_FILE_DIR   = "solid_list";
    
    public static final int     UNLIMITED_SIZE  = -1;
    
    private Context _context;
    private String  _name;
    private int     _maxSize;
    
    private String  _path;
    private LinkedList<T>   _linkedList;
    private Object  _lock;
    
    private DeadListCallback<T> _callback;
    
    public CacheList(Context context, String name, int maxSize, DeadListCallback<T> callback) {
        _context    = context;
        _name       = name;
        _maxSize    = maxSize;
        _callback   = callback;
                
        _path       = _context.getDir(LIST_FILE_DIR, Context.MODE_PRIVATE) + "/" + _name;
        
        createLock();
        createList();
    }

    private void createLock() {
        if( ! _lockMap.containsKey(_name)){
            _lockMap.put(_name, new Object());
        }
        
        _lock   = _lockMap.get(_name);
    }

    @SuppressWarnings("unchecked")
    private void createList() { 
        synchronized (_lock) {
            File listFile   = new File(_path);
            if( ! listFile.exists()) {
                _linkedList     = new LinkedList<T>();
                Log.d(LOG_TAG, "list file not exist, file=" + _path + ", create an empty list");
                return;
            }
            
            try{
                FileInputStream     fis = new FileInputStream(_path);
                ObjectInputStream   ois = new ObjectInputStream(fis);
                _linkedList     = (LinkedList<T>)(ois.readObject());
                ois.close();
            } catch(Exception e) {
                Log.e("Exception" ,e.toString());
                _linkedList     = new LinkedList<T>();
            }
        }
    }
    
    public boolean remove(int index) {
        synchronized (_lock) {
            if(index < 0 || index >= _linkedList.size()) {
                return false;
            }
            
            T element   = _linkedList.remove(index);
            if(-1 == _linkedList.lastIndexOf(element)){
                // no same element in list any more
                if(null != _callback) {
                    _callback.onRemove(element);
                }
            }
            solidify();
            
            return true;
        }
    }
    
    public boolean remove(T element) {
        synchronized (_lock) {
            if(_linkedList.remove(element)) {
                if(-1 == _linkedList.lastIndexOf(element)){
                    // no same element in list any more
                    if(null != _callback) {
                        _callback.onRemove(element);
                    }
                }
                solidify();
                
                return true;
            }else{
                Log.d(LOG_TAG, "remove element fail");
            }
            
            return false;
        }
    }
    
    public boolean addAll(List<T> elementList) {
        synchronized (_lock) {
            int i = 0;
            for( ; i < elementList.size(); ++i) {
                if(UNLIMITED_SIZE == _maxSize || _linkedList.size() < _maxSize) {
                    T element   = elementList.get(i);
                    _linkedList.addLast(element);
                    if(null != _callback) {
                        _callback.onAdd(element);
                    }
                } else {
                    break;
                }
            }
            solidify();

            return i >= elementList.size();

        }
    }

    public boolean add(T element) {
        synchronized (_lock) {
            if(UNLIMITED_SIZE == _maxSize || _linkedList.size() < _maxSize) {
                Log.e("cache", "add element,name: " + _name);
                _linkedList.addLast(element);
                if(null != _callback) {
                    _callback.onAdd(element);
                }
                solidify();

                return true;
            }

            return false;
        }
    }
    public boolean addFrist(T element) {
        synchronized (_lock) {
            if(UNLIMITED_SIZE == _maxSize || _linkedList.size() < _maxSize) {
                Log.e("cache", "add element,name: " + _name);
                _linkedList.addFirst(element);
                if(null != _callback) {
                    _callback.onAdd(element);
                }
                solidify();

                return true;
            }

            return false;
        }
    }

    /**
     * 固化链表(固化到本地)
     */
    public void solidify() {
        synchronized (_lock) {
            try {
                FileOutputStream fos    = new FileOutputStream(_path);
                ObjectOutputStream oos  = new ObjectOutputStream(fos);
                while(_maxSize != UNLIMITED_SIZE && _linkedList.size() > _maxSize) {
                    T element   = _linkedList.removeLast();
                    if(-1 == _linkedList.lastIndexOf(element)){
                        // no same element in list any more
                        if(null != _callback) {
                            _callback.onRemove(element);
                        }
                    }
                }
                oos.writeObject(_linkedList);
                oos.close();
            } catch(Exception e) {
                Log.e("Exception" ,e.toString());
            }
        }
    }
    
    public void clear() {
        synchronized (_lock) {
            int oldMaxSize  = _maxSize;
            setMaxSize(0);
            solidify();
            setMaxSize(oldMaxSize);
        }
    }
    
    public boolean destroy(){
        synchronized (_lock) {
            try {
                clear();
                
                File listFile   = new File(_path);
                if(listFile.exists()) {
                    boolean succ    = listFile.delete();
                    if(succ) {
                        Log.d(LOG_TAG, "list file delete succ, path=" + _path);
                    } else {
                        Log.e(LOG_TAG, "list file delete fail, path=" + _path);
                    }
                    
                    return succ;
                } else {
                    Log.d(LOG_TAG, "list file not exist, delete fail, path=" + _path);
                    return false;
                }

            } catch(Exception e) {
                Log.e("Exception", e.toString());
                return false;
            }
        }
    }
    
    public void setMaxSize(int maxSize) {
        synchronized (_lock) {
            _maxSize    = maxSize;
        }
    }
    
    public int size() {
        synchronized (_lock) {
            return _linkedList.size();
        }
    }
    
    public LinkedList<T> getList() {
        synchronized (_lock) {
            return _linkedList;
        }
    }
    
    public interface DeadListCallback<E> {
         void onAdd(E element);
         void onRemove(E element);
    }
}

代码非常简单,不做解释了,使用时:
1、新建列表:(如果已经存在缓存则会读取缓存)

userCacheList = new CacheList<>(this, "test", 50, new CacheList.DeadListCallback<User>() {
            @Override
            public void onAdd(User element) {
                Log.e("tag", "add element id: " + element.getId());
            }

            @Override
            public void onRemove(User element) {
                Log.e("tag", "remove element id: " + element.getId());

            }
        });

2、添加数据并固化到本地:

List<User> userList = new ArrayList<>();
        for (int i=0; i<50; i++){
            User user = new User();
            user.setId(i);
            user.setName("name"+i);
            user.setAddress("中华人民共和国北京天安门"+i);
            user.setImgUrl("lskdjflsajflsajflsafjlasdjflasdfasfasdfasfsaf"+i);
            user.setPwd("pwd"+i);
            user.setSex(1);
            userList.add(user);
        }
        userCacheList.addAll(userList);
        userCacheList.solidify();

3、删除数据:
3种删除方法

userCacheList.clear();//全清
userCacheList.remove(1);//按Position删除数据
userCacheList.remove(new User());//按对象删除数据

删除完别忘了固化到本地:

userCacheList.solidify();

实测50条数据的读取

12-22 15:59:14.533 23506-23506/com.twsm.testdemo E/tag: get data before: 1513929554533
12-22 15:59:14.570 23506-23506/com.twsm.testdemo E/tag: get data after: 1513929554570

不到40毫秒,完全可以在主线程读取(测试的数据量比较小,当然不推荐在主线程做操作)。

这样看起来是不是很方便,挺适合做列表展示数据的离线缓存。当然,还是推荐使用SqlLite,这只是提供另一种思路。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容