您的位置:首页 > 移动开发 > Android开发

安卓so动态库加载代理实现,可以实现C层的类反射效果

2016-05-26 15:07 501 查看
一般来说如果我们需要加载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");

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  JAVA android c语言