概述
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类型的域描述符为:
数组的域描述符比较特殊,规则:其中有多少级数组就有多少个“[”,数组的类型为类时,则有分号,为基本类型时没有分号
例如:
1 2 3 4 5
| int[] 描述符为 [I long[] 描述符为 [J String[] 描述符为 [Ljava/lang/String; int[][] 描述符为 [[I double[][] 描述符为 [[D
|
类描述符
类描述符是类的完整名称:包名+类名,Java中包名用.分隔,JNI中改成/分隔
如,Java中java.lang.String类的描述符为java/lang/String
方法描述符(方法签名)
方法描述符需要将所有类型的域描述符按照声明顺序放入括号,然后加上返回值类型的域描述符,规则如下:
例如:
1 2 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
1 2 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方法定义和调用
1 2 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++层:
1 2 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; }
|
输出结果:
1 2
| MainActivity: 调用前:num=1 MainActivity: 调用后:2
|
访问static变量
Java层:native方法定义和调用
1 2 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++层:
1 2 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); }
|
输出结果:
1 2
| MainActivity: 调用前:name=Tom MainActivity: 调用后:hello, Tom
|
注意:获取Java静态变量,都是调用JNI相应静态函数,不能调用非静态的,同时留意传入的参数是jclass
,而不是jobject。
访问private变量,并对其修改
Java层:native方法定义和调用
1 2 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++层:
1 2 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); }
|
输出结果:
1 2
| MainActivity: 调用前:age=25 MainActivity: 调用后: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方法定义和调用
1 2 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++层:
1 2 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); }
|
输出结果:
1 2
| MainActivity: 调用前:name=Tom MainActivity: 调用后:name=Jerry
|
调用Java private方法也一样,Java的访问修饰符对C++无效。
调用Java静态方法
Java层:native方法定义和调用
1 2 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++层:
1 2 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方法定义和调用
1 2 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++层:
1 2 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层:
1 2 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++层:
1 2 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层:
1 2 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++层:
1 2 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实战篇