Android JNI浅析Java和Native通信对象的传值和回调
Posted 木大白易
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android JNI浅析Java和Native通信对象的传值和回调相关的知识,希望对你有一定的参考价值。
简单了解一下jni
JNI是一个本地编程接口,它允许运行在Java虚拟机的Java代码与用其他语言(如C,C++和汇编)编写的库交互。
jni函数签名
首先看一下java类型对应的jni类型:
Java类型 | 符号 |
---|---|
Boolean | Z |
Byte | B |
Char | C |
Short | S |
Int | I |
Long | J |
Float | F |
Double | D |
Void | V |
数组 | [ 比如:int[] -> [I ,如果是二维数组 int[][] -> [[I |
objects | 以"L"开头,以";"结尾,中间是用"/" 隔开的包及类名。比如:Ljava/lang/String;如果是嵌套类,则用$来表示嵌套。 |
比如:
privite native int test(String arg);
则它的签名为:
(Ljava/lang/String;)I
函数参数的传递
- 基本类型(如整型,字符等)在Java和native之间是采用值传递
- Java对象采用的是引用传递
关于局部引用的相关内容,可以参考之前的文章:
JNI内存方面说明以及相关类型手动释放内存
JavaVM和JNIEnv
这两个结构体在jni.h中有定义:
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
这里也能看出c版本和c++版本,在使用调用上有一些不同,c++相当于又包了一层!
下边看一下c++的版本:
JavaVM
struct _JavaVM
const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
jint DestroyJavaVM()
return functions->DestroyJavaVM(this);
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
return functions->AttachCurrentThread(this, p_env, thr_args);
jint DetachCurrentThread()
return functions->DetachCurrentThread(this);
jint GetEnv(void** env, jint version)
return functions->GetEnv(this, env, version);
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args);
#endif /*__cplusplus*/
;
JNIEnv
struct _JNIEnv
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;
#if defined(__cplusplus)
jint GetVersion()
return functions->GetVersion(this);
jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
jsize bufLen)
return functions->DefineClass(this, name, loader, buf, bufLen);
jclass FindClass(const char* name)
return functions->FindClass(this, name);
...
...
总的来说:
JavaVM:是java虚拟机环境,每个进程有且只有只有一个。
JNIEnv: 是线程上下文环境,每个线程只有一个,不能跨线程。
我们可以通过JNIEnv来获取一个JavaVM:
jint GetJavaVM(JNIEnv *env, JavaVM **vm);
// vm:用来存放获得的虚拟机的指针的指针。
// return:成功返回0,失败返回其他。
也可以通过JavaVM来获取一个JNIEnv:
jint GetEnv(void** env, jint version)
上边也提到了JNIEnv是线程绑定的,所以通常,用全局的JavaVM获取一个当前线程的JNIEnv的时候,通常需要绑定到当前线程:
char thread_name[128] = 0 ;
prctl(PR_GET_NAME, (char *)(thread_name));
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6;
args.name = thread_name;
args.group = NULL;
gJvm->AttachCurrentThread(&pEnv, (void *)(&args))
同样的,也需要在不使用的时候解绑:
gJvm->DetachCurrentThread();
本地函数的静态加载和动态加载
所谓静态加载,就是我们常见的,jni函数的声明是Java_包名_类名_方法名(参数...)
这样子的,Java方法和本地函数之间的映射关系编译器已经帮我们做了。
下边了解一下动态加载:
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved);
JNI_OnLoad方法是在动态库被加载时调用,而JNI_OnUnload则是在本地库被卸载时调用。所以这两个函数就是一个本地库最重要的两个生命周期方法。
在JNI_OnLoad()中,就可以手动注册本地函数,做好对java方法的映射。
一般的可以这个样子:
const JNINativeMethod gMethods[] =
"native函数名", "native函数签名", (void *)native函数, //第三个为函数指针
...//别的native函数
;
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK)
return result;
// 动态注册native function
jclass clazz = env->FindClass("com.xx.xx.xxclass");
env->RegisterNatives(clazz, &method, 1) //第三个参数为总的数量
// 返回jni的版本
return JNI_VERSION_1_6;
访问字段和函数
对于jni函数中传入的jobject对象,要想访问对象的字段和函数,就需要先获取对应的class引用!
jobject testObject; //假设我们已经有了这么一个对象
jclass testCls = env->GetObjectClass(testObject); //获取class引用
//int testFiled;
//int test(String arg);
jfieldID testFiledId = env->GetFieldID(testCls, "testFiledID", "I");
jmethodID testMethodId = env->GetMethodID(testCls, "test", "(Ljava/lang/String;)I");
// 注意对字段的get/set,函数的调用,都必须使用具体的jobject对象,而不能是class引用
env->GetIntField(testObjcet, testFiledId);
env->CallIntMethod(testObject, testMethodId, "arg...");
java像Native中的对象的传递,一般也是安装上边的方式来进行,取出字段的值,再赋值给native对应的对象!
如果是native的对象回调到java层怎么做?
// 先找到class引用
jclass jcs = env->FindClass("com/xx/xx/xxclass");
// 创建对象有两种方法
// 第一种是调用类的构造函数:mthodId就是构造函数的id
jmethodID cls_constructor = env->GetMethodID(jcs, "<init>", "()V");
jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
// 第二种是直接alloc对象
jobject AllocObject(jclass clazz)
//然后将native对象赋值给jobject即可!
以上是关于Android JNI浅析Java和Native通信对象的传值和回调的主要内容,如果未能解决你的问题,请参考以下文章
+Java中的native关键字浅析(Java+Native+Interface)++
Android jni/ndk编程三:native访问java
Android JNI学习笔记-数据类型映射以及native调用java
转深入了解android平台的jni---注册native函数