Android SQLite数据库资产资源读取优化

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; }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485