Android数据库

一、SQLite

1、SQLite介绍

1.1、简介

SQLite是一款轻型的数据库,是遵守ACID的关联式数据库管理系统。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了样比起Mysql、PostgreSQL这两款开源世界著名的数据库管理系统来讲,它的处理速度比他们都快。

ACID:指数据库事务正确执行的四个基本要素的缩写。包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

1.2、特点
  • 轻量级
    它是进程内的数据库引擎,只需要带上它的一个动态库,就可以享受它的全部功能
  • 不需要"安装"
  • 单一文件
  • 跨平台/可移植性
  • 弱类型的字段
    同一列中的数据可以是不同类型
  • 开源
1.3、SQLite数据类型
  • NULL: 这个值为空值
  • VARCHAR(n):长度不固定且其最大长度为 n 的字串,n不能超过 4000。
  • CHAR(n):长度固定为n的字串,n不能超过 254。
  • INTEGER: 值被标识为整数,依据值的大小可以依次被存储为1,2,3,4,5,6,7,8
  • REAL: 所有值都是浮动的数值,被存储为8字节的IEEE浮动标记序号
  • TEXT: 值为文本字符串,使用数据库编码存储(TUTF-8, UTF-16BE or UTF-16-LE).
  • BLOB: 值是BLOB数据块,以输入的数据格式进行存储。如何输入就如何存储,不改变格式。
  • DATE :包含了 年份、月份、日期。
  • TIME: 包含了 小时、分钟、秒。

2、SQLiteDatabase的介绍

Android提供了创建和使用SQLite数据库的API。SQLiteDatabase代表一个数据库对象,提供了操作数据库的一些方法。在Android的SDK目录下有sqlite3工具,我们可以利用它创建数据库、创建表和执行一些SQL语句。下面是SQLiteDatabase的常用方法。

  • openOrCreateDatabase(String path,SQLiteDatabase.CursorFactory factory)
    打开或创建path文件代表的SQLite数据库,factory一般赋值为null,使用默认的工厂。存在则打开,不存在则创建一个数据库;创建成功则返回一个SQLiteDatabase对象,否则抛出异常FileNotFoundException
  • execSQL(String sql) 执行一条SQL语句
  • execSQL(String sql, Object[] bindArgs) 执行一条SQL语句,防止注入攻击
  • rawQuery(String sql, String[] selectionArgs) Cursor 执行查询语句
  • close() 关闭数据库
  • insert(String table,String nullColumnHack,ContentValues values) long
    插入一条记录。nullColumnHack指定强行插入null值的数据列名,ContentValues 类似于Map;返回插入的行号,若发生错误返回-1
  • delete(String table,String whereClause,String[] whereArgs) int
    删除一条记录
  • query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy[, String limit]) Cursor
    查询一条记录。limit参数控制最多查询几条记录(用于控制分页的参数);distinct参数控制是否去除重复的值
  • update(String table,ContentValues values,String whereClause,String[] whereArgs) int
    修改记录。whereArgs子句传入的参数
  • beginTransaction() void
  • endTransaction() void 只有当执行了setTransactionSuccessful()时,才会进行事物的提交,否则回滚
  • inTransaction() boolean 是否处于事务之中
2.1、Cursor

类似于ResultSet

  • move(int offset) void 向上或向下移动记录指针
  • moveToFirst() boolean
  • moveToLast() boolean
  • moveToNext() boolean
  • moveToPrevious() boolean
  • moveToPosition(int position) boolean
  • close() void
  • getCount() int
  • getXxx(int index) xxx
  • getColumnIndex(String columnName) int
2.2、ContentValues

类似于Map

  • keySet() Set<String>
  • put(String key,int value) void
  • get(String key) Object
  • getAsInteger(String key) Integer

3、SQLiteOpenHelper

3.1、概述
  • 管理数据库的一个工具类,用于管理数据库的创建和版本更新
  • 实际项目中,很少使用SQLiteDatabase的方法来打开数据库,通常会继承SQLiteOpenHelper开发子类,然后调用getWritableDatabase()getReadableDatabase()打开数据库。
3.2、方法
  • SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) name:数据库名;CursorFactory置为null,version:数据库版本
  • onCreate(SQLiteDatabase db) void 第一次创建数据库的时候回调该方法,通常:新建表操作
  • onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) void 当数据库版本更新时回调该方法,通常:删除表,再新建表
  • getReadableDatabase() SQLiteDatabase 先以读写的方式打开数据库,若磁盘满了,再以读的方式打开对应的SQLiteDatabase对象,若无则创建,此时才进行创建
  • getWritableDatabase() SQLiteDatabase 以读写的方式打开数据库对应的SQLiteDatabase对象,若无则创建
  • close() void //关闭所有打开的SQLiteDatabase

4、实例

创建数据库
StuDBHelper.java

public class StuDBHelper extends SQLiteOpenHelper {
    private static final String TAG = "TestSQLite";
    public static final int VERSION = 1;

    //必须要有构造函数
    public StuDBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
                       int version) {
        super(context, name, factory, version);
    }

    // 当第一次创建数据库的时候,调用该方法
    public void onCreate(SQLiteDatabase db) {
        String sql = "create table stu_table(id int,sname varchar(20),sage int,ssex varchar(10))";
        //输出创建数据库的日志信息
        Log.i(TAG, "create Database------------->");
        //execSQL函数用于执行SQL语句
        db.execSQL(sql);
    }

    //当更新数据库的时候执行该方法
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //输出更新数据库的日志信息
        Log.i(TAG, "update Database------------->");
    }
}

UI界面比较简单,就不给出代码了。如下图:

MainActivity.java

public class MainActivity extends Activity {
    //省略部分代码
    ... ...
    //为按钮注册监听的方法
    private void setListener() {
        createBtn.setOnClickListener(new CreateListener());
        updateBtn.setOnClickListener(new UpdateListener());
        insertBtn.setOnClickListener(new InsertListener());
        ModifyBtn.setOnClickListener(new ModifyListener());
        queryBtn.setOnClickListener(new QueryListener());
        deleteBtn.setOnClickListener(new DeleteListener());
    }

    //创建数据库的方法
    class CreateListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            //创建StuDBHelper对象
            StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 1);
            //得到一个可读的SQLiteDatabase对象
            SQLiteDatabase db = dbHelper.getReadableDatabase();
        }
    }

    //更新数据库的方法
    class UpdateListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            // 数据库版本的更新,由原来的1变为2
            StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 2);
            SQLiteDatabase db = dbHelper.getReadableDatabase();
        }
    }

    //插入数据的方法  
    class InsertListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 1);
            //得到一个可写的数据库
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            //方法1
            //生成ContentValues对象 //key:列名,value:想插入的值
            ContentValues cv = new ContentValues();
            //往ContentValues对象存放数据,键-值对模式
            cv.put("id", 1);
            cv.put("sname", "xiaoming");
            cv.put("sage", 21);
            cv.put("ssex", "male");
            //调用insert方法,将数据插入数据库
            db.insert("stu_table", null, cv);
            
            //方法2,效率更高点
            String sb = "INSERT INTO stu_table(id,sname,sage,ssex) " +
                    " VALUES( ?, ?, ?, ?)";
            SQLiteStatement statement = db.compileStatement(sb);//
            statement.bindLong(1, 2);
            statement.bindString(2, "xyh");
            statement.bindLong(3, 22);
            statement.bindString(4, "male");
            statement.executeInsert();
            //关闭数据库
            db.close();
        }
    }

    //查询数据的方法  
    class QueryListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 1);
            //得到一个可写的数据库
            SQLiteDatabase db = dbHelper.getReadableDatabase();
            //参数1:表名
            //参数2:要想显示的列
            //参数3:where子句
            //参数4:where子句对应的条件值
            //参数5:分组方式
            //参数6:having条件
            //参数7:排序方式
            Cursor cursor = db.query("stu_table", new String[]{"id", "sname", 
            "sage", "ssex"}, "id=?", new String[]{"1"}, null, null, null);
            while (cursor.moveToNext()) {
                String name = cursor.getString(cursor.getColumnIndex("sname"));
                String age = cursor.getString(cursor.getColumnIndex("sage"));
                String sex = cursor.getString(cursor.getColumnIndex("ssex"));
                System.out.println("query-->" + "姓名:" + name + " " + "年龄:" 
                + age + " " + "性别:" + sex);
            }
            //关闭数据库
            db.close();
        }
    }

    //修改数据的方法  
    class ModifyListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 1);
            //得到一个可写的数据库
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            ContentValues cv = new ContentValues();
            cv.put("sage", "23");
            //where 子句 "?"是占位符号,对应后面的"1",
            String whereClause = "id=?";
            String[] whereArgs = {String.valueOf(1)};
            //参数1 是要更新的表名
            //参数2 是一个ContentValeus对象
            //参数3 是where子句
            db.update("stu_table", cv, whereClause, whereArgs);
        }
    }

    //删除数据的方法  
    class DeleteListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            StuDBHelper dbHelper = new StuDBHelper(MainActivity.this, "stu_db", null, 1);
            //得到一个可写的数据库
            SQLiteDatabase db = dbHelper.getReadableDatabase();
            String whereClauses = "id=?";
            String[] whereArgs = {String.valueOf(2)};
            //调用delete方法,删除数据
            db.delete("stu_table", whereClauses, whereArgs);
        }
    }
}

二、SQLCipher

Sqlite数据库默认存放位置data/data/pakage/database目录下,对于已经ROOT的手机来说的没有任何安全性可以,一旦被利用将会导致数据库数据的泄漏,所以我们该如何避免这种事情的发生呢?我们尝试这对数据库进行加密。

1、简介

SQLCipher是一个在SQLite基础之上进行扩展的开源数据库,它主要是在SQLite的基础之上增加了数据加密功能,如果我们在项目中使用它来存储数据的话,就可以大大提高程序的安全性。

优势:
加密性能高、开销小,只要5-15%的开销用于加密
完全做到数据库100%加密
采用良好的加密方式(CBC加密模式)
使用方便,做到应用级别加密
采用OpenSSL加密库提供的算法

2、使用方法

  1. 导入包或者添加依赖
    compile group: 'net.zetetic', name: 'android-database-sqlcipher', version: '3.5.9'
  2. 创建一个SQLiteOpenHelper 注意接下来所以有关Sqlite相关类全部引用net.sqlcipher.database的类
    注意:SQLiteDatabase.loadLibs(context)这个千万别忘记调用
/**
 * Created by xiang on 2018/1/26.
 */
import android.content.Context;
import android.util.Log;

import net.sqlcipher.SQLException;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteOpenHelper;

public class DBCipherHelper extends SQLiteOpenHelper {
    private static final String TAG = "DatabaseHelper";
    private static final String DB_NAME = "test_cipher_db";//数据库名字
    public static final String DB_PWD = "whoislcj";//数据库密码
    public static String TABLE_NAME = "person";// 表名
    public static String FIELD_ID = "id";// 列名
    public static String FIELD_NAME = "name";// 列名
    private static final int DB_VERSION = 1;   // 数据库版本

    public DBCipherHelper(Context context, String name, 
    SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        //不可忽略的 进行so库加载,使用前调用即可
        SQLiteDatabase.loadLibs(context);
    }

    public DBCipherHelper(Context context) {
        this(context, DB_NAME, null, DB_VERSION);
    }

    /**
     * 创建数据库
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        //创建表
        createTable(db);
    }

    private void createTable(SQLiteDatabase db) {
        String sql = "CREATE TABLE " + TABLE_NAME + "(" + FIELD_ID 
        + " integer primary key autoincrement , "
        + FIELD_NAME + " text not null);";
        try {
            db.execSQL(sql);
        } catch (SQLException e) {
            Log.e(TAG, "onCreate " + TABLE_NAME + "Error" + e.toString());
            return;
        }
    }

    /**
     * 数据库升级
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }

}
  1. 接下来的步骤和原来的完全一样,唯一区别
//获取写数据库
SQLiteDatabase db = dbHelper.getWritableDatabase(DBCipherHelper.DB_PWD);//用于加密的秘钥
//获取可读数据库
SQLiteDatabase db = dbHelper.getReadableDatabase(DBCipherHelper.DB_PWD);

三、GreenDAO

1、简介

greenDAO是一种Android数据库ORM(object/relational mapping - 对象关系映射)框架,是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。与OrmLite、ActiveOrm、LitePal等数据库相比,单位时间内可以插入、更新和查询更多的数据,而且提供了大量的灵活通用接口。

设计的主要目标:一个精简的库、性能最大化、内存开销最小化、易于使用的 APIs、对 Android 进行高度优化。

设计的主要特点

  • greenDAO 性能远远高于同类的 ORMLite,具体测试结果可见官网
  • greenDAO 支持 protocol buffer(protobuf) 协议数据的直接存储,如果你通过 protobuf 协议与服务器交互,将不需要任何的映射。
  • 与 ORMLite 等使用注解方式的 ORM 框架不同,greenDAO 使用「Code generation」的方式,这也是其性能能大幅提升的原因。
  • 使用 SQL 语句进行查询容易出错,而且错误比较难以发现,使用 greenDAO 的话可以在编译阶段就发现错误。

2、如何开始

2.1、添加依赖

在project下的build.gradle中添加

dependencies {
    classpath 'com.android.tools.build:gradle:2.2.2'
    classpath 'org.greenrobot:greendao-gradle-plugin:3.2.1'//添加green的gradle插件
}

在app下的build.gradle中添加

apply plugin: 'com.android.application'
//使用greendao
apply plugin: 'org.greenrobot.greendao'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.handsome.didi"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    //greendao配置
    greendao {
        //版本号,升级时可配置
        schemaVersion 1                             
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}


dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
    //greendao依赖
    compile 'org.greenrobot:greendao:3.2.0'
}
2.2、创建Bean对象(表名和字段名)
@Entity
public class Shop {
    //表示为购物车列表
    public static final int TYPE_CART = 0x01;
    //表示为收藏列表
    public static final int TYPE_LOVE = 0x02;

    //不能用int
    @Id(autoincrement = true)
    private Long id;
    //商品名称
    @Unique
    private String name;
    //商品价格
    @Property(nameInDb = "price")
    private String price;
    //已售数量
    private int sell_num;
    //图标url
    private String image_url;
    //商家地址
    private String address;
    //商品列表类型
    private int type;
}

创建完成之后,需要build gradle来完成我们的代码自动生成。自动生成的代码有
Bean实体的构造方法和get、set方法、DaoMaster、DaoSession、ShopDAO类

执行gradle构建之后的Shop.java

@Entity
public class Shop {
    //表示为购物车列表
    public static final int TYPE_CART = 0x01;
    //表示为收藏列表
    public static final int TYPE_LOVE = 0x02;

    //不能用int,不指定会递增,指定了就用指定的值
    @Id(autoincrement = true) 
    private Long id;
    //商品名称
    @Unique
    private String name;
    //商品价格
    @Property(nameInDb = "price")
    private String price;
    //已售数量
    private int sell_num;
    //图标url
    private String image_url;
    //商家地址
    private String address;
    //商品列表类型
    private int type;
    @Generated(hash = 1304458862)
    public Shop(Long id, String name, String price, int sell_num, String image_url,
            String address, int type) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.sell_num = sell_num;
        this.image_url = image_url;
        this.address = address;
        this.type = type;
    }
    @Generated(hash = 633476670)
    public Shop() {
    }
    //省略getter和setter
    ... ...
}
    

对Bean对象的注释进行解释

  • @Entity:表明这个实体类会在数据库中生成一个与之相对应的表。
  • @Id:对象的Id,使用Long类型作为EntityId,否则会报错。(autoincrement = true)表示主键会自增,如果false就会使用旧值
  • @Property:可以自定义字段名,注意外键不能使用该属性,nameInDb属性表示数据库中的字段名
  • @NotNull:属性不能为空
  • @Transient:使用该注释的属性不会被存入数据库的字段中
  • @Unique:该属性值必须在数据库中是唯一值
  • @Generated:编译后自动生成的构造函数、方法等的注释,提示构造函数、方法等不能被修改
2.3、创建数据库(数据库名)
public class BaseApplication extends Application {
    private static DaoSession daoSession;
    @Override
    public void onCreate() {
        super.onCreate();
        //配置数据库
        setupDatabase();
    }

    /**
     * 配置数据库
     */
    private void setupDatabase() {
        //此时并不会创建shop.db,底层就是SQLiteOpenHelper的构造方法
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "shop.db", null);
        //获取可写数据库
        SQLiteDatabase db = helper.getWritableDatabase();
        //获取数据库对象
        DaoMaster daoMaster = new DaoMaster(db);
        //获取Dao对象管理者
        daoSession = daoMaster.newSession();
    }

    public static DaoSession getDaoInstant() {
        return daoSession;
    }
}

可以发现,GreenDao已经将我们的数据库创建缩成几句话,代码会自动将Bean对象创建成表,不再是传统的手写SQL语句。这里的数据库创建只需要在Application中执行一次即可(也可以在别的地方),这里对几个类进行解释

  • DevOpenHelper:创建SQLite数据库的SQLiteOpenHelper的具体实现
  • DaoMaster:GreenDao的顶级对象,作为数据库对象、用于创建表和删除表
  • DaoSession:管理所有的Dao对象,Dao对象中存在着增删改查等API
2.4、数据库的增删改查
public class LoveDao {
    /**
     * 添加数据,如果有重复则覆盖
     *
     * @param shop
     */
    public static void insertLove(Shop shop) {
        BaseApplication.getDaoInstant().getShopDao().insertOrReplace(shop);
    }

    /**
     * 删除数据
     *
     * @param id
     */
    public static void deleteLove(long id) {
        BaseApplication.getDaoInstant().getShopDao().deleteByKey(id);
    }

    /**
     * 更新数据
     *
     * @param shop
     */
    public static void updateLove(Shop shop) {
        BaseApplication.getDaoInstant().getShopDao().update(shop);
    }

    /**
     * 查询条件为Type=TYPE_LOVE的数据
     *
     * @return
     */
    public static List<Shop> queryLove() {
        return BaseApplication.getDaoInstant().getShopDao()
        .queryBuilder().where(ShopDao.Properties.Type.eq(Shop.TYPE_LOVE)).list();
    }

    /**
     * 查询全部数据
     */
    public static List<Shop> queryAll() {
        return BaseApplication.getDaoInstant().getShopDao().loadAll();
    }
}

GreenDao的封装之后短小精悍,语义明朗。下面对GreenDao中Dao对象其他API的介绍

AbstractDao方法:

  • 增加单个数据
    insert(T entity) long
    insertOrReplace(T entity) long
  • 增加多个数据
    insertInTx(T... entities) void
    insertOrReplaceInTx(T... entities) void
  • 删除单个数据
    delete(T entity) void
  • 删除多个数据
    deleteInTx(T... entities) void
  • 删除数据ByKey(key就是@Id字段)
    deleteByKey(K key) void
  • 修改单个数据
    update(T entity) void
  • 修改多个数据
    updateInTx(T... entities) void
  • 查询全部
    loadAll() List<T>
    queryBuilder().list() List<T> queryBuilder()返回的是QueryBuilder<T>

QueryBuilder<T> 方法:

  • 查询附加单个条件或多个条件
    where(WhereCondition cond, WhereCondition... condMore) QueryBuilder<T> cond:如ShopDao.Properties.Id = 12
    whereOr(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore) QueryBuilder<T>
  • 查询附加排序
    orderDesc(Property... properties) QueryBuilder<T> properties:如ShopDao.Properties.Id
    orderAsc(Property... properties) QueryBuilder<T>
  • 查询限制当页个数
    limit(int limit) QueryBuilder<T>
  • 查询总个数
    count() long
  • 出口
    list() List<T>

Property 方法:

  • eq(Object value) WhereCondition
  • notEq(Object value) WhereCondition
  • like(String value) WhereCondition
  • between(Object value1, Object value2) WhereCondition
    ... ...
2.4、基于Sqlcipher和GreenDao的数据库加密

需要下载greendao的源码,替换其SQLiteDatabase等对象,具体参考:基于Sqlcipher和GreenDao的数据库加密

四、WCDB

WCDB 是腾讯出品的一个高效、完整、易用的移动数据库框架,基于SQLCipher,支持iOS, macOS和Android,还内建了Repair Kit用于修复损坏的数据库以及内置的防SQL注入。
项目地址:https://github.com/Tencent/wcdb
使用方法:

dependencies {
    compile 'com.tencent.wcdb:wcdb-android:1.0.2'
}

和在 android 上使用 SQLite 一样,先创建一个 WcdbHelper 继承 SQLiteOpenHelper 类,注意包名这里是 com.tencent.wcdb.database 。

class WcdbHelper extends SQLiteOpenHelper{
    public WcdbHelper(Context context, String name, byte[] password, 
    SQLiteDatabase.CursorFactory factory, int version,
    DatabaseErrorHandler errorHandler) {
        super(context, name, password, factory, version, errorHandler);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

由于 wcdb 是基于 SQLCipher,所以我们可以为数据库设置一个密码,我们在构造函数里指定密码即可,wcdb 允许 byte[] 作为密码, SQLCipher 使用 String 作为密码。

File cdb = new File(getExternalFilesDir("db"), "hello.wcdb");
WcdbHelper wcdbHelper = new WcdbHelper(
        getApplicationContext(),
        cdb.getAbsolutePath(),
        "password".getBytes(), null, 1, null
);

接下来在这张表里创建一条数据:

String sql = "INSERT INTO \"main\".\"address_book\" (\"name\", \"age\", \"address\") VALUES (?, ?, ?)";
wcdbHelper.getWritableDatabase().execSQL(sql, new String[]{"dummy", "28", "Hubei wuhan"});

这样,在 hello.wcdb 这个数据库中就存在了一条数据,当我们像打开 sqlite 数据库时打开这个 wcdb 数据库时候,提示错误,这是正常的,因为它被加密了,这样无论手机是否 root 或是我们把数据库存放在外置存储中,对于我们 app 的敏感数据来说,他们依然是安全的。

总得来说,如果会使用 sqlite 的话,那么 wcdb 也不在话下,并且迁移成本也很小。唯一的不足是,wcdb 官方提到的 winq 查询在 android 上并不支持,但是官方也说在今后会提供其他方式的 orm 以方便数据库操作。

参考文献

Android 操作SQLite基本用法
Android数据存储之SQLCipher数据库加密
Android应用性能优化之使用SQLiteStatement优化SQLite操作
Android实战——GreenDao3.2的使用,爱不释手
greenDAO 3.2 初探
在Android上使用微信开源数据库框架WCDB

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

推荐阅读更多精彩内容