在Android应用开发中,SQLite数据库是一种常用的数据存储方式。通常情况下,如果开发者想要从Android的资产文件(asset)中读取SQLite数据库,他们需要先将资产文件复制到本地文件夹,然后再从本地文件中读取数据库。这种方法存在一些不足之处,例如浪费磁盘空间、消耗CPU资源以及安全风险。为了克服这些缺点,可以实施一种新的SQLite VFS,以支持Android资产资源的读取。
SQLite在Unix操作系统上使用unix-vfs,在Windows操作系统上使用win32-vfs。Android封装了SQLite代码并提供了一些Java接口(android.database.sqlite.SQLiteDatabase),但缺少了一些原始SQLite实现中的高级特性。
加密、URI文件语法和VFS是SQLite数据库的一些高级特性。实际上,Android已经包含了上述特性在libsqlite.so中,但没有提供Java接口/入口点。根据Android O+的安全行为变化,开发者在后续的Android版本中不应访问libsqlite.so。幸运的是,SQLite开发者已经提供了一个类似的Java包装器:SQLite Android Bindings,它可以提供这些特性。
要在Android资产中禁用SQLiteDatabase文件的压缩,在build.gradle中配置:
aaptOptions {
noCompress 'db'
}
实现SQLite VFS并注册:
sqlite3_vfs_register(&AndroidAsset::vfs, false);
sqlite3_vfs_register(&AssetFDMap::vfs, false);
sqlite3_vfs_register(&AssetFD::vfs, false);
使用自定义URI打开资产文件夹中的数据库文件:
为不同场景实现了三种VFS:
Java(使用自定义SQLiteURI打开SQLiteDatabase):
try (SQLiteDatabase db = SQLiteDatabase.openDatabase(
"file:asset_db.db?vfs=android_asset&immutable=1&mode=ro", null, SQLiteDatabase.OPEN_READONLY)) {
// ...
}
Native(C++):
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;
AAsset *asset = AAssetManager_open(g_AAssetManager, path, AASSET_MODE_RANDOM);
if (asset == NULL) {
return SQLITE_NOTFOUND;
}
f->fd = AAsset_openFileDescriptor64(asset, &f->offset, &f->length);
AAsset_close(asset);
if (f->fd < 0) {
return SQLITE_NOTFOUND;
}
*outflags = flags;
return SQLITE_OK;
}
Java(使用自定义SQLiteURI打开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)) {
// ...
}
}
Native(C++):
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;
}
__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;
}
Java(使用自定义SQLiteURI打开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;
}