概述
JNI(Java Native Interface):Java本地接口。是为了方便使用Java调用C、C++等本地代码所封装的一层接口。大家都知道,Java的优点是跨平台,但是作为优点的同时,其在本地交互的时候就变成了缺点。Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,于是Java提供了JNI专门用于和本地代码交互,这样就增强了Java语言的本地交互能力。
JNI描述符
域描述符
基本类型描述符
| Field Desciptor | Java Language Type | 
| Z | boolean | 
| B | byte | 
| C | char | 
| S | short | 
| I | int | 
| J | long | 
| F | float | 
| D | Double | 
除了boolean和long类型分别为Z和J外,其他的描述符对应的都是Java类型名的大写字母。void的描述符为V
引用类型描述符
一般的引用类型描述符规则:
如,String类型的域描述符为:
数组的域描述符比较特殊,规则:其中有多少级数组就有多少个“[”,数组的类型为类时,则有分号,为基本类型时没有分号
例如:
| 12
 3
 4
 5
 
 | int[]      描述符为 [Ilong[]     描述符为 [J
 String[]   描述符为 [Ljava/lang/String;
 int[][]    描述符为 [[I
 double[][] 描述符为 [[D
 
 | 
类描述符
类描述符是类的完整名称:包名+类名,Java中包名用.分隔,JNI中改成/分隔
如,Java中java.lang.String类的描述符为java/lang/String
方法描述符(方法签名)
方法描述符需要将所有类型的域描述符按照声明顺序放入括号,然后加上返回值类型的域描述符,规则如下:
例如:
| 12
 3
 4
 
 | Java 层方法               -->    JNI 函数签名String getString()       --> ()Ljava/lang/String;
 int sum(int a, int b)    --> (II)I
 void main(String[] args) --> ([Ljava/lang/String;)V
 
 | 
JNI方法结构分析
命名规则:
extern "C" JNIEXPORT 返回值 JNICALL Java_全路径类名_方法名__参数签名(JNIEnv* , jobject, 其它参数);
说明:
JNIEXPORT、JNICALL:这两个关键词是宏定义,主要是注明该函数是JNI函数,当虚拟机加载so库时,如果发现函数含有这两个宏定义时,就会链接到对应的Java层的native方法。
Java_:标识该函数来源于Java。
__参数签名:如果是重载方法,则有参数签名,否则没有。参数签名的斜杠“/”改为“_”,分号“;”改为”_2”连接。
extern “C” :如果在使用的是C++,在函数前面加extern “C”,表示按照C的方式编译。
JNIEnv:指向函数表指针的指针,函数表里面定义了很多JNI函数,通过这些函数可以实现Java层和JNI层的交互,就是说JNIEnv调用JNI函数可以访问Java虚拟机,操作Java对象。
jobject:调用该方法的Java实例对象。对于Java的native方法,static和非static方法的区别在于第二个参数,static的为jclass,非static的为jobject。
示例:

打印log
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | #include <android/log.h>
 #define  LOG_TAG    "native-lib"
 #define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
 #define  LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
 #define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
 
 extern "C" JNIEXPORT jstring JNICALL
 Java_com_github_xch168_ndkdemo2_MainActivity_stringFromJNI(JNIEnv* env, jobject) {
 
 LOGI("invoke method: stringFromJNI");
 
 std::string hello = "Hello from C++";
 return env->NewStringUTF(hello.c_str());
 }
 
 | 
JNI函数访问Java对象的变量
步骤:
- 通过- env->GetObjectClass(jobject)获取Java对象的class类,返回一个jclass;
 
- 调用- env->GetFieldID(jclazz, fieldName, signature)的到该变量的id,即jfieldID;
 - 如果变量是静态static的,则调用的方法为- GetStaticFieldID。
 
- 最后通过调用- env->Get{type}Field(jobject, fieldId)的到该变量的值。其中{type}是变量的类型;
 - 如果变量是静态static的,则调用的方法是- GetStatic{type}Field(jclass, fieldId)
 - 注意:static的话,是使用jclass作为参数 
访问非static变量
Java层:native方法定义和调用
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | private int num = 1;
 public native int addNum();
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 Log.i(TAG, "调用前:num=" + num);
 Log.i(TAG, "调用后:" + addNum());
 }
 
 | 
C++层:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | extern "C"JNIEXPORT jint JNICALL
 Java_com_github_xch168_ndkdemo_MainActivity_addNum(JNIEnv *env, jobject instance) {
 
 jclass jclazz = env->GetObjectClass(instance);
 
 jfieldID fid = env->GetFieldID(jclazz, "num", "I");
 
 jint num = env->GetIntField(instance, fid);
 num++;
 return num;
 }
 
 | 
输出结果:
| 12
 
 | MainActivity: 调用前:num=1MainActivity: 调用后:2
 
 | 
访问static变量
Java层:native方法定义和调用
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | public static String name = "Tom";
 public native void accessStaticField();
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 Log.i(TAG, "调用前:name=" + name);
 accessStaticField();
 Log.i(TAG, "调用后:" + name);
 }
 
 | 
C++层:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | extern "C"JNIEXPORT void JNICALL
 Java_com_github_xch168_ndkdemo_MainActivity_accessStaticField(JNIEnv *env, jobject instance) {
 jclass jclazz = env->GetObjectClass(instance);
 jfieldID fid = env->GetStaticFieldID(jclazz, "name", "Ljava/lang/String;");
 jstring name = (jstring)(env->GetStaticObjectField(jclazz, fid));
 const char *str = env->GetStringUTFChars(name, JNI_FALSE);
 
 
 
 
 
 
 char ch[30] = "hello, ";
 strcat(ch, str);
 jstring new_str = env->NewStringUTF(ch);
 
 env->SetStaticObjectField(jclazz, fid, new_str);
 }
 
 | 
输出结果:
| 12
 
 | MainActivity: 调用前:name=TomMainActivity: 调用后:hello, Tom
 
 | 
注意:获取Java静态变量,都是调用JNI相应静态函数,不能调用非静态的,同时留意传入的参数是jclass,而不是jobject。
访问private变量,并对其修改
Java层:native方法定义和调用
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | private int age = 25;
 public native void accessPrivateField();
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 Log.i(TAG, "调用前:age=" + age);
 accessPrivateField();
 Log.i(TAG, "调用后:age" + age);
 }
 
 | 
C++层:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | extern "C"JNIEXPORT void JNICALL
 Java_com_github_xch168_ndkdemo_MainActivity_accessPrivateField(JNIEnv *env, jobject instance) {
 jclass clazz = env->GetObjectClass(instance);
 jfieldID fid = env->GetFieldID(clazz, "age", "I");
 jint age = env->GetIntField(instance, fid);
 age++;
 env->SetIntField(instance, fid, age);
 }
 
 | 
输出结果:
| 12
 
 | MainActivity: 调用前:age=25MainActivity: 调用后:age=26
 
 | 
JNI函数调用Java对象的方法
步骤:
- 通过- env->GetObjectClass(jobject)获取Java对象的class类,返回一个jclass;
 
- 通过- env->GetMethodID(jclass, methodName, sign)获取到Java对象的方法id,即jmethodID,当获取的方法是static时,使用- GetStaticMethodID;
 
- 通过JNI函数- env->Call{type}Method(jobject, jmethod, param...)实现调用Java的方法;
 - 若调用的是static方法,则使用- CallStatic{type}Method(jclass, jmethod, param...),使用的是jclass。
 
调用Java公有方法
Java层:native方法定义和调用
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | private String name = "Tom";
 private void setName(String name) {
 this.name = name;
 }
 
 public native void invokePublicMethod();
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 Log.i(TAG, "调用前:name=" + name);
 accessPublicMethod();
 Log.i(TAG, "调用后:name=" + name);
 }
 
 | 
C++层:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | extern "C"JNIEXPORT void JNICALL
 Java_com_github_xch168_ndkdemo_MainActivity_invokePublicMethod(JNIEnv *env, jobject instance) {
 
 jclass jclazz = env->GetObjectClass(instance);
 
 jmethodID mid = env->GetMethodID(jclazz, "setName", "(Ljava/lang/String;)V");
 
 char c[10] = "Jerry";
 jstring jName = env->NewStringUTF(c);
 
 env->CallVoidMethod(instance, mid, jName);
 }
 
 | 
输出结果:
| 12
 
 | MainActivity: 调用前:name=TomMainActivity: 调用后:name=Jerry
 
 | 
调用Java private方法也一样,Java的访问修饰符对C++无效。
调用Java静态方法
Java层:native方法定义和调用
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | private static int height = 170;
 public static int getHeight() {
 return height;
 }
 
 public native int invokeStaticMethod();
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 Log.i(TAG, "调用静态方法:getHeight() = " + invokeStaticMethod());
 }
 
 | 
C++层:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | extern "C"JNIEXPORT jint JNICALL
 Java_com_github_xch168_ndkdemo_MainActivity_invokeStaticMethod(JNIEnv *env, jobject instance) {
 
 jclass jclazz = env->GetObjectClass(instance);
 
 jmethodID mid = env->GetStaticMethodID(jclazz, "getHeight", "()I");
 
 return env->CallStaticIntMethod(jclazz, mid);
 }
 
 | 
输出结果:
| 1
 | MainActivity: 调用静态方法:getHeight() = 170
 | 
调用Java父类方法
Java层:native方法定义和调用
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | public class BaseActivity extends AppCompatActivity {
 public String hello(String name) {
 return "Welcome to JNI world, " + name;
 }
 }
 
 public class MainActivity extends BaseActivity {
 
 public native String invokeSuperMethod();
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 Log.i(TAG, "调用父类方法:hello(name) = " + invokeSuperMethod());
 }
 }
 
 | 
C++层:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | extern "C"JNIEXPORT jstring JNICALL
 Java_com_github_xch168_ndkdemo_MainActivity_invokeSuperMethod(JNIEnv *env, jobject instance) {
 
 jclass jclazz = env->FindClass("com/github/xch168/ndkdemo/BaseActivity");
 if (jclazz == NULL) {
 char c[10] = "error";
 return env->NewStringUTF(c);
 }
 
 jmethodID mid = env->GetMethodID(jclazz, "hello", "(Ljava/lang/String;)Ljava/lang/String;");
 char ch[10] = "Tom";
 jstring jstr = env->NewStringUTF(ch);
 
 return (jstring) env->CallNonvirtualObjectMethod(instance, jclazz, mid, jstr);
 }
 
 | 
输出结果:
| 1
 | MainActivity: 调用父类方法:hello(name) = Welcome to JNI world, Tom
 | 
两个不同点:
- 获取的是父类的方法,所有不能通过GetObjectClass获取,需要通过反射FindClass获取;
- 调用父类的方法是CallNonvirtual{type}Method函数。Novirtual是非虚函数。
Java方法传递参数给JNI函数
native方法既可以传递基本类型参数给JNI(可以不经过转换直接使用),也可以传递复杂类型(需要转换为C/C++的数据结构才能使用)如数组,String或自定义的类等。
用到的JNI函数:
- 获取数组长度:GetArrayLength(j{type}Array),type为基础类型;
- 数组转换为对应类型的指针:Get{type}ArrayElements(jarr, 0)
- 获取构造函数的jmethodID时,仍然是用env->GetMethodID(jclass, methodName, sign)获取,方法名是<init>;
- 通过构造函数new一个jobject,env->NewObject(jclass, constructorMethodId, param...),无参构造函数param为空。
数组参数的传递
Java层:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | public native int intArrayMethod(int[] arr);
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 Log.i(TAG, "intArrayMethod: " + intArrayMethod(new int[] {4, 3, 9, 9}));
 }
 
 | 
C++层:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | extern "C"JNIEXPORT jint JNICALL
 Java_com_github_xch168_ndkdemo_MainActivity_intArrayMethod(JNIEnv *env, jobject instance, jintArray arr_) {
 jint *arr = env->GetIntArrayElements(arr_, NULL);
 
 int sum = 0;
 int len = env->GetArrayLength(arr_);
 for (int i = 0; i < len; ++i) {
 sum += arr[i];
 }
 
 env->ReleaseIntArrayElements(arr_, arr, 0);
 return sum;
 }
 
 | 
输出结果:
| 1
 | MainActivity: intArrayMethod: 25
 | 
自定义对象参数的传递
Java层:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 
 | public class Person {private String name;
 private int age;
 
 public Person() {
 }
 
 public Person(int age, String name) {
 this.name = name;
 this.age = age;
 }
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
 public int getAge() {
 return age;
 }
 
 public void setAge(int age) {
 this.age = age;
 }
 
 @Override
 public String toString() {
 return "Person: {name:" + name + ", age:" + age + "}";
 }
 }
 
 
 public native Person objectMethod(Person person);
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 
 Log.i(TAG, "objectMethod: " + objectMethod(new Person()).toString());
 }
 
 | 
C++层:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | extern "C"JNIEXPORT jobject JNICALL
 Java_com_github_xch168_ndkdemo_MainActivity_objectMethod(JNIEnv *env, jobject instance, jobject person) {
 
 jclass clazz = env->GetObjectClass(person);
 if (clazz == NULL) {
 return env->NewStringUTF("cannot find class");
 }
 jmethodID constructorMid = env->GetMethodID(clazz, "<init>", "(ILjava/lang/String;)V");
 if (constructorMid == NULL) {
 return env->NewStringUTF("cannot find constructor method");
 }
 jstring name = env->NewStringUTF("Tom");
 return env->NewObject(clazz, constructorMid, 25, name);
 }
 
 | 
输出结果:
| 1
 | MainActivity: objectMethod: Person: {name:Tom, age:25}
 | 
参考链接
- Java JNI介绍
- Android NDK开发:JNI基础篇
- Android NDK开发:JNI实战篇