1. 程式人生 > >安卓so動態庫載入代理實現,可以實現C層的類反射效果

安卓so動態庫載入代理實現,可以實現C層的類反射效果

一般來說如果我們需要載入so檔案,需要一個java對應層的類,即有一個類必須要是包名和類名是不變的。

比如說下面的c層程式碼,這樣寫就必須要求有個類是com.example.hellojni.HelloJni,呼叫的方法為stringFromJNI

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_hellojni_HelloJni */

#ifndef _Included_com_example_hellojni_HelloJni
#define _Included_com_example_hellojni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_hellojni_HelloJni
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI
  (JNIEnv *, jobject);

/*
 * Class:     com_example_hellojni_HelloJni
 * Method:    unimplementedStringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

但是有些時候我們需要不使用這個包名,因為可能包名有衝突,這種時候,我們應該怎麼辦呢?

我們知道,C層是可以用本地方法去載入so檔案的。

即dlopen 和dlsym

dlopen是開啟so,dlsym是匯出函式,可以對應到函式指標實現。

我們知道jni可以動態註冊函式,它會在執行時裡動態對映函式表。

我們想要實現的是完整的功能,就是不需要再編寫c層,一次編寫,就只需要更改java層程式碼實現,就可以實現所有對應的原生代碼代理。

類似反射的實現辦法。

這裡是c層的實現,本質就是在呼叫的時候將函式指標重新註冊到jni的對映中。

#include <string.h>
#include <jni.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <android/log.h>
#include <assert.h>
#include <vector>
#define LOG_TAG "debug log"
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)
using namespace std;
const char *kClassName = "com/siecom/nativelibs/NativeLoad";//指定要註冊的類
void *fp;
vector<JNINativeMethod> vec;
static int registerJniMapping(JNIEnv *env, jobject thiz, jstring java_func, jstring native_func);

jstring native_hello(JNIEnv *env, jobject thiz, jstring str) {
    return env->NewStringUTF("test");
}

char *jstringTostring(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("utf-8");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char *) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    return rtn;
}


jint load_so(JNIEnv *env, jobject thiz, jstring so_name, jstring func_name) {
    void *filehandle = NULL;
    void *getResult = NULL;
    jint result = -1;
    char *so = jstringTostring(env, so_name);
    char *func = jstringTostring(env, func_name);
    filehandle = dlopen(so, RTLD_LAZY);
    if (filehandle) {
        LOGE("filehandle %d", filehandle);
        getResult = dlsym(filehandle, func);
        LOGE("dlsym %d", getResult);
        fp = getResult;
        if (getResult) {
            result = 0;
            return result;
        }
    }
    return result;
}


static void getMethods(){

       JNINativeMethod method1,method2,method3;
       method1.name = "test";
       method1.signature = "(Ljava/lang/String;)Ljava/lang/String;";
       method1.fnPtr  = (void *) native_hello;
       vec.push_back(method1);

       method2.name = "load_so";
       method2.signature = "(Ljava/lang/String;Ljava/lang/String;)I",
       method2.fnPtr  = (void*)load_so;
       vec.push_back(method2);

       method3.name = "registerJniMapping";
       method3.signature = "(Ljava/lang/String;Ljava/lang/String;)I",
       method3.fnPtr  = (void*)registerJniMapping;
       vec.push_back(method3);

}

/*
* 為某一個類註冊本地方法
*/
static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods,
                                 int numMethods) {
    jclass clazz;
    clazz = env->FindClass(className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s'", className);
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s'", className);
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

static int registerJniMapping(JNIEnv *env, jobject thiz, jstring java_func, jstring native_func) {
    void *tmp = fp;
    int pos = vec.size();
    LOGE("before pos:%d", pos);
    JNINativeMethod method;
    method.name = jstringTostring(env, java_func);
    method.signature = jstringTostring(env, native_func);
    method.fnPtr = (void *) tmp;
    vec.push_back(method);
    pos = vec.size();
    LOGE("after pos:%d", pos);
    return registerNativeMethods(env, kClassName, &vec[0],
                                 vec.size());
}
/*
* 為所有類註冊本地方法
*/
static int registerNatives(JNIEnv *env) {
    getMethods();
    return registerNativeMethods(env, kClassName, &vec[0],
                                 vec.size());
}
typedef union {
    JNIEnv *env;
    void *venv;
} UnionJNIEnvToVoid;
/*
* System.loadLibrary("lib")時呼叫
* 如果成功返回JNI版本, 失敗返回-1
*/
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    UnionJNIEnvToVoid uenv;
    uenv.venv = NULL;
    jint result = -1;
    JNIEnv *env = NULL;
    //LOGI("JNI_OnLoad");
    if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed");
        goto bail;
    }
    env = uenv.env;
    if (registerNatives(env) != JNI_TRUE) {
        LOGE("ERROR: registerNatives failed");
        goto bail;
    }
    result = JNI_VERSION_1_4;
    bail:
    return result;
}

這是對應java層,當然java層還是需要有個固定的包名的,這是不能避免的,但是你可以直接使用這個類去load你需要的so庫類
 static
    {
        System.loadLibrary("Loadso");
        /**
         * 動態註冊jni函式
         * 不需要寫對應的c層函式,直接對映對應關係
         * 需要函式的返回型別和入參java層變數簽名如(Ljava/lang/String;)I
         *
         */
        int res =  NativeLoad.load_so("libwltdecode.so","Java_com_ivsign_android_IDCReader_IDCReaderSDK_wltInit");
        if(res==0)
            Log.e("loadso", "正確載入so和函式");
        res = NativeLoad.registerJniMapping("init","(Ljava/lang/String;)I");
        if(res==1)
            Log.e("loadso:", "正確重註冊jni");

        res =  NativeLoad.load_so("libwltdecode.so","Java_com_ivsign_android_IDCReader_IDCReaderSDK_wltGetBMP");
        if(res==0)
            Log.e("loadso", "正確載入so和函式");
        res = NativeLoad.registerJniMapping("wltGetBMP","([B[B)I");
        if(res==1)
            Log.e("loadso:", "正確重註冊jni");

    }