zoukankan      html  css  js  c++  java
  • 在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++回调的时候直接使用该全局类或函数方法即可

    这样类就反射出来了得意

     
     
  • 相关阅读:
    Redis 的 5 个常见使用场景
    当别人给你一个wsdl或者webservice接口时
    Java事务
    Java分布式锁的三种实现方案(redis)
    使用Redis数据库(String类型)
    超详细Redis数据库入门教程
    java对redis的基本操作(初识)
    String、StringBuffer与StringBuilder之间区别
    java正则表达式替换空格和换行符
    Linux 批量管理工具
  • 原文地址:https://www.cnblogs.com/lance2016/p/5204278.html
Copyright © 2011-2022 走看看