,HashMap等)
而调用方式有可以分为:
(1)java调用native方法
(2)native调用java静态方法,非静态方法(成员方法),以及获取java类的成员变量。
下面按照实现方式的不同结合以上要点,通过一个例子代码来说明下具体是如何实现的。
(1)静态注册的方式
工程结构如下:(这里只列举出主要说明的项)
JNISample1
│── build.gradle
│── CMakeLists.txt
└── app
├── build.gradle
├── CMakeLists.txt
└── src
├── cpp
│ ├── JNIUtils.h
│ └── JNIUtils.cpp
└── com.alphagl.main
├── JNIUtils.java
├── MainActivity.Java
└── Person.java
代码如下:(这里做了下简化,去掉些注释以及单元测试部分的代码)
MainActivity.java :
package com.alphagl.main;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends Activity {
static {
System.loadLibrary("native-lib");
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("MainActivity", "getStringFromJNI ============= " + JNIUtils.getStringFromJNI());
Log.i("MainActivity", "getIntArrayFromJNI ============= " + JNIUtils.getIntArrayFromJNI()[0] + "," + JNIUtils.getIntArrayFromJNI()[1]);
JNIUtils.setPersonToJNI(new Person(18, "jobs"));
Log.i("MainActivity", "getPersonFromJNI ============= " + JNIUtils.getPersonFromJNI().getAge()+ "," + JNIUtils.getPersonFromJNI().getName());
}
}
Person.java :(封装的自定义对象)
package com.alphagl.main;
import android.util.Log;
public class Person {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void printPerson() {
Log.d("MainActivity", "age ======== " + age + "," + "name ======== " + name);
}
}
JNIUtils.java :
package com.alphagl.main;
public class JNIUtils {
public static native String getStringFromJNI();
public static native int[] getIntArrayFromJNI();
public static native void setPersonToJNI(Person person);
public static native Person getPersonFromJNI();
}
JNIUtils.h :
#include <jni.h>
#include <stdio.h>
#ifndef _Included_com_alphagl_main_JNIUtils
#define _Included_com_alphagl_main_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_com_alphagl_main_JNIUtils_getStringFromJNI
(JNIEnv *, jclass);
JNIEXPORT jintArray JNICALL Java_com_alphagl_main_JNIUtils_getIntArrayFromJNI
(JNIEnv *, jclass);
JNIEXPORT void JNICALL Java_com_alphagl_main_JNIUtils_setPersonToJNI
(JNIEnv *, jclass, jobject);
JNIEXPORT jobject JNICALL Java_com_alphagl_main_JNIUtils_getPersonFromJNI
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
JNIUtils.cpp
#include "JNIUtils.h"
#include <android/log.h>
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MainActivity", __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "MainActivity", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROE, "MainActivity", __VA_ARGS__)
JNIEXPORT jstring JNICALL Java_com_alphagl_main_JNIUtils_getStringFromJNI (JNIEnv *env, jclass jcls) {
LOGD(" ====================== getStringFromJNI");
// 构造一个String字符串
return env->NewStringUTF("Hello from jni");
}
JNIEXPORT jintArray JNICALL Java_com_alphagl_main_JNIUtils_getIntArrayFromJNI (JNIEnv *env, jclass jcls) {
LOGD(" ====================== getIntArrayFromJNI");
// 构造一个int[]数组
jintArray intArray = env->NewIntArray(2);
int size[]={640, 960};
// 给int[]数组赋值
env->SetIntArrayRegion(intArray, 0, 2, size);
return intArray;
}
JNIEXPORT void JNICALL Java_com_alphagl_main_JNIUtils_setPersonToJNI (JNIEnv *env, jclass jcls, jobject jobj) {
LOGD(" ====================== setPersonToJNI");
jclass jperson = env->GetObjectClass(jobj);
if (jperson != NULL) {
// 获取Person对象的age字段id
jfieldID ageFieldId = env->GetFieldID(jperson, "age", "I");
// 获取Person对象的name字段id
jfieldID nameFieldId = env->GetFieldID(jperson, "name", "Ljava/lang/String;");
// 获取Person的age成员变量
jint age = env->GetIntField(jobj, ageFieldId);
// 获取Person的name成员变量
jstring name = (jstring)env->GetObjectField(jobj, nameFieldId);
const char *c_name = env->GetStringUTFChars(name, NULL);
// 打印从Java传递过来的Person对象的age和name变量
LOGD("age ===== %d, name ===== %s", age, c_name);
}
// 以下是从JNI构造Java对象,并调用Java类中的成员方法,仅用作演示
// 获取Person对象的class
jclass jstu = env->FindClass("com/alphagl/main/Person");
// 获取Person对象的构造方法的方法id
jmethodID personMethodId = env->GetMethodID(jperson, "<init>", "(ILjava/lang/String;)V");
// 构造一个String字符串
jstring name = env->NewStringUTF("bill");
// 构造一个Person对象
jobject jPersonObj = env->NewObject(jstu, personMethodId, 30, name);
// 获取Person对象的printPerson成员方法的方法id
jmethodID jid = env->GetMethodID(jstu, "printPerson", "()V");
// 调用java的printPerson方法
env->CallVoidMethod(jPersonObj, jid);
}
JNIEXPORT jobject JNICALL Java_com_alphagl_main_JNIUtils_getPersonFromJNI(JNIEnv *env, jclass jcls) {
LOGD(" ====================== getPersonFromJNI");
// 获取Person对象的class
jclass jstudent = env->FindClass("com/alphagl/main/Person");
// 获取Person对象的构造方法的方法id
jmethodID studentMethodId = env->GetMethodID(jstudent, "<init>", "(ILjava/lang/String;)V");
// 构造一个String字符串
jstring name = env->NewStringUTF("john");
// 构造一个Person对象
jobject jstudentObj = env->NewObject(jstudent, studentMethodId, 20, name);
return jstudentObj;
}
这里再提一下,如上`JNIUtils.java`类中定义好了native方法,如何根据对象的方法签名生成对应的C/C++方法的声明。这部分内容在Android游戏开发实践(1)之NDK与JNI开发01 已经提到过,我们可以借助javah
来根据编译后的.class
生成对于的头文件。
普通做法是:
在AndroidStudio中可以:
Tools-> External Tools -> 添加
(1)javah
所在的路径
(2)命令行参数
(3)头文件生成的路径
在声明了native方法的类,右键执行javah
即可。
(2)动态注册的方式
工程结构如下:(这里只列举出主要说明的项)
JNISample2
│── build.gradle
│── CMakeLists.txt
└── app
├── build.gradle
├── CMakeLists.txt
└── src
├── cpp
│ └── JNIUtils.cpp
│
└── com.alphagl.main
├── JNIUtils.java
├── MainActivity.Java
└── Person.java
这里主要看下不同的代码部分,即JNIUtils.cpp
。
JNIUtils.cpp :
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MainActivity", __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "MainActivity", __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROE, "MainActivity", __VA_ARGS__)
#define CLASSNAME "com/alphagl/main/JNIUtils"
static jstring getStringFromJNI_native(JNIEnv *env, jclass jcls) {
LOGD(" ====================== getStringFromJNI");
// 构造一个String字符串
return env->NewStringUTF("Hello from jni");
}
static jarray getIntArrayFromJNI_native(JNIEnv *env, jclass jcls) {
LOGD(" ====================== getIntArrayFromJNI");
// 构造一个int[]数组
jintArray intArray = env->NewIntArray(2);
int size[]={640, 960};
// 给int[]数组赋值
env->SetIntArrayRegion(intArray, 0, 2, size);
return intArray;
}
static void setJniPerson_native(JNIEnv *env, jclass jcls, jobject jobj) {
LOGD(" ====================== setPersonToJNI");
jclass jperson = env->GetObjectClass(jobj);
if (jperson != NULL) {
// 获取Person对象的age字段id
jfieldID ageFieldId = env->GetFieldID(jperson, "age", "I");
// 获取Person对象的name字段id
jfieldID nameFieldId = env->GetFieldID(jperson, "name", "Ljava/lang/String;");
// 获取Person的age成员变量
jint age = env->GetIntField(jobj, ageFieldId);
// 获取Person的name成员变量
jstring name = (jstring)env->GetObjectField(jobj, nameFieldId);
const char *c_name = env->GetStringUTFChars(name, NULL);
// 打印从Java传递过来的Person对象的age和name变量
LOGD("age ===== %d, name ===== %s", age, c_name);
}
// 以下是从JNI构造Java对象,并调用Java类中的成员方法,仅用作演示
// 获取Person对象的class
jclass jstu = env->FindClass("com/alphagl/main/Person");
// 获取Person对象的构造方法的方法id
jmethodID personMethodId = env->GetMethodID(jperson, "<init>", "(ILjava/lang/String;)V");
// 构造一个String字符串
jstring name = env->NewStringUTF("bill");
// 构造一个Person对象
jobject jPersonObj = env->NewObject(jstu, personMethodId, 30, name);
// 获取Person对象的printPerson成员方法的方法id
jmethodID jid = env->GetMethodID(jstu, "printPerson", "()V");
// 调用java的printPerson方法
env->CallVoidMethod(jPersonObj, jid);
}
static jobject getJniPerson_native(JNIEnv *env, jclass jcls) {
LOGD(" ====================== getPersonFromJNI");
// 获取Person对象的class
jclass jstudent = env->FindClass("com/alphagl/main/Person");
// 获取Person对象的构造方法的方法id
jmethodID studentMethodId = env->GetMethodID(jstudent, "<init>", "(ILjava/lang/String;)V");
// 构造一个String字符串
jstring name = env->NewStringUTF("john");
// 构造一个Person对象
jobject jstudentObj = env->NewObject(jstudent, studentMethodId, 20, name);
return jstudentObj;
}
static JNINativeMethod gMethods[] = {
{"getStringFromJNI", "()Ljava/lang/String;", (void*)getStringFromJNI_native},
{"getIntArrayFromJNI", "()[I", (void*)getIntArrayFromJNI_native},
{"setPersonToJNI", "(Lcom/alphagl/main/Person;)V", (void*)setJniPerson_native},
{"getPersonFromJNI", "()Lcom/alphagl/main/Person;", (void*)getJniPerson_native}
};
static jint registerNativeMethods(JNIEnv *env, const char* className, JNINativeMethod *gMethods, int numMethods) {
jclass jcls;
jcls = env->FindClass(className);
if (jcls == NULL) {
return JNI_FALSE;
}
if (env->RegisterNatives(jcls, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
static jint registerNative(JNIEnv *env) {
return registerNativeMethods(env, CLASSNAME, gMethods, sizeof(gMethods) / sizeof(gMethods[0]));
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv *env = NULL;
if ((vm->GetEnv((void**)&env, JNI_VERSION_1_6)) != JNI_OK) {
return JNI_ERR;
}
if (!registerNative(env)) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
最后的执行结果为:
两种实现方式比较:
(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 。
接着直接在C或C++代码中设置断点即可。
技术交流QQ群:528655025
作者:AlphaGL
出处:http://www.cnblogs.com/alphagl/
版权所有,欢迎保留原文链接进行转载 😃