NDK开发之javaVM
1.关于JNIEnv和JavaVM
JNIEnv是一个与线程相关的变量,不同线程的JNIEnv彼此独立。JavaVM是虚拟机在JNI层的代表,在一个虚拟机进程中只有一个JavaVM,因此该进程的所有线程都可以使用这个JavaVM。当后台线程需要调用JNI native时,在native库中使用全局变量保存JavaVM尤为重要,这样使得后台线程能通过JavaVM获得JNIEnv。
native程序中频繁使用JNIEnv*和JavaVM*。而C和C++代码使用JNIEnv*和JavaVM*这两个指针的做法是有区别的,网上大部分代码都使用C++,基本上找不到关于C和C++在这个问题上的详细叙述。
在C中:
使用JNIEnv* env要这样 (*env)->方法名(env,参数列表)
使用JavaVM* vm要这样 (*vm)->方法名(vm,参数列表)
在C++中:
使用JNIEnv* env要这样 env->方法名(参数列表)
使用JavaVM* vm要这样 vm->方法名(参数列表)
上面这二者的区别是,在C中必须先对env和vm间接寻址(得到的内容仍然是一个指针),在调用方法时要将env或vm传入作为第一个参数。C++则直接利用env和vm指针调用其成员。那到底C中的(*env)和C++中的env是否有相同的数据类型呢?C中的(*vm) 和C++中的vm是否有相同的数据类型呢?
为了验证上面的猜测,我们可以查看JNIEnv和JavaVM的定义。他们位于头文件jni.h。我开发JNI用的是android-5平台,下面是 $NDK\platforms\android-5\arch-arm\usr\include\jni.h的部分代码
[cpp] view plaincopyprint?
- struct _JNIEnv;
- struct _JavaVM;
- #if defined(__cplusplus)
- typedef _JNIEnv JNIEnv; //C++使用这个类型
- typedef _JavaVM JavaVM; //C++使用这个类型
- #else
- typedef const struct JNINativeInterface* JNIEnv; //C使用这个类型
- typedef const struct JNIInvokeInterface* JavaVM; //C使用这个类型
- #endif
- struct JNINativeInterface
- {
- /****省略了的代码****/
- jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
- /****省略了的代码****/
- jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);
- /****省略了的代码****/
- };
- struct _JNIEnv
- {
- const struct JNINativeInterface* functions;
- #if defined(__cplusplus)
- /****省略了的代码****/
- jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
- { return functions->GetMethodID(this, clazz, name, sig); }
- /****省略了的代码****/
- jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
- { return functions->GetStaticObjectField(this, clazz, fieldID); }
- /****省略了的代码****/
- #endif /*__cplusplus*/
- };
- struct JNIInvokeInterface
- {
- /****省略了的代码****/
- jint (*GetEnv)(JavaVM*, void**, jint);
- jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
- };
- struct _JavaVM
- {
- const struct JNIInvokeInterface* functions;
- #if defined(__cplusplus)
- /****省略了的代码****/
- 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*/
- };
假如我们用C编码,宏__cplusplus没有定义,那么从最上面的宏#if defined(__cplusplus)可推断
JNIEnv 代表类型 const struct JNINativeInterface*
JavaVM 代表类型 const struct JNIInvokeInterface*
那么JNIEnv* env实际上等价于声明 const struct JNINativeInterface** env
JavaVM* vm实际上等价于声明 const struct JNIInvokeInterface ** vm
因此要调用JNINativeInterface结构体内的函数指针就必须先对env间接寻址。
(*env)的类型是const struct JNINativeInterface*(指向JNINativeInterface结构体的指针),这时候可以用这个指针调用结构体的成员函数指针,(*env)-> GetMethodID(env, jclass, const char*, const char*)。同理可分析JavaVM* vm。
----------------------------------------------------------------------------------------------------------------------------------------------
假如我们用C++编码,宏__cplusplus有定义,那么从最上面的宏#if defined(__cplusplus)可推断
JNIEnv 代表类型 struct _JNIEnv
JavaVM 代表类型 struct _JavaVM
那么JNIEnv* env实际上等价于声明 struct _JNIEnv* env
JavaVM* vm实际上等价于声明 struct _JavaVM* vm
要调用_JNIEnv结构体内的函数指针这直接使用env而不需间接寻址, env-> GetMethodID(jclass, const char*, const char*)。同理可分析JavaVM* vm。
现在可以回答刚才的猜测了,C中的(*env)类型是const struct JNINativeInterface*,C++中的env类型是struct _JNIEnv*,因此他们的数据类型不相同(虽然都是指针,但指向不同的结构体类型)。
我们再看结构体_JNIEnv(C++的JNIEnv所代表的类型),这个结构体内有一个成员const struct JNINativeInterface* functions,再仔细看_JNIEnv内定义的函数。当调用_JNIEnv内定义的函数时,其实就是通过functions这个指针调用JNINativeInterface内的函数指针,因此_JNIEnv的成员方法是JNINativeInterface的同名成员函数指针的包装而已,归根结底无论在C还是C++中其实都使用了JNINativeInterface结构体。这时调用JNINativeInterface的函数指针的第一参数是this,在C++中this代表指向当前上下文对象的指针其类型是struct _JNIEnv*(即JNIEnv*)。同理可分析_JavaVM。
2.注册和注销native函数
C和C++注册native函数的方式大致上相同,下面给出具体的代码。
- /* JNINativeMethod数组的定义在C和C++中都一样*/
- static JNINativeMethod gMethods[] = {
- {
- "jobjectProcess",
- "(Lcom/example/hellojni/HelloJni$Student;Ljava/lang/Integer;)V",
- (void*)jobjectProcess
- }
- /*被省略掉的代码*/
- };
- jint JNI_OnLoad(JavaVM* vm,void* reserved)
- {
- JNIEnv* env = NULL;
- jint result=-1;
- if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)
- return result;
- jclass HelloJniClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni");
- /* C */
- jint r=(*env)->RegisterNatives(env, HelloJniClazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));
- /* C++ */
- r=AndroidRuntime::registerNativeMethods(env,"com/example/hellojni/HelloJni",gMethods,NELEM(gMethods));
- /*或者env->RegisterNatives(HelloJniClazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));*/
- if(0 == r)
- //注册native函数成功
- else
- //注册native函数失败
- return JNI_VERSION_1_4;
- }
- void JNI_OnUnload(JavaVM* vm,void* reserved)
- {
- JNIEnv* env = NULL;
- if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)
- return;
- jclass HelloJniClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni");
- /* C */
- jint r=(*env)->UnregisterNatives(env,HelloJniClazz);
- /* C++ */
- jint r= env->UnregisterNatives(HelloJniClazz)
- if(r == 0)
- //注销native函数成功
- else
- //注销native函数失败
- }
C和C++中都可以通过JNIEnv的RegisterNatives函数注册,而C++还提供了AndroidRuntime::registerNativeMethods,AndroidRuntime类的registerNativeMethods方法也可以注册。
3. 在native中向LogCat输出调试信息
在C/C++编译单元头部加上
#include <android/log.h>
#define LOG_TAG "自定义一个字符串"
log.h声明了函数int __android_log_print(int prio, const char *tag, const char *fmt, ...)我们就是用这个函数向LogCat输出信息的。
加入了头文件后还必须给链接器指定__android_log_print函数所在的库文件liblog.so,在Android.mk文件中加上一行
LOCAL_LDLIBS := -llog
在native函数中可以用如下语句输出了
__android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,"name=%s,age=%d",”卢斌晖”,28);
第一个参数ANDROID_LOG_DEBUG是枚举常量,它在log.h中定义。
- typedef enum android_LogPriority
- {
- ANDROID_LOG_UNKNOWN = 0,
- ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
- ANDROID_LOG_VERBOSE,
- ANDROID_LOG_DEBUG,
- ANDROID_LOG_INFO,
- ANDROID_LOG_WARN,
- ANDROID_LOG_ERROR,
- ANDROID_LOG_FATAL,
- ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
- } android_LogPriority;
我们可以根据调试信息的不同类别而选用不同的枚举常量。
4.关于jclass
jclass代表JAVA中的java.lang.Class。我们看jclass的定义,下面给出$NDK\platforms\android-5\arch-arm\usr\include\jni.h的部分代码
- #ifdef __cplusplus
- /*Reference types, in C++*/
- class _jobject {};
- class _jclass : public _jobject {}; /*_jclass继承_jobject*/
- typedef _jclass* jclass;
- #else
- /*Reference types, in C.*/
- typedef void* jobject;
- typedef jobject jclass;
- #endif
在C中jclass代表类型void*,在C++中代表类型_jclass*。因此jclass是指针,我们能够在log中输出jclass变量值。
__android_log_print(ANDROID_LOG_DEBUG,"native函数中输出","地址=%p",jclass变量);
当多个native函数都需要使用同一个JAVA类的jclass变量时,不能够定义jclass类型全局变量并只对其赋初值一次然后在多次JAVA对native函数调用中使用这个jclass变量。不能企图以此方式来节约获得jclass变量的开销。
每次JAVA调用native都必须重新获得jclass,上次调用native所得到的jclass在下次调用native时再使用是无效的。下面是C代码
- static jclass StudentClazz; //全局变量
- jint JNI_OnLoad(JavaVM* vm,void* reserved)
- {
- JNIEnv* env = NULL;
- jint result=-1;
- if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)
- return result;
- StudentClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni$Student"); //初始化
- return JNI_VERSION_1_4;
- }
- JNIEXPORT void JNICALL jobjectProcess(JNIEnv *env, jobject instance,jobject student,jobject flag)
- {
- /*StudentClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni$Student");*/
- __android_log_print(ANDROID_LOG_DEBUG,"在jobjectProcess中输出","StudentClazz=%p",StudentClazz);
- nameFieldId=(*env)->GetFieldID(env,StudentClazz,"name","Ljava/lang/String;");
- jstring name=(jstring)((*env)->GetObjectField(env,student,nameFieldId));
- }
下面是Activity的代码
- static
- {
- System.loadLibrary("hello-jni");
- }
- public native void jobjectProcess(Student student,Integer flag);
- public static class Student{/*省略的代码*/}
- protected void onResume()
- {
- jobjectProcess(new Student(),new Integer(20));
- super.onResume();
- }
上面的C代码在JNI_OnLoad函数中对StudentClazz初始化,在jobjectProcess函数内没有再对StudentClazz赋值。此时运行程序会出错并在LogCat输出如下信息:
DEBUG/在jobjectProcess中输出(8494): StudentClazz=0x44c0a8f0
WARN/dalvikvm(8286): JNI WARNING: 0x44c0a8f0 is not a valid JNI reference
WARN/dalvikvm(8286): in Lcom/example/hellojni/HelloJni;.jobjectProcess (Lcom/example/hellojni/HelloJni$Student;Ljava/lang/Integer;)V (GetFieldID)
提示StudentClazz所指向的地址(0x44c0a8f0)不是合法的JNI引用。如果把jobjectProcess函数的第一行注释解除掉,再次给StudentClazz赋值程序便正常执行。
其实不管在哪个native函数内得到的StudentClazz值都是相同的,但每次native调用还是必须执行一次FindClass重新给StudentClazz赋值。
5.native的char*和JAVA的String相互转换
首先确保C/C++源文件的字符编码是UTF-8与JAVA的class文件字符编码保持一致。如果C/C++源码含有中文,那么编译出来的so中的中文字符串也保存为UTF-8编码,这样的程序不会产生乱码。
JNI提供了jstring来引用JAVA的String类型变量,如果native函数需要返回 String或者接受String类型参数就必须使用到jstring。而C/C++用char*引用字符串起始地址,当native函数接到jstring后要转换为char*所指向的字符串才能处理。当我们处理完char*所指向的字符串又要转换为jstring才能返回给JAVA代码。下面给出转换的方法(下面均是C代码)。
jstring转换为char*使用JNIEnv的const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)
JNIEnv env=//传入参数 ; jstring name=//传入参数 ;
const char *nameStr=(*env)->GetStringUTFChars(env,name,NULL);
调用完GetStringUTFChars后必须调用JNIEnv的void ReleaseStringUTFChars(JNIEnv*, jstring, const char*)释放新建的字符串。
(*env)-> ReleaseStringUTFChars(env,name, nameStr);
char*转换为jstring使用JNIEnv的jstring NewStringUTF(JNIEnv*, const char*);
jstring newArgName=(*env)->NewStringUTF(env, nameStr);
调用完NewStringUTF后必须调用JNIEnv的void DeleteLocalRef(JNIEnv*, jobject);释放新建的jstring。
(*env)-> DeleteLocalRef(env, newArgName);
NDK开发之javaVM的更多相关文章
- Android NDK开发之C调用Java及原生代码断点调试(二)
上一篇中,我们主要学习了Java调用本地方法,并列举了两大特殊实例来例证我们的论据,还没学习的伙伴必须先去阅读下,本次的学习是直接在上一篇的基础上进行了.点击:Android NDK开发之从Java与 ...
- Android NDK开发之Android.mk文件
Android NDK开发指南---Android.mk文件 博客分类: Android NDK开发指南 Android.mk文件语法详述 介绍: ------------ 这篇文档是用来描述你的 ...
- NDK开发之ndk-build命令详解
毫无疑问,通过执行ndk-build脚本启动android ndk构建系统. 默认情况下,ndk-build脚本在工程的主目录中执行,如: 我们可以用使用-C参数改变上述行为,-C指定工程的目录,这样 ...
- NDK开发之Application.mk文件详解
做过NDK开发的同学应该都知道有个Application.mk文件,这是android NDK构建系统使用的一个可选构建文件.它的目的是描述应用程序需要哪些模块,也定义了所有模块的一些通用变量.主要有 ...
- Android NDK开发之从Java与C互调中详解JNI使用(一)
生活 这一个礼拜过得真的是苦不堪言,上周因为打球脚踝直接扭伤,肿的想猪蹄一样,然后休息几天消肿了,可以缓慢龟速的行走了,然而五一回来上班第一天,上班鞋子还能穿上,下班脚已插不进鞋子里面了,好吧,又肿回 ...
- AndroidStudio2.2 Preview3中NDK开发之CMake和传统 JNI在目录结构和配置文件上的区别(转载)
自从AndroidStudio更新到2.2,就有了CMake和传统JNI两种开发NDK的方法,主要就是在目录结构和build.gradle上的区别,下面我们将分别介绍目录区别和build.gradle ...
- Android NDK开发之Jni的数据类型
在前面的一篇博客<Android NDK开发简介>,我简单地说明了Android NDK开发的流程,以及其重要的一环:JNI层得开发.今天我再详细说明一下自己的学习经验. JNI是Java ...
- Android NDK开发之Jni调用Java对象
https://my.oschina.net/zhiweiofli/blog/114064 通过使用合适的JNI函数,你可以创建Java对象,get.set 静态(static)和 实例(instan ...
- NDK开发之JNIEnv参数详解
即使我们Java层的函数没有参数,原生方法还是自带了两个参数,其中第一个参数就是JNIEnv. 如下: native方法: public native String stringFromC(); pu ...
随机推荐
- java 把一个文件夹里图片复制到另一个文件夹里
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.ut ...
- iNeedle产品介绍
一.产品简介 1.产品背景 1.您曾经遇到过下面的问题和烦恼吗?2.当网站上线以后,如何实时的了解网站的运行状况?3.当网站访问速度慢,是升级服务器?还是升级带宽?还是优化网站代码?4.当网站新上线一 ...
- C++之STL一般总结
重新复习一下STL 什么是STL? STL(模板和标准模板库),实现与类型无关的算法和数据类型,需要将实现中的类型参数化,允许用户根据它的需要制定不同的类型. 一.一般介绍 STL(Standard ...
- JS MD5
1 /* * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message * Digest Algorithm, as ...
- 禁用站点asp运行
禁用站点asp运行 进入 Mcafee 的 VirusScan 控制台,双击访问保护->进文件, 共享资源和文件夹保护,在要阻挡的文件和文件夹那点添加 规则名: 禁止网站进程在任何地方修建和修改 ...
- 网络错误定位案例 ICMP host *** unreachable - admin prohibited
1. 环境 一台物理服务器 9.115.251.86,上面创建两个虚机,每个虚机两个网卡: vm1:eth0 - 9.*.*.232 eth1:10.0.0.14 vm2: eth0 - 9.8.*. ...
- CKEDITOR最新版不能上传图片的解决
文献:http://bbs.csdn.net/topics/390883077 代码例子:http://download.csdn.net/download/itmyhome/7851265 1.原先 ...
- 洛谷U4727小L的二叉树[树转序列 LIS]
题目背景 勤奋又善于思考的小L接触了信息学竞赛,开始的学习十分顺利.但是,小L对数据结构的掌握实在十分渣渣. 所以,小L当时卡在了二叉树. 题目描述 在计算机科学中,二叉树是每个结点最多有两个子结点的 ...
- HDU1502/Luogu1352/UVa1220 party[树形DP]
题目描述 某大学有N个职员,编号为1~N.他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司.现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri, ...
- NOIP2006金明的预算方案[DP 有依赖的背包问题]
题目描述 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间.更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”.今 ...