2020年10月28日 15点29分
背景
- 安卓自带的 package android.database.sqlite 中 SQLiteCustomFunction.callback 的返回为 void,如下:
/**
* A callback interface for a custom sqlite3 function.
* This can be used to create a function that can be called from
* sqlite3 database triggers.
* @hide
*/
public interface CustomFunction {
public void callback(String[] args);
}
也就是说,自定义 Sqlite 函数只能在 java 层接受参数,而不能返回 native 层给 Sqlite 调用。这不是逗嘛。。。
- 所以考虑重写 Sqlite 提供给 android 的 java 接口,由于 android 自带的 sqlite package 不好修改。
因此使用 Sqlite 原版提供给 android 调用的带C++源码的包SQLite Android Bindings。 - 将原版的包在自己的工程里面配置好,通过编译运行正常后,执行如下步骤。
Step1
进入 ..\sqlite3\src\main\java\org\sqlite\database\sqlite\SQLiteCustomFunction.java 文件,找到如下代码段
// Called from native.
@SuppressWarnings("unused")
private void dispatchCallback(String[] args) {
callback.callback(args);
}
修改为
// Called from native.
@SuppressWarnings("unused")
private String dispatchCallback(String[] args) {
return callback == null ? null : callback.callback(args);
}
Step2
进入 ..\sqlite3\src\main\java\org\sqlite\database\sqlite\SQLiteDatabase.java 文件,找到如下代码段
/**
* A callback interface for a custom sqlite3 function.
* This can be used to create a function that can be called from
* sqlite3 database triggers.
* @hide
*/
public interface CustomFunction {
public void callback(String[] args);
}
修改为
/**
* A callback interface for a custom sqlite3 function.
* This can be used to create a function that can be called from
* sqlite3 database triggers.
* @hide
*/
public interface CustomFunction {
String callback(String[] args);
}
Step3
进入 ..\sqlite3\src\main\jni\sqlite\android_database_SQLiteConnection.cpp 文件,找到如下代码段
int register_android_database_SQLiteConnection(JNIEnv *env)
{
jclass clazz;
FIND_CLASS(clazz, "org/sqlite/database/sqlite/SQLiteCustomFunction");
GET_FIELD_ID(gSQLiteCustomFunctionClassInfo.name, clazz,
"name", "Ljava/lang/String;");
GET_FIELD_ID(gSQLiteCustomFunctionClassInfo.numArgs, clazz,
"numArgs", "I");
GET_METHOD_ID(gSQLiteCustomFunctionClassInfo.dispatchCallback,
clazz, "dispatchCallback", "([Ljava/lang/String;)V");
FIND_CLASS(clazz, "java/lang/String");
gStringClassInfo.clazz = jclass(env->NewGlobalRef(clazz));
return jniRegisterNativeMethods(env,
"org/sqlite/database/sqlite/SQLiteConnection",
sMethods, NELEM(sMethods)
);
}
将
GET_METHOD_ID(gSQLiteCustomFunctionClassInfo.dispatchCallback,
clazz, "dispatchCallback", "([Ljava/lang/String;)V");
修改为
GET_METHOD_ID(gSQLiteCustomFunctionClassInfo.dispatchCallback,
clazz, "dispatchCallback", "([Ljava/lang/String;)Ljava/lang/String;");
Step4
进入 ..\sqlite3\src\main\jni\sqlite\android_database_SQLiteConnection.cpp 文件,找到如下代码段
// Called each time a custom function is evaluated.
static void sqliteCustomFunctionCallback(sqlite3_context *context,
int argc, sqlite3_value **argv) {
JNIEnv* env = 0;
gpJavaVM->GetEnv((void**)&env, JNI_VERSION_1_4);
// Get the callback function object.
// Create a new local reference to it in case the callback tries to do something
// dumb like unregister the function (thereby destroying the global ref) while it is running.
jobject functionObjGlobal = reinterpret_cast<jobject>(sqlite3_user_data(context));
jobject functionObj = env->NewLocalRef(functionObjGlobal);
jobjectArray argsArray = env->NewObjectArray(argc, gStringClassInfo.clazz, NULL);
if (argsArray) {
for (int i = 0; i < argc; i++) {
const jchar* arg = static_cast<const jchar*>(sqlite3_value_text16(argv[i]));
if (!arg) {
ALOGW("NULL argument in custom_function_callback. This should not happen.");
} else {
size_t argLen = sqlite3_value_bytes16(argv[i]) / sizeof(jchar);
jstring argStr = env->NewString(arg, argLen);
if (!argStr) {
goto error; // out of memory error
}
env->SetObjectArrayElement(argsArray, i, argStr);
env->DeleteLocalRef(argStr);
}
}
// TODO: Support functions that return values.
env->CallVoidMethod(functionObj,
gSQLiteCustomFunctionClassInfo.dispatchCallback, argsArray);
error:
env->DeleteLocalRef(argsArray);
}
env->DeleteLocalRef(functionObj);
if (env->ExceptionCheck()) {
ALOGE("An exception was thrown by custom SQLite function.");
/* LOGE_EX(env); */
env->ExceptionClear();
}
}
修改为
// Called each time a custom function is evaluated.
static void sqliteCustomFunctionCallback(sqlite3_context *context,
int argc, sqlite3_value **argv) {
JNIEnv* env = 0;
gpJavaVM->GetEnv((void**)&env, JNI_VERSION_1_4);
// Get the callback function object.
// Create a new local reference to it in case the callback tries to do something
// dumb like unregister the function (thereby destroying the global ref) while it is running.
jobject functionObjGlobal = reinterpret_cast<jobject>(sqlite3_user_data(context));
jobject functionObj = env->NewLocalRef(functionObjGlobal);
jobjectArray argsArray = env->NewObjectArray(argc, gStringClassInfo.clazz, NULL);
jstring callBackFromJava;
const char* resultStr;
if (argsArray) {
for (int i = 0; i < argc; i++) {
const jchar* arg = static_cast<const jchar*>(sqlite3_value_text16(argv[i]));
if (!arg) {
ALOGW("NULL argument in custom_function_callback. This should not happen.");
} else {
size_t argLen = sqlite3_value_bytes16(argv[i]) / sizeof(jchar);
jstring argStr = env->NewString(arg, argLen);
if (!argStr) {
goto error; // out of memory error
}
env->SetObjectArrayElement(argsArray, i, argStr);
env->DeleteLocalRef(argStr);
}
}
// TODO: Support functions that return values.
// env->CallVoidMethod(functionObj,
// gSQLiteCustomFunctionClassInfo.dispatchCallback, argsArray);
callBackFromJava = reinterpret_cast<jstring>(env->CallObjectMethod(functionObj,
gSQLiteCustomFunctionClassInfo.dispatchCallback, argsArray));
resultStr = env->GetStringUTFChars(callBackFromJava, NULL);
// __android_log_write(ANDROID_LOG_INFO, "resultStr.", resultStr);
sqlite3_result_text(context, resultStr, env->GetStringLength(callBackFromJava), sqlite3_free);
env->ReleaseStringUTFChars(callBackFromJava, resultStr);
error:
env->DeleteLocalRef(argsArray);
}
env->DeleteLocalRef(functionObj);
if (env->ExceptionCheck()) {
ALOGE("An exception was thrown by custom SQLite function.");
/* LOGE_EX(env); */
env->ExceptionClear();
}
}
Android 使用测试
伪代码:配置数据库
SQLiteOpenHelper mSQLiteOpenHelper = new SQLiteOpenHelper() {
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
}
@Override
public void onOpen(@NotNull SQLiteDatabase db) {
// 实现简单的字符串连接功能
db.addCustomFunction("StringFunc", 1, args -> {
return args[0] + "@" + args[0];
});
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
};
伪代码:执行查询
SQLiteDatabase db = mSQLiteOpenHelper.getWritableDatabase();
try {
Cursor cursor = db.rawQuery("SELECT StringFunc(id) From TableA WHERE id < 5" , null);
cursor.moveToFirst();
int row = 0;
while (!cursor.isAfterLast()) {
Log.d("test", "row." + (i++) + " StringFunc. " + cursor.getString(0));
cursor.moveToNext();
}
cursor.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
db.close();
}
进阶 1
如果不是必须使用 android 层的 java 函数,在已经可以重新编译 Sqlite3 的情况下,为什么要考虑重构它提供的 java-native 层调用呢?直接修改 sqlite3.c 源文件,将其变成 build-in 内置函数不香嘛?
实现思路:在 sqlite3.c 源文件里找到如下函数,在里面增加注册额外的自定义函数。
SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void) {
//...
}
进阶 2
还可以利用 Sqlite3 load_extension(X) 加载扩展的方法加载 .so 库,实现更多功能。不过 android 原本集成的 sqlite 没有启用加载权限,对数据库执行如下语句加载路径“...\libabc.so”的 extension 会出现“not authorized”错误:
SELECT load_extension('...\libabc.so')
所以还是要修改 sqlite3.c 源文件在适合的地方添加语句使其允许加载扩展。
方法一
在 sqlite3.c 文件里找到如下语句
static void loadExt(sqlite3_context *context, int argc, sqlite3_value **argv){
//...
}
在其真正有效执行的开头加入代码,如下:
static void loadExt(sqlite3_context *context, int argc, sqlite3_value **argv){
//...
sqlite3_enable_load_extension(db, 1);
//...
}
此法比较暴力,任何加载 extension 的行为都首先得到允许。
方法二
在相应 Anroid.mk 文件【ps: android studio + Sqlite3(SQLite Android Bindings) 环境】添加配置语句
LOCAL_CFLAGS += -DSQLITE_ENABLE_LOAD_EXTENSION
且找到 sqlite3.c 文件的如下代码段
#ifdef SQLITE_ENABLE_LOAD_EXTENSION
| SQLITE_LoadExtension
#endif
修改成
#ifdef SQLITE_ENABLE_LOAD_EXTENSION
| SQLITE_LoadExtension
| SQLITE_LoadExtFunc
#endif
最后重新编译,即可允许 Sqlite3 数据库使用如下语句加载地址为“...\libabc.so”的扩展:
SELECT load_extension('...\libabc.so')
网友评论