首页

源码搜藏网

首页 > 安卓源码 > 技术博客 >

从Android资源中读取SQLite数据库的方法

创建时间:2018-08-08 23:20  浏览

演示一种操作位于资产文件夹中的SQLite数据库的方法

介绍

从Android Asset Resource改进SQLite文件处理性能

背景

  1. Android SQLiteDatabase
  2. Android资产处理
  3. SQLite VFS
  4. SQLite URI

使用代码

一般来说,如果我们想从Android资产文件中读取SQLite数据库,我们必须将资产文件复制到本地文件夹,然后从本地文件中读取数据库。

这种方法有一些缺点:

  1. 如果数据库文件大小有点大,则会浪费磁盘使用量
  2. 浪费CPU
  3. 安全易受攻击的
    用户可以在root设备之后替换本地数据库文件

为了克服这些缺点,我们将推出支持Android资源资源的新SQLite VFS。

SQLite在Unix OS上使用unix-vfs并在Window OS上使用win32-vfs

Android包装SQLite代码并导出一些java接口(android.database.sqlite.SQLiteDatabase),但遗漏了原始SQLite实现的一些高级功能

  1. 自定义功能
  2. 加密
  3. URI文件语法
  4. VFS
实际上Android已经在libsqlite.so中包含了上述功能,但是没有提供Java接口/入口点,并且根据Android O +安全行为的变化,开发人员不得在后一个Android版本中访问libsqlite.so

幸运的是,SQLite开发人员已经提供了类似的Java包装器:SQLite Android Bindings,它可以提供这些功能

类名和成员名大多相同,因此您可以导入aar并更改您的java源导入

import android.database.sqlite.SQLiteDatabase;

import org.sqlite.database.sqlite.SQLiteDatabase;

使用步骤

  1. 禁用
     build.gradle 中android资产的SQLiteDatabase文件的压缩
    aaptOptions {
        noCompress 'db'
    }
  2. 实现SQLite VFS并注册它
    sqlite3_vfs_register(&AndroidAsset::vfs, false);
    sqlite3_vfs_register(&AssetFDMap::vfs, false);
    sqlite3_vfs_register(&AssetFD::vfs, false);
  3. 使用custome URI打开asset文件夹中的数据库文件 

我为不同的scenerio实现了三个VFS 

第一个VFS:android_asset

Java(使用自定义SQLite URI打开SQLiteDatabase):

try (SQLiteDatabase db = SQLiteDatabase.openDatabase("file:asset_db.db?vfs=android_asset&immutable=1&mode=ro", null, SQLiteDatabase.OPEN_READONLY)) {
   ................................
}

本机:

static int xRead(sqlite3_file *file, void *buf, int iAmt, sqlite3_int64 iOfst) {
    vfs_file *f = (vfs_file *) file;
    int expectReadLen = (iAmt + iOfst > f->length) ? (f->length - iOfst) : iAmt;
    int readLen = pread64(f->fd, buf, expectReadLen, iOfst + f->offset);
    if (readLen < 0) {
        return SQLITE_IOERR_READ;
    } else if (readLen == expectReadLen) {
        return SQLITE_OK;
    } else {
        memset((__uint8_t *) buf + readLen, 0, iAmt - readLen);
        return SQLITE_IOERR_SHORT_READ;
    }
}

static int vfsOpen(sqlite3_vfs *vfs, const char *path, sqlite3_file *file, int flags,
                   int *outflags) {

    ALOGD("%s:: path=%s flags=%x", __FUNCTION__, path, flags);
    if (g_AAssetManager == NULL) {
        return SQLITE_ERROR;
    }
    vfs_file *f = (vfs_file *) file;
    f->pMethods = &vfs_io_methods;
    AAsset *asset = AAssetManager_open(g_AAssetManager, path, AASSET_MODE_RANDOM);
    if (asset == NULL) {    //if the asset file don't exist
        return SQLITE_NOTFOUND;
    }
    f->fd = AAsset_openFileDescriptor64(asset, &f->offset, &f->length);
    AAsset_close(asset);
    if (f->fd < 0) {  //if the asset file is compressed
        return SQLITE_NOTFOUND;
    }
    *outflags = flags;
    return SQLITE_OK;
}

第二个VFS:asset_fd_map

Java(使用自定义SQLite URI打开SQLiteDatabase):

try (AssetFileDescriptor afd = getAssets().openFd("asset_db.db")) {
    try (SQLiteDatabase db = SQLiteDatabase.openDatabase(String.format("file:%X_%X_%X?vfs=asset_fd_map&immutable=1&mode=ro", afd.getParcelFileDescriptor().getFd(), afd.getStartOffset(), afd.getLength()), null, SQLiteDatabase.OPEN_READONLY)) {
     .................................
  }
}

本机:

static int xRead(sqlite3_file *file, void *buf, int iAmt, sqlite3_int64 iOfst) {
    vfs_file *f = (vfs_file *) file;
    int expectReadLen = (iAmt + iOfst > f->length) ? (f->length - iOfst) : iAmt;
    memcpy(buf, (__uint8_t *) f->address + iOfst + f->offset, expectReadLen);
    int readLen = expectReadLen;
    return SQLITE_OK;
}

static int vfsOpen(sqlite3_vfs *vfs, const char *path, sqlite3_file *file, int flags,
                   int *outflags) {

    ALOGD("%s:: path=%s flags=%x", __FUNCTION__, path, flags);
    vfs_file *f = (vfs_file *) file;
    f->pMethods = &vfs_io_methods;
    if (3 > sscanf(path, "%x_%llx_%llx", &f->fd, &f->offsetFileStart, &f->length)) {
        return SQLITE_ERROR;
    }
    //because mmap() require the offset must be on the page boundary
    __int64_t offsetToPage = (f->offsetFileStart / 4096) * 4096;
    f->offsetMapStart = f->offsetFileStart - offsetToPage;
    f->mapLength = f->length + f->offsetMapStart;

    f->address = mmap64(NULL, f->mapLength, PROT_READ, MAP_PRIVATE, f->fd, offsetToPage);
    if (f->address == MAP_FAILED) {
        return SQLITE_ERROR;
    }
    *outflags = flags;
    return SQLITE_OK;
}

第三个VFS:asset_fd

Java(使用自定义SQLite URI打开SQLiteDatabase):

try (AssetFileDescriptor afd = getAssets().openFd("asset_db.db")) {
    try (SQLiteDatabase db = SQLiteDatabase.openDatabase(String.format("file:%X_%X_%X?vfs=asset_fd&immutable=1&mode=ro", afd.getParcelFileDescriptor().getFd(), afd.getStartOffset(), afd.getLength()), null, SQLiteDatabase.OPEN_READONLY)) {
     .................................
  }
}

本机:

static int xRead(sqlite3_file *file, void *buf, int iAmt, sqlite3_int64 iOfst) {
    vfs_file *f = (vfs_file *) file;
    int expectReadLen = (iAmt + iOfst > f->length) ? (f->length - iOfst) : iAmt;
    int readLen = pread64(f->fd, buf, expectReadLen, iOfst + f->offset);
    if (readLen < 0) {
        return SQLITE_IOERR_READ;
    } else if (readLen == expectReadLen) {
        return SQLITE_OK;
    } else {
        memset((__uint8_t *) buf + readLen, 0, iAmt - readLen);
        return SQLITE_IOERR_SHORT_READ;
    }
}

static int vfsOpen(sqlite3_vfs *vfs, const char *path, sqlite3_file *file, int flags,
                   int *outflags) {

    ALOGD("%s:: path=%s flags=%x", __FUNCTION__, path, flags);
    vfs_file *f = (vfs_file *) file;
    f->pMethods = &vfs_io_methods;
    if (3 > sscanf(path, "%x_%llx_%llx", &f->fd, &f->offset, &f->length)) {
        return SQLITE_ERROR;
    }
    *outflags = flags;
    return SQLITE_OK;
}

在演示项目中,我比较了不同的方法。

经过时间差异似乎不是太大,可能是因为性能瓶颈是SQLite内部数据处理,而不是文件处理

从Android资源中读取SQLite数据库的方法
上一篇:如何创建Xamarin Android Single View应用程序将详细介绍
下一篇:使用Java和XML为Android创建自己的控件

相关内容

热门推荐