You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 07:44:55