安卓so動態庫載入代理實現,可以實現C層的類反射效果
阿新 • • 發佈:2019-01-02
一般來說如果我們需要載入so檔案,需要一個java對應層的類,即有一個類必須要是包名和類名是不變的。
這是對應java層,當然java層還是需要有個固定的包名的,這是不能避免的,但是你可以直接使用這個類去load你需要的so庫類
比如說下面的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"); }