在native线程利用JNI 反射自定义类
NDK编程中遇到的一些细节问题,希望对大家有帮助
-----题记
在JNI中,有时候出于业务要求需要实现异步事件机制,例如网络通讯的收发
这时就会在C++中回调java类的方法,于是就会用到java反射机制
在JNI中,实现类反射主要用到以下几个方法:(本例以反射静态方法为例)
JavaVM jint GetEnv(void **penv, jint version)
JavaVM jint AttachCurrentThread(void **penv, void *args)
JNIEnv jclass FindClass(const char *name)
JNIEnv jmethodID GetStaticMethodID(jclass clazz, const char *name, const char *sig)
JNIEnv void CallStaticVoidMethod(jclass cls, jmethodID methodID, ...)
JavaVM jint DetachCurrentThread()
假设我们要反射的类是:
package com.genius.test; public class InflectClass { public static void test(int cmd,String value,String data){ } }
那么C++中回调的JNI代码就是:
void testInflect(int cmd, const char* value, const char* data) { if (g_vm == NULL) { return ; } int status; JNIEnv *env = NULL; bool isAttach = false; status = g_vm->GetEnv((void **) &env, JNI_VERSION_1_4); if(status != JNI_OK) { status = g_vm->AttachCurrentThread(&env, NULL); if(status < 0) { return; } isAttach = true; } jstring valueString = NULL; jstring dataString = NULL; jclass inflectClass = NULL; jmethodID inflectMethod = NULL; jclass inflectClass = env->FindClass("com/genius/test/InflectClass"); if (inflectClass == NULL) { return; } jmethodID inflectMethod= env->GetStaticMethodID(inflectClass, "test", "(ILjava/lang/String;Ljava/lang/String;)V"); if (inflectMethod == NULL) { return ; } valueString = env->NewStringUTF(value); dataString = env->NewStringUTF(data); env->CallStaticVoidMethod(inflectClass, inflectMethod, cmd, valueString, dataString); end: if (env->ExceptionOccurred()) { env->ExceptionDescribe(); env->ExceptionClear(); } if (isAttach) { g_vm->DetachCurrentThread(); } env->DeleteLocalRef(dataString); }
其中g_vm是全局的虚拟机实例,可以在
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved);
函数中保存该实例
jint GetEnv(void **penv, jint version)是获取当前线程对应的JNI环境指针
在JNI中,多线程间JNIEnv 是不可以共享的,所以不能全局保存使用
获取是有可能失败的,比如当前是native线程
何为native线程?即在jni中开启的线程。
而java线程则是调用native方法的宿主线程,可以是主线程也可以是子线程
在获取失败时,调用jint AttachCurrentThread(void **penv, void *args)来将当前线程附加到虚拟机并获取JNI环境指针
之后就是通过
jclass FindClass(const char *name)
GetStaticMethodID(jclass clazz, const char *name, const char *sig)
void CallStaticVoidMethod(jclass cls, jmethodID methodID, ...)
来获取类方法并调用
GetStaticMethodID方法的第三个参数是函数签名
主要是c++中重载函数的存在使得函数名不能作为区别函数方法的唯一标示
这时就需要区别参数列表,获取函数签名的命令是javap
具体使用方法大家百度一下就知道了
最后如果有附加当前线程到虚拟机的话
需要调用DetachCurrentThread方法来释放线程
否则线程不能正常结束
温馨提示:
理论上通过上面几个步骤就可以反射到java层了
但实际操作起来会发现在native线程里执行FindClass的时候会找不到该自定义类(系统类可以)
具体原因不详,解决方案如下:
在JNI_OnLoad里获取该类对象并保存一个全局引用
JavaVM *g_vm = NULL; jclass g_inflectClass = NULL; jmethodID g_methodID = NULL;
void InitInflectClass(JavaVM* vm) { g_vm = vm; JNIEnv *env = NULL; int status = g_vm->GetEnv((void **) &env, JNI_VERSION_1_4); if(status != JNI_OK) { return ; } jclass inflectClass = env->FindClass("com/genius/test/InflectClass"); if (inflectClass == NULL) { return ; } g_inflectClass = inflectClass; g_methodID = env->GetStaticMethodID(inflectClass, "test", "(ILjava/lang/String;Ljava/lang/String;)V"); if (g_methodID == NULL) { return ;
} } 直接把函数方法保存下来也可以
然后在C++回调的时候直接使用该全局类或函数方法即可
这样类就反射出来了
参考1:http://www.cfanz.cn/?c=article&a=read&id=74827
----------------------------------------------------
参考2:
static jclass FindClass(JNIEnv* env, const char* name)
{
这个函数传入两个参数,第一个参数是jni的环境参数;第二个参数name是需要查找的类描述名称。
JNI_ENTER();
const Method* thisMethod;
ClassObject* clazz;
jclass jclazz = NULL;
Object* loader;
char* descriptor = NULL;
thisMethod = dvmGetCurrentJNIMethod();
assert(thisMethod != NULL);
这段代码是获取当前正在运行的JNI加载方法。
descriptor = dvmNameToDescriptor(name);
if (descriptor == NULL) {
clazz = NULL;
goto bail;
}
这段代码是把查找类的名称转换类描述的方式,以便从类索引表里查找到。
//Thread* self = dvmThreadSelf();
if (_self->classLoaderOverride != NULL) {
/* hack for JNI_OnLoad */
assert(strcmp(thisMethod->name, "nativeLoad") == 0);
loader = _self->classLoaderOverride;
这段代码是使用本线程重载加载方法,也就是使用 nativeLoad的本地方法。
} else if (thisMethod == gDvm.methFakeNativeEntry) {
/* start point of invocation interface */
if (!gDvm.initializing)
loader = dvmGetSystemClassLoader();
else
loader = NULL;
这段代码是使用系统里的类加载器。
} else {
loader = thisMethod->clazz->classLoader;
这行代码是使用本类里的加载器。
}
clazz = dvmFindClassNoInit(descriptor, loader);
这行代码是使用指定类加载器来查找给出描述名称的类。
jclazz = addLocalReference(env, (Object*) clazz);
这行代码是增加类的引用计数。
bail:
free(descriptor);
JNI_EXIT();
return jclazz;
}
//QQ:9073204 EMAIL:9073204@qq.com
//蔡军生 2011-10-6
-------------------------------------------------------
参考3:http://stackoverflow.com/questions/7248468/findclass-cannot-find-custom-java-class
参考4:http://stackoverflow.com/questions/7248468/findclass-cannot-find-custom-java-class
参考5:http://stackoverflow.com/questions/13263340/findclass-from-any-thread-in-android-jni
参考6:
参考: http://blog.csdn.net/fontlose/article/details/6600122
http://www.cnblogs.com/daniel-shen/archive/2006/10/16/530587.html
http://blog.csdn.net/yang_hui1986527/article/details/6887052
http://www.cnblogs.com/liangwind/archive/2009/08/26/1925511.html
http://www.cnblogs.com/luxiaofeng54/category/315742.html
- ===============================================================================================================
- //找到类文件
- jclass objectClass = (*env)->FindClass(env, "com.lp.MailInfo");
- //或者 obj 参数表示要你想要得到类型的类对象。
- jclass class_Field = (*env)->GetObjectClass(env,obj);
- //得到构造函数
- jmethodID mid_date = (*env)->GetMethodID(env,objectClass, "<init>", "()V");
- //生成对象
- jobject o = (*env)->NewObject(env,jclass,jmethod);
- //jclass类 构造方法 后面的就是构造方法的参数 可以没有
- 例如
- jobject book;
- jclass class_book;
- jmethodID md_book;
- class_book = (*env)->FindClass(env,"LBook1;");
- md_book = (*env)->GetMethodID(env,class_book,"<init>","(IILjava/lang/String;)V");
- book = (*env)->NewObject(env,class_book,md_book,100,1,"huanghe");
- ===============================================================================================================
- http://www.cnblogs.com/daniel-shen/archive/2006/10/16/530587.html
- 表 3-2 Java虚拟机类型签名
- 类型签名 Java 类型
- Z boolean
- B byte
- C char
- S short
- I int
- J long 注意 这个是J
- F float
- D double
- L fully-qualified-class ; 全限定的类
- [type type[]
- (arg-types)ret-type 方法类型
- 例如,Java 方法:
- long fun(int n, String s, int[] arr);
- 具有以下类型签名:
- (ILjava/lang/String;[I)J
- 属性
- 访问对象实例域的相关函数如下:
- jfieldID GetFieldID(JNIEnv *env, jclass cl, const char name[], const char sig[]) 获取实例对象的域ID
- 该函数返回一个域的标识符。各参数含义如下:
- env JNI 接口指针;cl 类对象 ; name 域名; sig 编码的域签名
- XXX GetXXXField(JNIEnv *env, jobject obj, jfieldID id)
- 该函数返回域的值。域类型XXX是Object, Boolean, byte, char , short, int ,long ,float, double 中类型之一。
- 参数 env JNI借口指针;obj为域所在对象;id为域的标识符。
- void SetXXXField(JNIEnv *env,jobject obj, jfieldID id, XXX value)
- 例如:
- //招到类中名为"topic"的string类型的属性
- jfieldID topicFieldId = (*env)->GetFieldID(env, objectClass,"topic", "Ljava/lang/String;");
- jstring str = (*env)->GetStringField(env,obj,topicFieldId);
- jfieldID ival = (env)->GetFieldID(objectClass,"serial","I");
- (env)->SetIntField(obj,ival,10);
- 方法
- 得到方法id
- static jmethodID message;
- static jmethodID audio;
- static jmethodID video;
- video = (*env)->GetMethodID(env,ljls,"video_data_cb","([BI)I");
- audio = (*env)->GetMethodID(env,ljls,"audio_data_cb","([BI)I");
- message = (*env)->GetMethodID(env,ljls,"message_cb","(SI)I");
- 调用方法
- int ret = (*env)->CallIntMethod(env,g_meida_obj,audio,jarray,size);
- env obj对象 方法id 参数
- 静态方法 不推荐调用
- GetStaticMethodID
- ===============================================================================================================
- jbyteArray 转换为unsigned char *
- unsigned char *src;
- jbyte *arr = (*env)->GetByteArrayElements(env,yuvBuf,0);
- src = (char *)arr;
- unsigned char *转换为jbyteArray
- unsigned char *dst;
- jbyte *byte = (jbyte*)dst;
- jbyteArray rgbBuf = (*env)->NewByteArray(env,width*height*3);
- (*env)->SetByteArrayRegion(env,rgbBuf, 0, width*height*3, byte);
- //释放
- (*env)->ReleaseByteArrayElements(env,yuvBuf,arr,0);
- ===============================================================================================================
- 让object成为全局变量
- 1、定义全局变量
- static jobject g_meida_obj;
- 2、声明全局变量
- g_meida_obj = (*env)->NewGlobalRef(env,obj);
- 貌似用jmethodID不需要特殊设置 直接定义复制即可
- ===============================================================================================================
- 关于线程
- 1、定义全局变量
- static JavaVM *g_jvm;
- 2、赋值
- (*env)->GetJavaVM(env,&g_jvm);
- 3、你在需要用的地方 特别是不是jni的方法 要想使用当前线程的JNIEnv
- JNIEnv *env;
- (*g_jvm)->AttachCurrentThread(g_jvm,&env, NULL);
- 。。。。。。
- (*g_jvm)->DetachCurrentThread(g_jvm);
- ===============================================================================================================
- 回收内存
- jclass cls = env->FindClass("sinashow1android/info/BlackIPUserInfo");
- jobject black_ip_obj = env->NewObject(cls,construction_id);
- //回收局部对象的内存
- env->DeleteLocalRef(black_ip_obj);
- env->DeleteLocalRef(cls);
- 线程处理是 需要放开
- (*g_jvm)->DetachCurrentThread(g_jvm);
- ===============================================================================================================
- char 转换为 string
- jstring chartojstring(JNIEnv *env, const char *chars)
- {
- jstring result;
- jint len = strlen(chars);
- if(len <= 0)
- {
- result = env->NewStringUTF("");
- return result;
- }
- jclass stringClass;
- jmethodID cid;
- jbyteArray elemArr;
- jstring jencoding;
- stringClass = env->FindClass("java/lang/String");
- if (stringClass == NULL) {
- return NULL;
- }
- cid = env->GetMethodID(stringClass,"<init>", "([BLjava/lang/String;)V");
- if (cid == NULL) {
- return NULL;
- }
- jencoding = env->NewStringUTF("GBK");
- elemArr = env->NewByteArray(len);
- if (elemArr == NULL) {
- return NULL;
- }
- env->SetByteArrayRegion(elemArr, 0, len, (jbyte*)chars);
- result = (jstring)(env->NewObject(stringClass, cid, elemArr, jencoding));
- env->DeleteLocalRef(elemArr);
- env->DeleteLocalRef(stringClass);
- env->DeleteLocalRef(jencoding);
- return result;
- }
数组的常用操作:
对象的常用操作:
另外
1、
- http://blog.csdn.net/yang_hui1986527/article/details/6887052
这个里面 有JNI各个方法的说明 可以当做api
2、
- 推荐一个网址 里面关于jni的学习内容比较多
http://blog.csdn.net/kangyaping/article/details/6584027
-------------------------------------------------
参考7:http://blog.csdn.net/kangyaping/article/details/6584027
参考8:http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html
参考9:http://doc-snapshot.qt-project.org/qt5-stable/qtbluetooth-index.html