专栏首页独行猫a的沉淀积累总结Android数据库存储模块封装,让操作记录更好用可复用

Android数据库存储模块封装,让操作记录更好用可复用

按照模块化搭积木的思想。

要实现一款POS机,需要存储模块、配置文件操作模块,通信模块,卡操作模块,界面显示模块。

如果这些都具备了,实现一完整的pos机岂不是很简单?就像搭积木一样。

再在android的框架下按照一种MVVM架构去实现,轻而易举且能做到结构清晰。

先来看结果,操作记录有多简单,

Record01 rec = new Record01();

rec.recType = 1;//赋值

RecordApi api = RecordApi.getInstance();

api.saveRec01(rec);//存储

long unrec = api.getRecUnNum();//获取未上传记录数

Log.d("un send rec num:",String.valueOf(unrec));

//依此上传未上传记录

Record01 rdrec;

for(int i =0 ;i < unrec;i++){

rdrec = api.readRecNotServer(1+i);

Log.d("recDebug:",rdrec.toString());

}

//删除已上传记录:

RecordApi api = RecordApi.getInstance();

api.decRec01(unrec);

目前有这个打算从零开始构建一个Android上运行的POS机应用。

先从构建各个模块开始。银联8583解析的模块有了,操作配置文件的已经有了,前文介绍过了。通信的简单,Android上有各种开源的库,其中的retrofit配合Rxjava就很好用。卡操作模块打算用JNI做封装,因此卡操作模块也简单。重要的就是数据存储模块了,

以及后续对MVVM架构模式做个探究,再加上界面就完整了。

这里主要针对数据存储做个封装,让接口更好用。

Android上对本地数据库的操作很简单,且有很多开源的第三方ORM库可以用,如litepal ,greendao,realm,OrmLite等。

其中的greendao据说是性能很好,它能够支持数千条记录的CRUD每秒,和OrmLite相比,GreenDAO要快几乎4.5倍。

Realm 则是一个移动数据库,可运行于手机、平板和可穿戴设备之上。可以让你的应用更快速,带来难以想象的体验。其目标是为了代替 CoreData 和 SQLite 数据库。 Realm非常易用,不是在SQLite基础上的ORM,它有自己的数据查询引擎。是完全重新开发的数据库,速度非常快,并且支持跨平台,数据加密,数据迁移,支持json,流式api等 。

Android官方也有操作sqllite的ROOM框架。

这里我采用了国人做的一个ORM框架litepal,我觉得它操作简单好用,且升级维护表结构也简单。

只所以要封装,是因为本身的数据的 增,删,改,查,无论用哪种框架或者直接用原生的sqlhelper都不难。

但是这还不能满足我们的要求。

我们的需求是,数据存储不能一直顺序存,存满指定数目要从头覆盖存储。一条一条的覆盖已经上传过的记录。

且从不能删除记录,记录只能打过上传标记被覆盖,不能删除记录表里的记录。

还要能查询未上传记录数目,按顺序依次上传未上传的记录数量。

因此,本次封装实现原理,新建一个数据目录表。一个记录表对应一个记录目录表。

记录目录表里就一条记录,用来跟踪记录表里当前记录写到的ID位置,记录上传ID的位置。

且要考虑同一条记录可能需要上传至多个后台(平台)。没上传之前不能被覆盖。

可能有人说直接在一个记录表里就可以搞定了,或者写几个sql语句可以实现。但是数据量很大时效率是个问题。

以下为实现:

package com.example.yang.testmvvm.database.table;

import org.litepal.annotation.Column;
import org.litepal.crud.LitePalSupport;

/**
 * Created by yangyongzhen on 2018/08/07
 *
 * RecordDir,记录表对应的目录表,用来对记录表进行管理。
 * RecordDir表,记录了当前记录的写的位置及记录读的位置。
 * 可据此实现,查询未上传记录数量,依次上传未上传记录,
 * 记录顺序存储,存满指定容量后从头覆盖存储的方式。
 * 删除记录操作只更新记录目录表的读的位置,从不真正的从记录表删除数据,保证数据的安全性。
 */
public class RecordDir extends LitePalSupport {
    @Column(nullable = false)
    //Litepal数据库自动生成的自增的ID
    private long recNO;
    private long writeID;
    private long readID1;
    private long readID2;
    private long readID3;
    private int  mode;

    private int  upDateFlag;
    private long curWriteID;
    private long curReadID;
    @Column(ignore = true)
    public static final long MaxRecNO = 10;

    public long getRecNO() {
        return recNO;
    }

    public long getWriteID() {
        return writeID;
    }

    public long getReadID1() {
        return readID1;
    }

    public long getReadID2() {
        return readID2;
    }

    public long getReadID3() {
        return readID3;
    }

    public long getCurReadID() {
        return curReadID;
    }

    public int getMode() {
        return mode;
    }
    public long getCurWriteID() {
        return curWriteID;
    }

    public void setRecNO(long recNO) {
        this.recNO = recNO;
    }

    public void setWriteID(long writeID) {
        this.writeID = writeID;
    }

    public void setReadID1(long readID1) {
        this.readID1 = readID1;
    }

    public void setReadID2(long readID2) {
        this.readID2 = readID2;
    }

    public void setReadID3(long readID3) {
        this.readID3 = readID3;
    }

    public void setMode(int mode) {
        this.mode = mode;
    }
    public void setCurWriteID(long curWriteID) {
        this.curWriteID = curWriteID;
    }
    public void setCurReadID(long curReadID) {
        this.curReadID = curReadID;
    }

    public int getUpDateFlag() {
        return upDateFlag;
    }

    public void setUpDateFlag(int upDateFlag) {
        this.upDateFlag = upDateFlag;
    }

    @Override
    public String toString() {
        return "RecordDir{" +
                "recNO=" + recNO +
                ", writeID=" + writeID +
                ", readID1=" + readID1 +
                ", readID2=" + readID2 +
                ", readID3=" + readID3 +
                ", mode=" + mode +
                ", upDateFlag=" + upDateFlag +
                ", curWriteID=" + curWriteID +
                ", curReadID=" + curReadID +
                '}';
    }
}

记录操作的接口:

package com.example.yang.testmvvm.database;

import android.content.Context;
import android.util.Log;

import com.example.yang.testmvvm.app.App;
import com.example.yang.testmvvm.database.table.Record01;
import com.example.yang.testmvvm.database.table.RecordDir;

import org.litepal.LitePal;

/**
 * Created by yangyongzhen on 2018/08/07
 * 实现记录的常用操作接口:记录存储,查询未上传记录数,读取未上传记录,删除记录
 */
public class RecordApi {

    public RecordDir recDir01;

    private static Context context;
    private static RecordApi instance = null;

    private RecordApi(Context contxt){
        context = contxt;
        recDir01 = LitePal.find(RecordDir.class, 1);
        if(recDir01 == null){
            recDir01 = new RecordDir();
            recDir01.setWriteID(0);
            recDir01.setReadID1(0);
            recDir01.setRecNO(0);
            recDir01.setUpDateFlag(0);
            recDir01.save();
        }

    }

    /**
     * 保存记录
     * @param rec
     * @return
     */
    public int saveRec01( Record01 rec){
        if(rec == null){
            return 1;
        }
        if(recDir01.getWriteID()+1 > RecordDir.MaxRecNO){
            if((recDir01.getWriteID() + 1 - RecordDir.MaxRecNO) == recDir01.getReadID1()){
                return 2;//记录满
            }
            recDir01.setWriteID(1);
            recDir01.setUpDateFlag(1);
            rec.update(1);
        }
        else {
            if(recDir01.getUpDateFlag() == 1){
                if((recDir01.getWriteID() + 1) == recDir01.getReadID1()){
                    return 3;//记录满
                }
                rec.update(recDir01.getWriteID()+1);
                recDir01.setCurWriteID(recDir01.getWriteID()+1);
                recDir01.setRecNO(recDir01.getRecNO() + 1);
                recDir01.setWriteID(recDir01.getWriteID() + 1);
                recDir01.update(1);
            }
            else {
                rec.save();
                recDir01.setRecNO(recDir01.getRecNO() + 1);
                recDir01.setWriteID(recDir01.getWriteID() + 1);
                recDir01.setCurWriteID(recDir01.getWriteID()+1);
                recDir01.update(1);
            }
        }
        Log.d("WriteRec:",recDir01.toString());
        return 0;
    }

    /**
     * 删除记录,实际上只更改记录目录表的读指针,并不删除记录表的数据
     * 记录表的数据采取循环存储,循环覆盖的模式,保证安全性
     * @param recnum
     * @return
     */
    public int decRec01(long recnum){

        long id = recDir01.getReadID1();
        if(recDir01.getWriteID() == recDir01.getReadID1()){
            return 0;
        }
        if((id + recnum) > RecordDir.MaxRecNO){
            if((id + recnum - RecordDir.MaxRecNO) > recDir01.getWriteID() ){
                recDir01.setReadID1(recDir01.getWriteID());
                recDir01.setCurReadID(recDir01.getWriteID());
                recDir01.update(1);
                return 0;
            }
            recDir01.setReadID1(id + recnum - RecordDir.MaxRecNO);
            recDir01.setCurReadID(id + recnum - RecordDir.MaxRecNO);
            recDir01.update(1);
        }else {
            if(recDir01.getWriteID() > recDir01.getReadID1()){
                if(id + recnum > recDir01.getWriteID()){
                    recDir01.setReadID1(recDir01.getWriteID());
                    recDir01.setCurReadID(recDir01.getWriteID());
                    recDir01.update(1);
                    return 0;
                }
            }
            recDir01.setReadID1(id + recnum);
            recDir01.setCurReadID(id + recnum);
            recDir01.update(1);
        }
        return 0;
    }

    /**
     * 获取未上传的记录条数
     * @return
     */
    public long getRecUnNum()
    {
        long num = 0;
        if(recDir01.getWriteID() == recDir01.getReadID1()){
            num = 0;
            return num;
        }
        if(recDir01.getUpDateFlag() == 0){
            num = (recDir01.getWriteID() - recDir01.getReadID1());
        }else{
            if(recDir01.getWriteID() > recDir01.getReadID1()){
                num = (recDir01.getWriteID() - recDir01.getReadID1());
            }else{
                num = RecordDir.MaxRecNO - recDir01.getReadID1() + recDir01.getWriteID();
            }
        }
        return num;
    }

    /**
     * 读取未上传的记录数据,顺序读取
     * sn取值 1-到-->未上传记录数目
     * @param sn
     * @return
     */
    public Record01 readRecNotServer(long sn){

        Record01 rec;
        long id = recDir01.getReadID1();
        if((id + sn) > RecordDir.MaxRecNO){
            if(id + sn - RecordDir.MaxRecNO > recDir01.getWriteID()){
                return null;
            }
            rec = LitePal.find(Record01.class, id + sn - RecordDir.MaxRecNO );
        }else {
            if(recDir01.getReadID1() < recDir01.getWriteID()){
                if((id + sn) > recDir01.getWriteID()){
                    return null;
                }
            }
            rec = LitePal.find(Record01.class, recDir01.getReadID1() + sn);

        }
        return rec;
    }

    public static RecordApi getInstance(){
        if(instance == null){
            instance = new RecordApi(App.getContext());
        }
        return instance;
    }
}

一个测试 demo:

btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //String anotherName = "John Doe";
                //myViewModel.getName().setValue(anotherName);
                //myViewModel.loginmodel.login("qq8864","123456", LoginType.TYPE_LOGIN);
                Record01 rec = new Record01();
                rec.recType = 1;
                RecordApi api = RecordApi.getInstance();
                api.saveRec01(rec);
                long unrec = api.getRecUnNum();
                Record01 rdrec;
                Log.d("un send rec num:",String.valueOf(unrec));
                for(int i =0 ;i < unrec;i++){
                    rdrec = api.readRecNotServer(1+i);
                    Log.d("recDebug:",rdrec.toString());
                }

            }
        });

        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //mThread = new LongTimeWork();
                //mThread.start();
                /*
                sysCfg.ver.value = "1234";
                sysCfg.ip.value = "218.28.133.181";
                sysCfg.saveConfig();
                sysCfg.printConfig();
                sysCfg.ver.value = "5678";
                sysCfg.saveConfig();
                sysCfg.printConfig();
                */
                RecordApi api = RecordApi.getInstance();
                api.decRec01(1);
                Log.d("del rec:",String.format("curwriteid:%d,curreadid:%d",api.recDir01.getCurWriteID(),api.recDir01.getCurReadID()));

            }
        });

测试日志:

D/WriteRec:: RecordDir{recNO=81, writeID=9, readID1=2, readID2=0, readID3=0, mode=0, upDateFlag=1, curWriteID=9, curReadID=2} D/un send rec num:: 7 D/recDebug:: Record01{id=3, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0} D/recDebug:: Record01{id=4, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0} D/recDebug:: Record01{id=5, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0} D/recDebug:: Record01{id=6, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0} D/recDebug:: Record01{id=7, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0} D/recDebug:: Record01{id=8, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0} D/recDebug:: Record01{id=9, physn=0, recType=1, psamTid='null', lineID=0, dealTime='null', cardAsn='null', dealMoney=0, oldBalance=0}

至此,操作记录存储以及上传未上传记录变得很简单了。

首先一开始先定义好Recod01表结构。

存记录时:

Record01 rec = new Record01();
rec.recType = 1;//赋值
RecordApi api = RecordApi.getInstance();
api.saveRec01(rec);//存储
long unrec = api.getRecUnNum();//获取未上传记录数
Record01 rdrec;
Log.d("un send rec num:",String.valueOf(unrec));
//依此上传未上传记录
for(int i =0 ;i < unrec;i++){
    rdrec = api.readRecNotServer(1+i);
    Log.d("recDebug:",rdrec.toString());
}

//删除已上传记录:

RecordApi api = RecordApi.getInstance();
api.decRec01(unrec);

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 记一个AddressSanitizer(ASAN)linux下的内存分析神器的问题

    感谢万能的互联网,我向google提出的issue很快就得到了工程师回复,解决了我的问题。

    特立独行的猫a
  • go语言微信公众号开发后台接口封装

    业余时间做了个有意思的小功能,每天早上7点准时给发天气预报,每晚8点发布一条英语说说,提醒自己不能忘记学习。

    特立独行的猫a
  • log4go源码分析(一)

    一直想研究分享几个开源项目提高提高,但由于工作忙一直没时间。今天把业余时间总结的log4go源码分析的第一篇记录下来。

    特立独行的猫a
  • PyTorch版本DCGAN实现的注解

    该篇博文是对PyTorch官方Examples中DCGAN(Deep Convolution Generative Adversarial Networks)实...

    卡尔曼和玻尔兹曼谁曼
  • 如何利用各种“桥梁”,建立跨国沟通的信任

    游戏业务运维能够接触现网环境作为玩家与业务项目组、代理开发商之间的”桥梁“,同时也作为业务项目组与代理开发商之间的技术”桥梁“,处于一个核心枢纽岗位的游戏运维人...

    腾讯大讲堂
  • 合理利用延迟初始化优化 Spring Boot

    随着我们项目的不断迭代 Bean 的数量会大大增加,如果都在启动时进行初始化会非常耗时。Spring Boot 允许延迟初始化应用程序, 也就是根据需要初始化 ...

    码农小胖哥
  • “骇极杯”全国大学生网络安全邀请赛WriteUp

    这里看到需要伪造ip 在头中伪造ip只有几种情况:xff xci clientip remoteaddr

    安恒网络空间安全讲武堂
  • 纯CSS实现吸附效果

    「吸附效果」就是网页滚到到某个位置,元素固定在该位置,后续不随网页滚动而滚动。吸附效果很常见,譬如吸顶效果和吸底效果,经常用在跟随导航、移动广告和悬浮提示等场景...

    JowayYoung
  • NSQ深入与实践

    1. 介绍 最近在研究一些消息中间件,常用的MQ如RabbitMQ,ActiveMQ,Kafka等。NSQ是一个基于Go语言的分布式实时消息平台,它基于MIT开...

    aoho求索
  • dotNET Core 3.X 请求处理管道和中间件的理解

    理解 dotNET Core 中的管道模型,对我们学习 dotNET Core 有很大的好处,能让我们知其然,也知其所以然,这样在使用第三方组件或者自己写一些扩...

    oec2003

扫码关注云+社区

领取腾讯云代金券