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?

  1. struct _JNIEnv;
  1. struct _JavaVM;
  1. #if defined(__cplusplus)
  1. typedef _JNIEnv JNIEnv;                                 //C++使用这个类型
  1. typedef _JavaVM JavaVM;                                 //C++使用这个类型
  1. #else
  1. typedef const struct JNINativeInterface* JNIEnv;        //C使用这个类型
  1. typedef const struct JNIInvokeInterface* JavaVM;        //C使用这个类型
  1. #endif
  1. struct JNINativeInterface
  1. {
  2. /****省略了的代码****/
  3. jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
  4. /****省略了的代码****/
  5. jobject     (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);
  6. /****省略了的代码****/
  7. };
  1. struct _JNIEnv
  1. {
  2. const struct JNINativeInterface* functions;
  1. #if defined(__cplusplus)
  1. /****省略了的代码****/
  2. jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
  3. { return functions->GetMethodID(this, clazz, name, sig); }
  4. /****省略了的代码****/
  5. jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
  6. { return functions->GetStaticObjectField(this, clazz, fieldID); }
  7. /****省略了的代码****/
  1. #endif /*__cplusplus*/
  1. };
  1. struct JNIInvokeInterface
  1. {
  2. /****省略了的代码****/
  3. jint (*GetEnv)(JavaVM*, void**, jint);
  4. jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
  5. };
  1. struct _JavaVM
  1. {
  2. const struct JNIInvokeInterface* functions;
  1. #if defined(__cplusplus)
  1. /****省略了的代码****/
  2. jint GetEnv(void** env, jint version)
  3. { return functions->GetEnv(this, env, version); }
  4. jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
  5. { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
  1. #endif /*__cplusplus*/
  1. };

假如我们用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函数的方式大致上相同,下面给出具体的代码。

  1. /* JNINativeMethod数组的定义在C和C++中都一样*/
  1. static JNINativeMethod gMethods[] = {
  1. {
  2. "jobjectProcess",
  3. "(Lcom/example/hellojni/HelloJni$Student;Ljava/lang/Integer;)V",
  4. (void*)jobjectProcess
  5. }
  6. /*被省略掉的代码*/
  7. };
  8. jint JNI_OnLoad(JavaVM* vm,void* reserved)
  9. {
  10. JNIEnv* env = NULL;
  11. jint result=-1;
  12. if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)
  13. return result;
  14. jclass HelloJniClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni");
  15. /* C */
  16. jint r=(*env)->RegisterNatives(env, HelloJniClazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));
  17. /* C++ */
  18. r=AndroidRuntime::registerNativeMethods(env,"com/example/hellojni/HelloJni",gMethods,NELEM(gMethods));
  1. /*或者env->RegisterNatives(HelloJniClazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));*/
  2. if(0 == r)
  3. //注册native函数成功
  4. else
  5. //注册native函数失败
  6. return JNI_VERSION_1_4;
  7. }
  1. void JNI_OnUnload(JavaVM* vm,void* reserved)
  1. {
  2. JNIEnv* env = NULL;
  3. if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)
  4. return;
  5. jclass HelloJniClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni");
  6. /* C */
  7. jint r=(*env)->UnregisterNatives(env,HelloJniClazz);
  8. /* C++ */
  9. jint r= env->UnregisterNatives(HelloJniClazz)
  10. if(r == 0)
  11. //注销native函数成功
  12. else
  13. //注销native函数失败
  14. }

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中定义。

  1. typedef enum android_LogPriority
  1. {
  2. ANDROID_LOG_UNKNOWN = 0,
  3. ANDROID_LOG_DEFAULT,    /* only for SetMinPriority() */
  4. ANDROID_LOG_VERBOSE,
  5. ANDROID_LOG_DEBUG,
  6. ANDROID_LOG_INFO,
  7. ANDROID_LOG_WARN,
  8. ANDROID_LOG_ERROR,
  9. ANDROID_LOG_FATAL,
  10. ANDROID_LOG_SILENT,     /* only for SetMinPriority(); must be last */
  11. } android_LogPriority;

我们可以根据调试信息的不同类别而选用不同的枚举常量。

4.关于jclass

jclass代表JAVA中的java.lang.Class。我们看jclass的定义,下面给出$NDK\platforms\android-5\arch-arm\usr\include\jni.h的部分代码

  1. #ifdef __cplusplus
  1. /*Reference types, in C++*/
  1. class _jobject {};
  2. class _jclass : public _jobject {}; /*_jclass继承_jobject*/
  3. typedef _jclass*        jclass;
  1. #else
  1. /*Reference types, in C.*/
  1. typedef void*           jobject;
  2. typedef jobject         jclass;
  1. #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代码

  1. static jclass StudentClazz;   //全局变量
  1. jint JNI_OnLoad(JavaVM* vm,void* reserved)
  2. {
  3. JNIEnv* env = NULL;
  4. jint result=-1;
  5. if( (*vm)->GetEnv(vm,(void**)&env , JNI_VERSION_1_4) != JNI_OK)
  6. return result;
  7. StudentClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni$Student");    //初始化
  8. return JNI_VERSION_1_4;
  9. }
  10. JNIEXPORT void JNICALL jobjectProcess(JNIEnv *env, jobject instance,jobject student,jobject flag)
  11. {
  12. /*StudentClazz=(*env)->FindClass(env,"com/example/hellojni/HelloJni$Student");*/
  13. __android_log_print(ANDROID_LOG_DEBUG,"在jobjectProcess中输出","StudentClazz=%p",StudentClazz);
  14. nameFieldId=(*env)->GetFieldID(env,StudentClazz,"name","Ljava/lang/String;");
  15. jstring name=(jstring)((*env)->GetObjectField(env,student,nameFieldId));
  16. }

下面是Activity的代码

  1. static
  1. {
  2. System.loadLibrary("hello-jni");
  3. }
  1. public native void jobjectProcess(Student student,Integer flag);
  2. public static class Student{/*省略的代码*/}
  3. protected void onResume()
  1. {
  2. jobjectProcess(new Student(),new Integer(20));
  3. super.onResume();
  4. }

上面的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);

时间: 2024-07-30 21:02:33

NDK开发之javaVM的相关文章

AndroidStudio2.2 Preview3中NDK开发之CMake和传统 JNI在目录结构和配置文件上的区别(转载)

自从AndroidStudio更新到2.2,就有了CMake和传统JNI两种开发NDK的方法,主要就是在目录结构和build.gradle上的区别,下面我们将分别介绍目录区别和build.gradle种配置的区别(提示:在第一次用CMake时,最好在新建项目时勾选Include C++ Support,这样这个项目的NDK开发就是CMake方式,这估计也是Android以后主推的方式,建好项目,熟悉CMake方式的目录结构,这样对以后建普通项目再转CMake开发NDK有很大帮助,后面会讲到普通项

NDK开发之ndk-build命令详解

毫无疑问,通过执行ndk-build脚本启动android ndk构建系统. 默认情况下,ndk-build脚本在工程的主目录中执行,如: 我们可以用使用-C参数改变上述行为,-C指定工程的目录,这样我们就可以在任何目录执行ndk-build脚本了. 如果源文件没有被修改,那么android ndk构建系统不会重新构建目标,这时我们可以使用参数-B来强制重新构建所有源代码. 如果想要清理生成的二进制文件和目标文件,可以使用ndk-build clean命令. android ndk构建系统依赖于

NDK开发之Application.mk文件详解

做过NDK开发的同学应该都知道有个Application.mk文件,这是android NDK构建系统使用的一个可选构建文件.它的目的是描述应用程序需要哪些模块,也定义了所有模块的一些通用变量.主要有以下几个变量. APP_MODULES,默认情况下,ndk会构建在android.xk文件中声明的所有模块.但是这个变量可是覆盖上述行为. 假如我们的android.mk文件是这样的: LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_

android ndk开发之 extern &quot;C&quot; 编译出错

感叹:神一般的eclipse! 首先, 我这么写 extern "C" void func(){}; 代码爆红:(error: expected identifier or ‘(’ before string constant ) what the fuck!! 百度后是说c编译器不支持这种写法, 会报错, 虽然我知道我用的是c++编译器,然而决定还是改一下 #ifdef __cplusplus #define EXTERNC externc "C" #else #

Android NDK开发之Jni调用Java对象

https://my.oschina.net/zhiweiofli/blog/114064 通过使用合适的JNI函数,你可以创建Java对象,get.set 静态(static)和 实例(instance)的域,调用静态(static)和实例(instance)函数.JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数.下表列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数.每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对

NDK开发之JNIEnv参数详解

即使我们Java层的函数没有参数,原生方法还是自带了两个参数,其中第一个参数就是JNIEnv. 如下: native方法: public native String stringFromC(); public native String stringFromCpp(); 原生方法: jstring Java_com_example_jni_MainActivity_stringFromC(JNIEnv* env,jobject thiz){ return (*env)->NewStringUTF

[原]零基础学习SDL开发之在Android使用SDL2.0显示BMP图

关于如何移植SDL2.0到安卓上面来参考我的上一篇文章:[原]零基础学习SDL开发之移植SDL2.0到Android 在一篇文章我们主要使用SDL2.0来加载一张BMP图来渲染显示. 博主的开发环境:Ubuntu 14.04 64位,Eclipse + CDT + ADT+NDK 博主曾经自己使用NDK编译出了libSDL2.so,然后使用共享库的方式来调用libSDL2中的函数,结果发现SDL\src\core\android\SDL_android.c 这个jni函数写的实在是不够自己另外做

[原]零基础学习SDL开发之在Android使用SDL2.0显示BMP叠加图

关于如何移植在android上使用SDL,可以参考[原]零基础学习SDL开发之移植SDL2.0到Android 和 [原]零基础学习SDL开发之在Android使用SDL2.0显示BMP图 . 在一篇文章我们主要使用SDL2.0来加载一张BMP图来渲染显示,同时叠加一张图作为背景图. 博主的开发环境:Ubuntu 14.04 64位,Eclipse + CDT + ADT+NDK 在前面两篇文章我们知道了如何移植SDL2.0到android上面来,并且可以在Android上面来显示一张图片,这篇

[Unity3D]Unity3D游戏开发之从Unity3D到Eclipse

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 喜欢我的博客请记住我的名字:秦元培,我的博客地址是blog.csdn.net/qinyuanpei. 转载请注明出处,本