NDK篇 - 手动编译 SQLite

2023-05-16

今天来写 NDK 的最后一篇 - SQLite,下一篇文章将进入 Framework 的世界。

SQLite,是一款轻型的数据库,是遵守 ACID 的关系型数据库,它包含在一个相对小的 C 库中。它是 D.RichardHipp 建立的公有领域项目。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式中,可能只需要几百K的内存就够了。它能够支持 Windows/Linux/Unix 等主流的操作系统,同时能够跟很多程序语言相结合,比如 C#、PHP、Java 等。还有 ODBC 接口,比起 Mysql、PostgreSQL 这两款开源的世界著名数据库管理系统来讲,它的处理速度比它们都快。SQLite 第一个版本诞生于2000年5月,至2015年已经有15个年头,SQLite 也迎来了新版本 SQLite 3 的发布。

 

目录:

  1. 获取 SQLite3 源代码
  2. 编写 Java 接口
  3. 编写 JNI 类
  4. Android.mk 和 Application.mk
  5. 编译生成 so 库

 

 

1. 获取 SQLite3 源代码

可以上官网下载源码,SQLite 的源码没有选择使用 github 托管,下载地址:https://sqlite.org/download.html

如果需要快速获取源码调试,可以使用这份:https://github.com/HuiLi/Sqlite3.07.14/tree/master/Sqlite3.07.14-run,不过版本比较旧。

我这边使用的是:SQLite 3.18.0:

sqlite3.h 的代码有一万行,sqlite3.c 的代码有二十多万行。

 

 

2. 编写 Java 接口

SQLiteDatabase.java:

package io.kzw.sqlite3native;

public class SQLiteDatabase {

    native int opendb(String fileName, String tempDir) throws SQLiteException;
    native void closedb(int sqliteHandle) throws SQLiteException;
    native void beginTransaction(int sqliteHandle);
    native void commitTransaction(int sqliteHandle);
}

SQLiteCursor.java:

package io.kzw.sqlite3native;

public class SQLiteCursor {

    native int columnType(int statementHandle, int columnIndex);
    native int columnIsNull(int statementHandle, int columnIndex);
    native int columnIntValue(int statementHandle, int columnIndex);
    native long columnLongValue(int statementHandle, int columnIndex);
    native double columnDoubleValue(int statementHandle, int columnIndex);
    native String columnStringValue(int statementHandle, int columnIndex);
    native byte[] columnByteArrayValue(int statementHandle, int columnIndex);
    native int columnByteBufferValue(int statementHandle, int columnIndex);
}

SQLitePreparedStatement:

package io.kzw.sqlite3native;

public class SQLitePreparedStatement {

	native void bindByteBuffer(int statementHandle, int index, ByteBuffer value, int length) throws SQLiteException;
	native void bindString(int statementHandle, int index, String value) throws SQLiteException;
	native void bindInt(int statementHandle, int index, int value) throws SQLiteException;
    	native void bindLong(int statementHandle, int index, long value) throws SQLiteException;
	native void bindDouble(int statementHandle, int index, double value) throws SQLiteException;
	native void bindNull(int statementHandle, int index) throws SQLiteException;
	native void reset(int statementHandle) throws SQLiteException;
	native int prepare(int sqliteHandle, String sql) throws SQLiteException;
	native void finalize(int statementHandle) throws SQLiteException;
    	native int step(int statementHandle) throws SQLiteException;
}

SQLiteException:

package io.kzw.sqlite3native;

public class SQLiteException extends Exception {

	private static final long serialVersionUID = -2398298479089615621L;

	public final int errorCode;
	
	public SQLiteException(int errcode, String msg) {
		super(msg);
		errorCode = errcode;		
	}

	public SQLiteException(String msg) {
		this(0, msg);
	}

	public SQLiteException() {
		errorCode = 0;
	}
}

 

 

3.  编写 JNI 类

SqliteWrapper.cpp:

#include "sqlite3.h"
#include <jni.h>

void throw_sqlite3_exception(JNIEnv *env, sqlite3 *handle, int errcode) {
    if (SQLITE_OK == errcode) {
        errcode = sqlite3_errcode(handle);
    }
    const char *errmsg = sqlite3_errmsg(handle);
    jclass exClass = env->FindClass("io/kzw/sqlite3native/SQLiteException");
    env->ThrowNew(exClass, errmsg);
}

extern "C" {

int Java_io_kzw_sqlite3native_SQLitePreparedStatement_step(JNIEnv *env, jobject object, int statementHandle) {
    sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle;
    
    int errcode = sqlite3_step(handle);
    if (errcode == SQLITE_ROW)  {
        return 0;
    } else if(errcode == SQLITE_DONE) {
        return 1;
    }  else if(errcode == SQLITE_BUSY) {
        return -1;
    }
    throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode);
}

int Java_io_kzw_sqlite3native_SQLitePreparedStatement_prepare(JNIEnv *env, jobject object, int sqliteHandle, jstring sql) {
    sqlite3 *handle = (sqlite3 *) sqliteHandle;
    
    char const *sqlStr = env->GetStringUTFChars(sql, 0);
    
    sqlite3_stmt *stmt_handle;
    
    int errcode = sqlite3_prepare_v2(handle, sqlStr, -1, &stmt_handle, 0);
    if (SQLITE_OK != errcode) {
        throw_sqlite3_exception(env, handle, errcode);
    }
    
    if (sqlStr != 0) {
        env->ReleaseStringUTFChars(sql, sqlStr);
    }
    
    return (int) stmt_handle;
}

void Java_io_kzw_sqlite3native_SQLitePreparedStatement_reset(JNIEnv *env, jobject object, int statementHandle) {
    sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle;
    
    int errcode = sqlite3_reset(handle);
    if (SQLITE_OK != errcode) {
        throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode);
    }
}

void Java_io_kzw_sqlite3native_SQLitePreparedStatement_finalize(JNIEnv *env, jobject object, int statementHandle) {
    sqlite3_finalize((sqlite3_stmt *) statementHandle);
}

void Java_io_kzw_sqlite3native_SQLitePreparedStatement_bindByteBuffer(JNIEnv *env, jobject object, int statementHandle, int index, jobject value, int length) {
    sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle;
    void *buf = env->GetDirectBufferAddress(value);
    
    int errcode = sqlite3_bind_blob(handle, index, buf, length, SQLITE_STATIC);
    if (SQLITE_OK != errcode) {
        throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode);
    }
}

void Java_io_kzw_sqlite3native_SQLitePreparedStatement_bindString(JNIEnv *env, jobject object, int statementHandle, int index, jstring value) {
    sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle;
    
    char const *valueStr = env->GetStringUTFChars(value, 0);
    
    int errcode = sqlite3_bind_text(handle, index, valueStr, -1, SQLITE_TRANSIENT);
    if (SQLITE_OK != errcode) {
        throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode);
    }
    
    if (valueStr != 0) {
        env->ReleaseStringUTFChars(value, valueStr);
    }
}

void Java_io_kzw_sqlite3native_SQLitePreparedStatement_bindInt(JNIEnv *env, jobject object, int statementHandle, int index, int value) {
    sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle;
    
    int errcode = sqlite3_bind_int(handle, index, value);
    if (SQLITE_OK != errcode) {
        throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode);
    }
}

void Java_io_kzw_sqlite3native_SQLitePreparedStatement_bindLong(JNIEnv *env, jobject object, int statementHandle, int index, long long value) {
    sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle;
    
    int errcode = sqlite3_bind_int64(handle, index, value);
    if (SQLITE_OK != errcode) {
        throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode);
    }
}

void Java_io_kzw_sqlite3native_SQLitePreparedStatement_bindDouble(JNIEnv *env, jobject object, int statementHandle, int index, double value) {
    sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle;
    
    int errcode = sqlite3_bind_double(handle, index, value);
    if (SQLITE_OK != errcode) {
        throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode);
    }
}

void Java_io_kzw_sqlite3native_SQLitePreparedStatement_bindNull(JNIEnv *env, jobject object, int statementHandle, int index) {
    sqlite3_stmt *handle = (sqlite3_stmt *) statementHandle;
    
    int errcode = sqlite3_bind_null(handle, index);
    if (SQLITE_OK != errcode) {
        throw_sqlite3_exception(env, sqlite3_db_handle(handle), errcode);
    }
}

void Java_io_kzw_sqlite3native_SQLiteDatabase_closedb(JNIEnv *env, jobject object, int sqliteHandle) {
    sqlite3 *handle = (sqlite3 *)sqliteHandle;
    int err = sqlite3_close(handle);
    if (SQLITE_OK != err) {
        throw_sqlite3_exception(env, handle, err);
    }
}

void Java_io_kzw_sqlite3native_SQLiteDatabase_beginTransaction(JNIEnv *env, jobject object, int sqliteHandle) {
    sqlite3 *handle = (sqlite3 *)sqliteHandle;
    sqlite3_exec(handle, "BEGIN", 0, 0, 0);
}

void Java__io_kzw_sqlite3native_SQLiteDatabase_commitTransaction(JNIEnv *env, jobject object, int sqliteHandle) {
    sqlite3 *handle = (sqlite3 *)sqliteHandle;
    sqlite3_exec(handle, "COMMIT", 0, 0, 0);
}

int Java_io_kzw_sqlite3native_SQLiteDatabase_opendb(JNIEnv *env, jobject object, jstring fileName, jstring tempDir) {
    char const *fileNameStr = env->GetStringUTFChars(fileName, 0);
    char const *tempDirStr = env->GetStringUTFChars(tempDir, 0);
    
    if (sqlite3_temp_directory != 0) {
        sqlite3_free(sqlite3_temp_directory);
    }
    sqlite3_temp_directory = sqlite3_mprintf("%s", tempDirStr);
    
    sqlite3 *handle = 0;
    int err = sqlite3_open(fileNameStr, &handle);
    if (SQLITE_OK != err) {
        throw_sqlite3_exception(env, handle, err);
    }
    if (fileNameStr != 0) {
        env->ReleaseStringUTFChars(fileName, fileNameStr);
    }
    if (tempDirStr != 0) {
        env->ReleaseStringUTFChars(tempDir, tempDirStr);
    }
    return (int)handle;
}

int Java_io_kzw_sqlite3native_SQLiteCursor_columnType(JNIEnv *env, jobject object, int statementHandle, int columnIndex) {
	sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle;
	return sqlite3_column_type(handle, columnIndex);
}

int Java_io_kzw_sqlite3native_SQLiteCursor_columnIsNull(JNIEnv *env, jobject object, int statementHandle, int columnIndex) {
	sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle;
	int valType = sqlite3_column_type(handle, columnIndex);
	return SQLITE_NULL == valType;
}

int Java_io_kzw_sqlite3native_SQLiteCursor_columnIntValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) {
	sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle;
	int valType = sqlite3_column_type(handle, columnIndex);
	if (SQLITE_NULL == valType) {
		return 0;
	}
	return sqlite3_column_int(handle, columnIndex);
}

long long Java_io_kzw_sqlite3native_SQLiteCursor_columnLongValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) {
	sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle;
	int valType = sqlite3_column_type(handle, columnIndex);
	if (SQLITE_NULL == valType) {
		return 0;
	}
	return sqlite3_column_int64(handle, columnIndex);
}

double Java_io_kzw_sqlite3native_SQLiteCursor_columnDoubleValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) {
	sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle;
	int valType = sqlite3_column_type(handle, columnIndex);
	if (SQLITE_NULL == valType) {
		return 0;
	}
	return sqlite3_column_double(handle, columnIndex);
}

jstring Java_io_kzw_sqlite3native_SQLiteCursor_columnStringValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) {
	sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle;
	const char *str = (const char *) sqlite3_column_text(handle, columnIndex);
	if (str != 0) {
		return env->NewStringUTF(str);
	}
	return 0;
}

jbyteArray Java_io_kzw_sqlite3native_SQLiteCursor_columnByteArrayValue(JNIEnv *env, jobject object, int statementHandle, int columnIndex) {
    sqlite3_stmt *handle = (sqlite3_stmt *)statementHandle;
	const jbyte *buf = (const jbyte *) sqlite3_column_blob(handle, columnIndex);
	int length = sqlite3_column_bytes(handle, columnIndex);
	if (buf != nullptr && length > 0) {
		jbyteArray result = env->NewByteArray(length);
        env->SetByteArrayRegion(result, 0, length, buf);
        return result;
	}
	return nullptr;
}
}
 

 

4. Android.mk 和 Application.mk

Android.mk:

LOCAL_PATH := $(call my-dir)

ifeq ($(TARGET_ARCH_ABI),armeabi)
	LOCAL_ARM_MODE  := thumb
else
	LOCAL_ARM_MODE  := arm
endif
LOCAL_MODULE := sqlite3
LOCAL_CFLAGS 	:= -w -std=c11 -Os -DNULL=0 -DSOCKLEN_T=socklen_t -DLOCALE_NOT_USED -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=32
LOCAL_CFLAGS 	+= -DANDROID_NDK -DDISABLE_IMPORTGL -fno-strict-aliasing -fprefetch-loop-arrays -DAVOID_TABLES -DANDROID_TILE_BASED_DECODE -DANDROID_ARMV6_IDCT -DHAVE_STRCHRNUL=0

LOCAL_SRC_FILES     := \
./sqlite3.c

include $(BUILD_STATIC_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE 	:= sqlite_wrapper
LOCAL_CPPFLAGS 	:= -std=c++11
LOCAL_LDLIBS 	:= -llog -lz
LOCAL_STATIC_LIBRARIES := sqlite3

LOCAL_SRC_FILES     += \
./SqliteWrapper.cpp \

include $(BUILD_SHARED_LIBRARY)

这边编译的是 armeabi 和 armeabi-v7a 平台,先编译 sqlite3 静态库,然后作为 sqlite_wrapper 共享库的依赖库。由于我使用的是 NDK r15 编译的,需要设置 -D_FILE_OFFSET_BITS=32,否则会报错:mmap unknown identifier,具体可以看这篇文章:https://github.com/couchbaselabs/couchbase-lite-libsqlcipher/pull/23

 

Application.mk:

APP_PLATFORM := android-14
APP_ABI := armeabi armeabi-v7a
NDK_TOOLCHAIN_VERSION := 4.9
APP_STL := gnustl_static

 

 

5. 编译生成 so 库

MacBook-Pro-3:jni kuangzhongwen$ ndk-build 
[armeabi] Compile++ thumb: sqlite_wrapper <= SqliteWrapper.cpp
[armeabi] Compile thumb  : sqlite3 <= sqlite3.c
[armeabi] StaticLibrary  : libgnustl_shared.a
[armeabi] SharedLibrary  : libsqlite_wrapper.so
[armeabi] Install        : libsqlite_wrapper.so => libs/armeabi/libsqlite_wrapper.so
[armeabi-v7a] Compile++ thumb: sqlite_wrapper <= SqliteWrapper.cpp

ndk-build,可以看到依次生成了两个平台的 so 库,都是先打出 .a 静态库,再打出 so 共享库,最终 so 库会打包进 apk 中。

使用时加载库即可:

    static {
        System.loadLibrary("sqlite_wrapper");
    }

时间有限,就不写 demo 运行了,感兴趣的同学可以自行编译运行一下。明天开始,将开写 40-50 篇的 Framework 文章,也是对自己的一个查漏补缺。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

NDK篇 - 手动编译 SQLite 的相关文章

  • 通过组合字符串将不同的行归为一行

    我有一个这样的数据集 Column1 Column2 1 A 1 B 1 C 2 D 2 E 2 F 2 G 3 H 3 I 我想将它合并成这样的东西 Column1 Column2 1 A B C 2 D E F G 3 H I 是否可以
  • 如何从 TKinter 和 SQLite3 中的列表框中删除行

    我试图弄清楚如何从列表框和 sqlite3 中删除一行 我的底部有一个 delButton 函数 我需要从列表框和sql中删除该行吗 我不确定我在这里做什么 就删除部分而言 我通过复制和粘贴各种示例将该功能放在一起 from Tkinter
  • Android SQLite 通配符

    我正在尝试使用通配符元素进行查询 以在 SQLite 表中搜索特定变量中任意位置具有元素的条目 public String getCheckoutEntry String title String ISBN Wild card Syntax
  • 插入失败“OperationalError:没有这样的列”

    我尝试使用我尝试修复的姓名和电话创建一个数据库 但它会随时向我重播 File exm0 py line 14 in
  • 如何在.Net中使用SQLCipher

    我想用C winform 读取一个SQLCipher数据库并找到一个开源项目SQLitePCL https github com ericsink SQLitePCL raw 但是我还没有找到这个使用的例子 而且文档也不是很多 有朋友用过这
  • 如何在android上的manageQuery中添加限制子句

    Android 的 API 通过 SQLite 提供了一种干净的机制来查询联系人列表 但是 我不确定如何限制结果 Cursor cur Activity mCtx managedQuery People CONTENT URI column
  • 如何将数据一次性插入sqlite数据库

    我需要将数据添加到 sqlite 数据库一次 也就是说 我希望我的应用程序的用户看到该数据已加载 如何做到这一点 我使用查询执行了它 INSERT INTO TABLE NAME VALUES 值1 值2 值3 值N 但是每次应用程序打开该
  • 如果存在 sqlite 则重命名列?

    我创建了一个最初未使用的列 但现在我们正在设置和获取值 我发现列名不正确 以及我想要更改列名称但想要保留设备中现有数据库的数据 有任何疑问吗检查并重命名sqlite 列 像这样的东西 Alter Table MyTable RENAME C
  • Android SQLite 列和索引最佳实践

    我开始在我的 Android 应用程序中使用 SQLite 我对数据库并不陌生 我有很多年使用 Delphi 进行数据库工作的经验 这确实减轻了使用数据库的一些工作 在应用程序中 可能有一张包含多列的表格 从表中读取数据时 会执行一些 SQ
  • 两列上的唯一索引是否意味着每一列上都有一个索引?

    我的架构中有一个表 它对两列有唯一约束 UNIQUE Column1 Column2 SQlite 文档告诉我 这在这些列上创建唯一索引 http www sqlite org lang createtable html 我的问题是 这是否
  • SQLite3 数学函数 Python

    更新 SQLite 版本 3 5 0 后 可以使用 SQL 数学函数 如果我在 pycharm 查询中使用它 它效果很好 但我无法在 python 代码中执行查询 然后我收到以下错误消息 pandas io sql DatabaseErro
  • SQlite删除内连接

    我已经阅读了很多其他答案 但无法使其正常工作 select from invTypes inner join invGroups on invtypes groupID invgroups groupID where invGroups c
  • PHP - SQLite 与 SQLite3

    我已经使用 SQLite 2 8 17 制作了一个 Web 应用程序 我现在才发现有一个 SQLite3 在制作 Web 应用程序时 它以某种方式逃避了我的注意 可能是由于缺少 php 函数的文档 我想知道 使用 SQLite3 比 SQL
  • sqlite 插入需要很长时间

    我正在将不到 200 000 行插入到 sqlite 数据库表中 我只是在终端中通过 sqlite3 使用一个非常简单的 sql 文件 我打赌它已经运行了至少 30 分钟 这是正常现象还是我应该关闭该过程并尝试不同的方法 sqlite中的插
  • SQLite3 中的“预准备”语句?

    在具有固定查询集的内存受限嵌入式系统中使用 SQLite 如果可以 预先准备 查询 则似乎可以节省代码和数据 也就是说 准备好的语句是由 相当于 sqlite3 prepare v2 在build运行时只需要调用 bind step 等 引
  • 错误:找不到模块 \node_modules\sqlite3\lib\binding\electron-v8.0-win32-x64\node_sqlite3.node'

    我在 Electron 8 1 中安装 sqlite3 时遇到问题 我收到以下错误 Error Cannot find module D TASK 2020 1 1 AMS node modules sqlite3 lib binding
  • 迭代 Sqlite-query 中的行

    我有一个表布局 我想用数据库查询的结果填充它 我使用全选 查询返回四行数据 我使用此代码来填充表行内的 TextView Cursor c null c dh getAlternative2 startManagingCursor c th
  • 如何在SQLite中的两个表之间复制数据?

    我有两个具有不同列的表 如下所示 table1 id title name number address table2 id phone name address 如何将数据 名称 地址 从表 1 复制到表 2 我的问题有两种情况 第一 t
  • 我应该保留远程数据库的本地副本吗?

    我正在开发一个应用程序 基本上允许人们创建 加入和管理其他人的群组 群组内的人也可以互相发送消息 我一直在想哪条路会更好 保留包含所有信息的远程数据库 包括发送给用户和从用户发送的消息 并让应用程序在每次需要信息时查询服务器 甚至是它以前见
  • 游标在“查询、删除表、创建表、查询相同表名”后返回相同的列

    例如我有一个名为myTable在我的数据库文件中 Android ps它不支持列重命名 删除等 这张表有idx name columns 我想删除并重新创建该表 但使用新列 我是这样做的 db transaction var cursor

随机推荐