如何让Android上的SQLDelight通过JNI直接调用sqlite3?
问题背景
- 数据库中包含约3.5MB的大Blob(图片),在Android 9(API 28)及以下版本无法读取,原因是SQLDelight的
AndroidSqliteDriver底层依赖Cursor,该版本以下无法设置更大的自定义游标窗口大小,导致大Blob读取失败;而原生sqlite3查询无此限制。
核心疑问
如何让SQLDelight通过自定义JNI封装直接调用sqlite3 API?是否需要编写自定义驱动类?如果需要,该如何实现?(思路是基于AndroidSqliteDriver替换Cursor调用为JNI调用,但不清楚正确实现方式)
实现方案:自定义SQLDelight驱动
是的,你需要编写自定义驱动类,核心是绕过Cursor,直接通过JNI调用原生sqlite3 API处理大Blob查询。以下是分步实现示例:
1. 编写JNI层代码(C/C++)
创建sqlite3_jni.c文件,封装原生sqlite3的数据库连接、Blob读取逻辑:
#include <jni.h> #include <sqlite3.h> static sqlite3 *db = NULL; JNIEXPORT jint JNICALL Java_com_yourpackage_NativeSqliteDriver_openDatabase(JNIEnv *env, jobject thiz, jstring dbPath) { const char *path = (*env)->GetStringUTFChars(env, dbPath, NULL); int rc = sqlite3_open(path, &db); (*env)->ReleaseStringUTFChars(env, dbPath, path); return rc; } JNIEXPORT jbyteArray JNICALL Java_com_yourpackage_NativeSqliteDriver_readBlob(JNIEnv *env, jobject thiz, jstring query) { if (db == NULL) return NULL; sqlite3_stmt *stmt; const char *queryStr = (*env)->GetStringUTFChars(env, query, NULL); int rc = sqlite3_prepare_v2(db, queryStr, -1, &stmt, NULL); (*env)->ReleaseStringUTFChars(env, query, queryStr); if (rc != SQLITE_OK) { sqlite3_finalize(stmt); return NULL; } jbyteArray blob = NULL; if (sqlite3_step(stmt) == SQLITE_ROW) { int blobSize = sqlite3_column_bytes(stmt, 0); const void *blobData = sqlite3_column_blob(stmt, 0); blob = (*env)->NewByteArray(env, blobSize); (*env)->SetByteArrayRegion(env, blob, 0, blobSize, (jbyte *)blobData); } sqlite3_finalize(stmt); return blob; } JNIEXPORT void JNICALL Java_com_yourpackage_NativeSqliteDriver_closeDatabase(JNIEnv *env, jobject thiz) { if (db != NULL) { sqlite3_close(db); db = NULL; } }
2. 配置CMakeLists.txt(NDK编译)
cmake_minimum_required(VERSION 3.22.1) project("nativesqlite") add_library( nativesqlite SHARED sqlite3_jni.c ) find_library( log-lib log ) target_link_libraries( nativesqlite ${log-lib} )
3. 编写Java层Native驱动类
创建NativeSqliteDriver.java,实现SQLDelight的SqlDriver接口,替换查询逻辑为JNI调用:
package com.yourpackage; import com.squareup.sqldelight.db.SqlCursor; import com.squareup.sqldelight.db.SqlDriver; import com.squareup.sqldelight.db.SqlPreparedStatement; import com.squareup.sqldelight.db.SqlSchema; import java.io.File; import java.util.List; public class NativeSqliteDriver implements SqlDriver { static { System.loadLibrary("nativesqlite"); } private final File dbFile; public NativeSqliteDriver(SqlSchema schema, File dbFile) { this.dbFile = dbFile; openDatabase(dbFile.getAbsolutePath()); schema.create(this); } private native int openDatabase(String dbPath); private native byte[] readBlob(String query); private native void closeDatabase(); @Override public SqlPreparedStatement compile(String sql, boolean persistent) { return new NativePreparedStatement(sql); } @Override public void execute(String sql) { // 参考AndroidSqliteDriver实现非查询语句执行 } @Override public void execute(String sql, List<Object> args) { // 实现带参数的非查询执行 } @Override public long executeInsert(String sql, List<Object> args) { return 0; } @Override public int executeUpdateDelete(String sql, List<Object> args) { return 0; } @Override public void close() { closeDatabase(); } private class NativePreparedStatement implements SqlPreparedStatement { private final String sql; NativePreparedStatement(String sql) { this.sql = sql; } @Override public void bind(int index, Object value) { // 扩展JNI方法实现参数绑定,避免SQL注入 } @Override public SqlCursor executeQuery() { byte[] blobData = readBlob(sql); return new NativeSqlCursor(blobData); } @Override public void execute() { // 非查询执行逻辑 } @Override public void close() { // 资源释放 } } private class NativeSqlCursor implements SqlCursor { private final byte[] blob; private boolean hasNext = true; NativeSqlCursor(byte[] blob) { this.blob = blob; } @Override public boolean next() { boolean result = hasNext; hasNext = false; return result; } @Override public int getColumnCount() { return 1; } @Override public String getColumnName(int index) { return "blob_data"; } @Override public long getLong(int index) { return 0; } @Override public double getDouble(int index) { return 0; } @Override public String getString(int index) { return null; } @Override public byte[] getBlob(int index) { return blob; } @Override public void close() { // 资源释放 } } }
4. 替换默认驱动使用自定义驱动
在应用初始化时,用NativeSqliteDriver替代AndroidSqliteDriver:
File dbFile = new File(getApplicationContext().getFilesDir(), "your_database.db"); YourDatabase database = new YourDatabase(new NativeSqliteDriver(YourDatabase.Schema.INSTANCE, dbFile)); // 查询大Blob database.yourQueries().getLargeBlob().executeAsOne();
注意事项
- 上述示例为简化实现,仅针对Blob查询场景,如需完整支持SQLDelight所有操作,需参考
AndroidSqliteDriver完善SqlDriver接口的所有方法。 - 若需支持带参数的查询,需扩展JNI层参数绑定逻辑,避免SQL注入风险。
- 确保NDK编译配置正确,可使用系统自带sqlite3或静态编译sqlite3源码。
内容的提问来源于stack exchange,提问作者schmidt9
相关产品推荐
相关产品推荐

