有了前面几篇NDK与JNI开发相关基础做铺垫,再来通过代码说明下这方面具体的操作以及一些重要的细节。那么,就继续NDK与JNI的学习总结。

  JavaVM和JNIEnv

  在jni.h头文件中定义了两种重要的数据结构JavaVM和JNIEnv,并且在C和C++中它们的实现是不同的(通过#if defined(__cplusplus)宏定义实现)。本质都是指向封装了JNI函数列表的指针。

  JavaVM

  是java虚拟机在jni层的表示。在Android中一个JVM只允许有一个JavaVM对象。可以在线程间共享一个JavaVM对象。

  JavaVM声明

  在jni中针对C语言环境和C++语言环境的JavaVM实现有所不同。

  C版的JavaVM声明为:

C++代码
  1. typedef const struct JNIInvokeInterface* JavaVM;  
  2.   
  3. struct JNIInvokeInterface {  
  4.     void*       reserved0;  
  5.     void*       reserved1;  
  6.     void*       reserved2;  
  7.   
  8.     jint        (*DestroyJavaVM)(JavaVM*);  
  9.     jint        (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);  
  10.     jint        (*DetachCurrentThread)(JavaVM*);  
  11.     jint        (*GetEnv)(JavaVM*, void**, jint);  
  12.     jint        (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);  
  13. };  

  C++版的JavaVM声明为:

Java代码
  1. typedef _JavaVM JavaVM;  
  2.   
  3. struct _JavaVM {  
  4.     const struct JNIInvokeInterface* functions;  
  5.   
  6. #if defined(__cplusplus)  
  7.     jint DestroyJavaVM()  
  8.     { return functions->DestroyJavaVM(this); }  
  9.     jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)  
  10.     { return functions->AttachCurrentThread(this, p_env, thr_args); }  
  11.     jint DetachCurrentThread()  
  12.     { return functions->DetachCurrentThread(this); }  
  13.     jint GetEnv(void** env, jint version)  
  14.     { return functions->GetEnv(this, env, version); }  
  15.     jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)  
  16.     { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }  
  17. #endif /*__cplusplus*/  
  18. };  

  JavaVM获取方式

  (1)jni动态注册的方式。在加载动态链接库的时候,JVM会调用JNI_OnLoad(JavaVM* vm, void* reserved),并传入JavaVM指针:

C++代码
  1. JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {  
  2.   
  3. }  

  (2)在本地代码中通过调用jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*)来创建。

  JNIEnv

  简单来说,就是JNIEnv提供了所有JNI函数调用的接口。不能在线程间共享同一个JNIEnv变量,仅在创建它的线程有效,如果要在其它线程访问JVM,需要调用AttachCurrentThread或AttachCurrentThreadAsDaemon将当前线程与JVM绑定。再通过JavaVM对象的GetEnv来获取JNIEnv。

  JNIEnv声明

  与JavaVM类似,JNIEnv在C和C++语言中的声明也有所不同。

  C版的JavaVM声明为:

C++代码
  1. typedef const struct JNINativeInterface* JNIEnv;  
  2.   
  3. struct JNINativeInterface {  
  4.         jint        (*GetVersion)(JNIEnv *);  
  5.         ···  
  6. }  

  C++版的JavaVM声明为:

C++代码
  1. typedef _JNIEnv JNIEnv;  
  2.   
  3. struct _JNIEnv {  
  4.     /* do not rename this; it does not seem to be entirely opaque */  
  5.     const struct JNINativeInterface* functions;  
  6.   
  7. #if defined(__cplusplus)  
  8.   
  9.     jint GetVersion()  
  10.     { return functions->GetVersion(this); }  
  11.   
  12.     ...  
  13. }  

  jobject、jclass、jmethodID和jfieldID

  jobject:

  是JNI对原始java.lang.Object的映射。可以通过调用NewObject来获得一个jobject对象。例如:

  env->NewObject(jclass clazz, jmethodID methodID, ...)

  jclass:

  是JNI对原始java.lang.Class的映射。可以通过调用FindClass来获得jclass对象。例如:

  jclass intArrayClass = env->FindClass("[I");

  jmethodID:

  获取对应类成员方法的方法id。可以通过调用GetMethodID来获取。例如:

  jmethodID myMethodId = env->(jclass clazz, const char *name, const char *sig);

  jfieldID:

  获取对应类成员变量的字段id。可以通过调用GetFieldID来获得。例如:

  jfieldID nameFieldId = env->GetFieldID(jclass clazz, const char *name, const char *sig)

  本地库调用

  JNI的加载本地库中的代码,步骤简述如下(同时,也是Android推荐的做法):

  (1)在java类的静态块中调用System.loadLibrary来加载动态库,若动态库的名字为libcocos2dx.so,那么,调用为:

Java代码
  1. static {  
  2.     System.loadLibrary("cocos2dx");  
  3. }  

  (2)在本地代码中实现JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);方法。

  (3)在该JNI_OnLoad方法中,调用env->RegisterNatives(jclass clazz, const JNINativeMethod *methods, jint nMethods)注册所有本地的实现方法。推荐将方法声明为静态的,这样不会占据设备上的符号表的空间。

  JNI通信

  JNI的通信过程,其实就是原生Java与底层C/C++数据传递的过程。这里简单归纳下,数据传递分为以下这几种:

  • 传递基本数据类型(例如:int,float等)

  • 传递对象(例如:String,Object,自定义类MyObject等)

  • 传递数组(例如:int[], String[]等)

  • 传递集合对象(例如:ArrayList,HashMap等)

  而调用方式有可以分为:

  (1)java调用native方法

  (2)native调用java静态方法,非静态方法(成员方法),以及获取java类的成员变量。

  下面按照实现方式的不同结合以上要点,通过一个例子代码来说明下具体是如何实现的。

  (1)静态注册的方式

  工程结构如下:(这里只列举出主要说明的项)

XML/HTML代码
  1. JNISample1    
  2.   │── build.gradle  
  3.   │── CMakeLists.txt   
  4.   └── app   
  5.       ├── build.gradle  
  6.       ├── CMakeLists.txt  
  7.       └── src   
  8.           ├── cpp  
  9.           │    ├── JNIUtils.h  
  10.           │    └── JNIUtils.cpp  
  11.           └── com.alphagl.main  
  12.                     ├── JNIUtils.java  
  13.                     ├── MainActivity.Java  
  14.                     └── Person.java  

  代码如下:(这里做了下简化,去掉些注释以及单元测试部分的代码)

  MainActivity.java:

Java代码
  1. package com.alphagl.main;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.util.Log;  
  6.   
  7. public class MainActivity extends Activity {  
  8.   
  9.     static {  
  10.         System.loadLibrary("native-lib");  
  11.     }  
  12.   
  13.     protected void onCreate(Bundle savedInstanceState) {  
  14.         super.onCreate(savedInstanceState);  
  15.         setContentView(R.layout.activity_main);  
  16.   
  17.         Log.i("MainActivity""getStringFromJNI ============= " + JNIUtils.getStringFromJNI());  
  18.         Log.i("MainActivity""getIntArrayFromJNI ============= " + JNIUtils.getIntArrayFromJNI()[0] + "," + JNIUtils.getIntArrayFromJNI()[1]);  
  19.         JNIUtils.setPersonToJNI(new Person(18"jobs"));  
  20.         Log.i("MainActivity""getPersonFromJNI ============= " + JNIUtils.getPersonFromJNI().getAge()+ "," + JNIUtils.getPersonFromJNI().getName());  
  21.     }  
  22. }  

  Person.java:(封装的自定义对象)

Java代码
  1. package com.alphagl.main;  
  2.   
  3. import android.util.Log;  
  4.   
  5. public class Person {  
  6.     private int age;  
  7.     private String name;  
  8.   
  9.     public Person(int age, String name) {  
  10.         this.age = age;  
  11.         this.name = name;  
  12.     }  
  13.   
  14.     public void setAge(int age) {  
  15.         this.age = age;  
  16.     }  
  17.   
  18.     public int getAge() {  
  19.         return age;  
  20.     }  
  21.   
  22.     public void setName(String name) {  
  23.         this.name = name;  
  24.     }  
  25.   
  26.     public String getName() {  
  27.         return name;  
  28.     }  
  29.   
  30.     public void printPerson() {  
  31.         Log.d("MainActivity""age ======== " + age + "," + "name ======== " + name);  
  32.     }  
  33. }  

  JNIUtils.java:

Java代码
  1. package com.alphagl.main;  
  2.   
  3. public class JNIUtils {  
  4.     public static native String getStringFromJNI();  
  5.     public static native int[] getIntArrayFromJNI();  
  6.     public static native void setPersonToJNI(Person person);  
  7.     public static native Person getPersonFromJNI();  
  8. }  

  JNIUtils.h:

C++代码
  1. #include <jni.h>  
  2. #include <stdio.h>  
  3.   
  4. #ifndef _Included_com_alphagl_main_JNIUtils  
  5. #define _Included_com_alphagl_main_JNIUtils  
  6. #ifdef __cplusplus  
  7. extern "C" {  
  8. #endif  
  9.   
  10. JNIEXPORT jstring JNICALL Java_com_alphagl_main_JNIUtils_getStringFromJNI  
  11.   (JNIEnv *, jclass);  
  12.   
  13.   
  14. JNIEXPORT jintArray JNICALL Java_com_alphagl_main_JNIUtils_getIntArrayFromJNI  
  15.   (JNIEnv *, jclass);  
  16.   
  17.   
  18. JNIEXPORT void JNICALL Java_com_alphagl_main_JNIUtils_setPersonToJNI  
  19.   (JNIEnv *, jclass, jobject);  
  20.   
  21.   
  22. JNIEXPORT jobject JNICALL Java_com_alphagl_main_JNIUtils_getPersonFromJNI  
  23.   (JNIEnv *, jclass);  
  24.   
  25. #ifdef __cplusplus  
  26. }  
  27. #endif  
  28. #endif  

  JNIUtils.cpp

C++代码
  1. #include "JNIUtils.h"  
  2. #include <android/log.h>  
  3.   
  4. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MainActivity", __VA_ARGS__)  
  5. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "MainActivity", __VA_ARGS__)  
  6. #define LOGE(...) __android_log_print(ANDROID_LOG_ERROE, "MainActivity", __VA_ARGS__)  
  7.   
  8.   
  9. JNIEXPORT jstring JNICALL Java_com_alphagl_main_JNIUtils_getStringFromJNI (JNIEnv *env, jclass jcls) {  
  10.     LOGD(" ====================== getStringFromJNI");  
  11.     // 构造一个String字符串  
  12.     return env->NewStringUTF("Hello from jni");  
  13. }  
  14.   
  15.   
  16. JNIEXPORT jintArray JNICALL Java_com_alphagl_main_JNIUtils_getIntArrayFromJNI (JNIEnv *env, jclass jcls) {  
  17.     LOGD(" ====================== getIntArrayFromJNI");  
  18.     // 构造一个int[]数组  
  19.     jintArray intArray = env->NewIntArray(2);  
  20.     int size[]={640, 960};  
  21.     // 给int[]数组赋值  
  22.     env->SetIntArrayRegion(intArray, 0, 2, size);  
  23.   
  24.     return intArray;  
  25. }  
  26.   
  27.   
  28. JNIEXPORT void JNICALL Java_com_alphagl_main_JNIUtils_setPersonToJNI (JNIEnv *env, jclass jcls, jobject jobj) {  
  29.     LOGD(" ====================== setPersonToJNI");  
  30.     jclass jperson = env->GetObjectClass(jobj);  
  31.     if (jperson != NULL) {  
  32.         // 获取Person对象的age字段id  
  33.         jfieldID ageFieldId = env->GetFieldID(jperson, "age""I");  
  34.         // 获取Person对象的name字段id  
  35.         jfieldID nameFieldId = env->GetFieldID(jperson, "name""Ljava/lang/String;");  
  36.   
  37.         // 获取Person的age成员变量  
  38.         jint age = env->GetIntField(jobj, ageFieldId);  
  39.         // 获取Person的name成员变量  
  40.         jstring name = (jstring)env->GetObjectField(jobj, nameFieldId);  
  41.   
  42.         const char *c_name = env->GetStringUTFChars(name, NULL);  
  43.   
  44.         // 打印从Java传递过来的Person对象的age和name变量  
  45.         LOGD("age ===== %d, name ===== %s", age, c_name);  
  46.     }  
  47.   
  48.     // 以下是从JNI构造Java对象,并调用Java类中的成员方法,仅用作演示  
  49.     // 获取Person对象的class  
  50.     jclass jstu = env->FindClass("com/alphagl/main/Person");  
  51.     // 获取Person对象的构造方法的方法id  
  52.     jmethodID personMethodId = env->GetMethodID(jperson, "<init>""(ILjava/lang/String;)V");  
  53.     // 构造一个String字符串  
  54.     jstring name = env->NewStringUTF("bill");  
  55.   
  56.     // 构造一个Person对象  
  57.     jobject  jPersonObj = env->NewObject(jstu, personMethodId, 30, name);  
  58.     // 获取Person对象的printPerson成员方法的方法id  
  59.     jmethodID jid = env->GetMethodID(jstu, "printPerson""()V");  
  60.     // 调用java的printPerson方法  
  61.     env->CallVoidMethod(jPersonObj, jid);  
  62. }  
  63.   
  64.   
  65. JNIEXPORT jobject JNICALL Java_com_alphagl_main_JNIUtils_getPersonFromJNI(JNIEnv *env, jclass jcls) {  
  66.     LOGD(" ====================== getPersonFromJNI");  
  67.     // 获取Person对象的class  
  68.     jclass jstudent = env->FindClass("com/alphagl/main/Person");  
  69.     // 获取Person对象的构造方法的方法id  
  70.     jmethodID studentMethodId = env->GetMethodID(jstudent, "<init>""(ILjava/lang/String;)V");  
  71.     // 构造一个String字符串  
  72.     jstring name = env->NewStringUTF("john");  
  73.     // 构造一个Person对象  
  74.     jobject  jstudentObj = env->NewObject(jstudent, studentMethodId, 20, name);  
  75.   
  76.     return jstudentObj;  
  77. }  

  这里再提一下,如上`JNIUtils.java`类中定义好了native方法,如何根据对象的方法签名生成对应的C/C++方法的声明。这部分内容在Android游戏开发实践(1)之NDK与JNI开发01 已经提到过,我们可以借助javah来根据编译后的.class生成对于的头文件。

  普通做法是:

Android游戏开发实践之NDK与JNI开发04

  在AndroidStudio中可以:

  Tools-> External Tools -> 添加

Android游戏开发实践之NDK与JNI开发04

  (1)javah所在的路径

  (2)命令行参数

  (3)头文件生成的路径

Android游戏开发实践之NDK与JNI开发04

  在声明了native方法的类,右键执行javah即可。

  (2)动态注册的方式

  工程结构如下:(这里只列举出主要说明的项)

XML/HTML代码
  1. JNISample2    
  2.   │── build.gradle  
  3.   │── CMakeLists.txt   
  4.   └── app   
  5.       ├── build.gradle  
  6.       ├── CMakeLists.txt  
  7.       └── src   
  8.           ├── cpp  
  9.           │   └── JNIUtils.cpp  
  10.           │      
  11.           └── com.alphagl.main  
  12.                     ├── JNIUtils.java  
  13.                     ├── MainActivity.Java  
  14.                     └── Person.java  

  这里主要看下不同的代码部分,即JNIUtils.cpp。

  JNIUtils.cpp:

C++代码
  1. #include <jni.h>  
  2. #include <string>  
  3. #include <android/log.h>  
  4.   
  5. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MainActivity", __VA_ARGS__)  
  6. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "MainActivity", __VA_ARGS__)  
  7. #define LOGE(...) __android_log_print(ANDROID_LOG_ERROE, "MainActivity", __VA_ARGS__)  
  8.   
  9. #define CLASSNAME "com/alphagl/main/JNIUtils"  
  10.   
  11. static jstring getStringFromJNI_native(JNIEnv *env, jclass jcls) {  
  12.     LOGD(" ====================== getStringFromJNI");  
  13.     // 构造一个String字符串  
  14.     return env->NewStringUTF("Hello from jni");  
  15. }  
  16.   
  17. static jarray getIntArrayFromJNI_native(JNIEnv *env, jclass jcls) {  
  18.     LOGD(" ====================== getIntArrayFromJNI");  
  19.     // 构造一个int[]数组  
  20.     jintArray intArray = env->NewIntArray(2);  
  21.     int size[]={640, 960};  
  22.     // 给int[]数组赋值  
  23.     env->SetIntArrayRegion(intArray, 0, 2, size);  
  24.   
  25.     return intArray;  
  26. }  
  27.   
  28. static void setJniPerson_native(JNIEnv *env, jclass jcls, jobject jobj) {  
  29.     LOGD(" ====================== setPersonToJNI");  
  30.     jclass jperson = env->GetObjectClass(jobj);  
  31.     if (jperson != NULL) {  
  32.         // 获取Person对象的age字段id  
  33.         jfieldID ageFieldId = env->GetFieldID(jperson, "age""I");  
  34.         // 获取Person对象的name字段id  
  35.         jfieldID nameFieldId = env->GetFieldID(jperson, "name""Ljava/lang/String;");  
  36.   
  37.         // 获取Person的age成员变量  
  38.         jint age = env->GetIntField(jobj, ageFieldId);  
  39.         // 获取Person的name成员变量  
  40.         jstring name = (jstring)env->GetObjectField(jobj, nameFieldId);  
  41.   
  42.         const char *c_name = env->GetStringUTFChars(name, NULL);  
  43.   
  44.         // 打印从Java传递过来的Person对象的age和name变量  
  45.         LOGD("age ===== %d, name ===== %s", age, c_name);  
  46.     }  
  47.   
  48.     // 以下是从JNI构造Java对象,并调用Java类中的成员方法,仅用作演示  
  49.     // 获取Person对象的class  
  50.     jclass jstu = env->FindClass("com/alphagl/main/Person");  
  51.     // 获取Person对象的构造方法的方法id  
  52.     jmethodID personMethodId = env->GetMethodID(jperson, "<init>""(ILjava/lang/String;)V");  
  53.     // 构造一个String字符串  
  54.     jstring name = env->NewStringUTF("bill");  
  55.   
  56.     // 构造一个Person对象  
  57.     jobject  jPersonObj = env->NewObject(jstu, personMethodId, 30, name);  
  58.     // 获取Person对象的printPerson成员方法的方法id  
  59.     jmethodID jid = env->GetMethodID(jstu, "printPerson""()V");  
  60.     // 调用java的printPerson方法  
  61.     env->CallVoidMethod(jPersonObj, jid);  
  62. }  
  63.   
  64. static jobject getJniPerson_native(JNIEnv *env, jclass jcls) {  
  65.     LOGD(" ====================== getPersonFromJNI");  
  66.     // 获取Person对象的class  
  67.     jclass jstudent = env->FindClass("com/alphagl/main/Person");  
  68.     // 获取Person对象的构造方法的方法id  
  69.     jmethodID studentMethodId = env->GetMethodID(jstudent, "<init>""(ILjava/lang/String;)V");  
  70.     // 构造一个String字符串  
  71.     jstring name = env->NewStringUTF("john");  
  72.     // 构造一个Person对象  
  73.     jobject  jstudentObj = env->NewObject(jstudent, studentMethodId, 20, name);  
  74.   
  75.     return jstudentObj;  
  76. }  
  77.   
  78. static JNINativeMethod gMethods[] = {  
  79.         {"getStringFromJNI""()Ljava/lang/String;", (void*)getStringFromJNI_native},  
  80.         {"getIntArrayFromJNI""()[I", (void*)getIntArrayFromJNI_native},  
  81.         {"setPersonToJNI""(Lcom/alphagl/main/Person;)V", (void*)setJniPerson_native},  
  82.         {"getPersonFromJNI""()Lcom/alphagl/main/Person;", (void*)getJniPerson_native}  
  83. };  
  84.   
  85. static jint registerNativeMethods(JNIEnv *env, const char* className, JNINativeMethod *gMethods, int numMethods) {  
  86.     jclass jcls;  
  87.     jcls = env->FindClass(className);  
  88.     if (jcls == NULL) {  
  89.         return JNI_FALSE;  
  90.     }  
  91.   
  92.     if (env->RegisterNatives(jcls, gMethods, numMethods) < 0) {  
  93.         return JNI_FALSE;  
  94.     }  
  95.   
  96.     return JNI_TRUE;  
  97. }  
  98.   
  99. static jint registerNative(JNIEnv *env) {  
  100.     return registerNativeMethods(env, CLASSNAME, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));  
  101. }  
  102.   
  103. JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {  
  104.     JNIEnv *env = NULL;  
  105.     if ((vm->GetEnv((void**)&env, JNI_VERSION_1_6)) != JNI_OK) {  
  106.         return JNI_ERR;  
  107.     }  
  108.   
  109.     if (!registerNative(env)) {  
  110.         return JNI_ERR;  
  111.     }  
  112.   
  113.     return JNI_VERSION_1_6;  
  114. }  

  最后的执行结果为:

Android游戏开发实践之NDK与JNI开发04

  两种实现方式比较:

  (1)动态注册中,可以不用声明形如Java_packageName_className_methodName格式的方法。

  (2)动态注册中,要重写JNI_OnLoad方法,手动调用RegisterNatives来注册本地方法,以及声明在JNINativeMethod中。

  (3)动态注册,明显这种方式更灵活,但对代码要求更高,推荐使用这种方式。

  以上示例代码都已上传Github,有需要的可以自行查看。

  https://github.com/cnsuperx/android-jni-example

  JNI调试

  如果安装了LLVM环境的话,直接将Jni Debuggable选项打开即可。环境搭建可以参考Android游戏开发实践(1)之NDK与JNI开发03。

Android游戏开发实践之NDK与JNI开发04

  接着直接在C或C++代码中设置断点即可。

本文发布:Android开发网
本文地址:http://www.jizhuomi.com/android/game/713.html
2017年9月13日
发布:鸡啄米 分类:Android游戏开发 浏览: 评论:1