美文网首页
Android Studio+Sqlite3:利用原生 Sqli

Android Studio+Sqlite3:利用原生 Sqli

作者: 今夜一只喵 | 来源:发表于2020-10-28 15:17 被阅读0次

    2020年10月28日 15点29分

    背景

    1. 安卓自带的 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 调用。这不是逗嘛。。。

    1. 所以考虑重写 Sqlite 提供给 android 的 java 接口,由于 android 自带的 sqlite package 不好修改。
      因此使用 Sqlite 原版提供给 android 调用的带C++源码的包SQLite Android Bindings
    2. 将原版的包在自己的工程里面配置好,通过编译运行正常后,执行如下步骤。

    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')
    

    结束

    相关文章

      网友评论

          本文标题:Android Studio+Sqlite3:利用原生 Sqli

          本文链接:https://www.haomeiwen.com/subject/dsnqvktx.html