Android JNI

本文是在针对 Android 平台中使用 JNI 的分析。

基础

JNI:Java Native InterfaceJava 本地开发接口,JNI 是一个协议,这个协议用来沟通 Java 代码和外部的本地代码 C/C++ ,确保 JavaC/C++ 代码之间能相互调用。

类型与数据结构

基本数据类型

Java 基本数据类型在 JNI 中的对应关系:

Java 类型 Native 类型 描述
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void not applicable

jni.h 中基本类型对应源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// jni.h
/* Primitive types that match up with Java equivalents. */
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */

/* "cardinal indices and sizes" */
typedef jint jsize;

#define JNI_FALSE 0
#define JNI_TRUE 1

引用类型

Java 中的引用类型 Object 对应 JNI 中的 jobject ,代表一切引用类型。在 C++ 中通过类继承的方式体现层级,在 C 中都是无类型指针 void* ;先看对应关系:

Java 类型 Native 类型 描述
Object jobject 所有的 Java 对象
Class jclass Class 对象
Throwable jthrowable 可抛出类型对象
String jstring 字符串
Object[] jarray 数组
boolean[] jbooleanArray 布尔数组
byte[] jbyteArray 字节数组
char[] jcharArray 字符数组
short[] jshortArray 短整型数组
int[] jintArray 整形数组
long[] jlongArray 长整型数组
float[] jfloatArray 浮点数组
double[] jdoubleArray 双浮点数组

jni.h 中引用类型对应的源码:

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#ifdef __cplusplus
/*
* Reference types, in C++
*/
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};

typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;


#else /* not __cplusplus */

/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;

#endif /* not __cplusplus */

属性和方法

Java 中的属性和方法,在反射时会用到,在 JNI 中由对应类型:

1
2
3
4
5
struct _jfieldID;                       /* opaque structure */
typedef struct _jfieldID* jfieldID; /* field IDs */

struct _jmethodID; /* opaque structure */
typedef struct _jmethodID* jmethodID; /* method IDs */

这里并没有给出 _jfieldID, _jmethodID 两个结构体的详细定义,但是可以用 jfieldID, jmethodID 两个结构体指针来表示 Java 类的属性和方法。

可变参数列表

Java 方法中的可变参数列表比如 void set(String... params) ,不定长度的参数可以看做一个数组;在 JNI 中使用 jvalue 来表示,它是一个联合体,通常在 C/C++ 调用 Java 时会用到:

1
2
3
4
5
6
7
8
9
10
11
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;

类型签名 Type Signatures

JNI 中使用 Java VM 虚拟机的类型签名格式来表示:

类型签名 Java 类型
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L fully-qualified-class ; fully-qualified-class
[ type type[]
( arg-types ) ret-type method type

这个表格包含如下几个部分:

  • 基本类型
    基本类型都使用单个字符来表示签名,如 int a ,签名为 I
  • 类全限定名
    类都使用全限定名,并L 开头,; 分号结尾,比如 String 的全限定名为: Ljava/lang/String;
  • 数组
    数组必须以 [ 开头,比如 int[] 表示为:[I
  • 方法描述符
    先写参数再写返回值,比如方法 long f (int n, String s, int[] arr); 表示为 (ILjava/lang/String;[I)J

字符串编码

Unicode 编码

字符串常见的编码有 ASCIIUnicode ;其中 ASCII 只能表示 128 种符号,对于英文完全够用了,但是对于中文、俄文、阿拉伯文等远远不够,于是新定义了通用型 Unicode 方案,它使用 21 位来编码,表示范围为 U+0000 ~ U+10FFFF 接近几百万个字符,其中 U+D800 ~ U+DFFF 之间为保留码位。
ASCII 只需要 8 位就能全部表示完,但是 Unicode 编码方案,并没有规定如何存储,因此实现 Unicode 的存储出现了不同的 UTF: Unicode Transformation Format 编码方案:UTF-8, UTF-16, UTF-32 。其中 UTF-8, UTF-16 是变长编码,而 UTF-3232 bit 定长编码。

UTF-8

UTF-8: 8-bit Unicode Transformation Format 变长编码,每个 Unicode 字符被编码为 1-4 个字节,对应关系如下:

Unicode 编码 UTF-8 编码
U+0000 ~ U+007F 0xxxxxxx
U+0080 ~ U+07FF 110xxxxx 10xxxxxx
U+0800 ~ U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
U+10000 ~ U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

也就是最高字节有几个 1 即表示当前 Unicode 字符占几个字节;所以英文字符只需要 1 个字节,绝大部分中文需要 3 个字节,少数可能会使用 4 个字节。

Unicode 码 对应字符 UTF-8 编码
U+0041 A 0x41
U+7834 0xE7 0xA0 0xB4
U+6653 0xE6 0x99 0x93
U+2A6A5 𪚥(四个龍) 0xF0 0xAA 0x9A 0xA5

从实际的结果上看,UTF-8 编码只有在单字节内的 Unicode 码和对应的 UTF-8 编码相同;多字节的 UTF-8 编码已经看不出对应的 Unicode 码了。

UTF-16

UTF-16: 16-bit Unicode Transformation Format 变长编码,每个 Unicode 字符被编码为 2 个或者 4 个字节;即码位范围 U+0000 ~ U+FFFF 使用 2 字节,而 U+10000 ~U+10FFFF 使用 4 字节。

码位范围 U+0000 ~ U+FFFF ,以中文为例,相当于能表示 65536 个常用汉字,因为使用的是 2 字节,所以这个范围内的 UTF-16 编码和 Unicode 码完全一样:

Unicode 字符 UTF-16 码元 UTF-16 LE 小端 UTF-16 BE 大端
U+0041 A 0x0041 0x41 0x00 0x00 0x41
U+7834 0x7834 0x34 0x78 0x78 0x34
U+6653 0x6653 0x53 0x66 0x66 0x53

码位范围 U+10000 ~U+10FFFF ,并不是直接使用 4 字节存储的,而是使用的代理码,即先将码表减去 0x10000 再针对高低 10 位分别与前导码做或运算后再存储的,这里不做详细分析。

修改后的 UTF-8 编码

JVM 中字符串使用的是修改后的 UTF-8 编码方案,即 Modified UTF-8 ,它实际是 UTF-8UTF-16 的一个混合体:

Unicode 编码 Modified UTF-8 编码
U+0000 ~ U+007F 0xxxxxxx
U+0080 ~ U+07FF 110xxxxx 10xxxxxx
U+0800 ~ U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
U+10000 ~ U+10FFFF 11101101 1010vvvv 10wwwwww 11101101 1011yyyy 10zzzzzz

修改后的 UTF-8 只使用 1 字节、 2 字节、 3 字节和 6 字节四种方式来存储,其中码位范围 U+10000 ~U+10FFFF 的第 1 和 4 字节都是固定的 11101101 ,另外 4 个字节使用的是 UTF-16 的代理码,高 10 位放入第 2 和第 3 字节,低 10 位放入第 5 和第 6 字节。
JNI 中的字符串也采用的是这种修改后的 UTF-8 编码。

JNI 接口指针

基本概念

JNI 的设计架构如下图所示:

0112-android-jni-designa.png

JNI 接口指针指向函数表,包含当前线程的所有信息,而这个接口指针在 JNI 中,使用 JNIEnv 来表示;每个线程对应一个 JNIEnv 结构,它们保存在线程本地存储 TLS 中;因此不同的线程的 JNIEnv 是不同,也不能相互共享使用。
JavaVM 是虚拟机在 JNI 层的代表,一个进程只有一个 JavaVM,所有的线程共用一个 JavaVM ,所以一个 JavaVM 中可以有多个 JNIEnv

1
2
3
4
5
6
7
8
9
10
11
12
13
// jni.h
struct JNIInvokeInterface;
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

JavaVM 源码

JavaVM 代表的是虚拟机相关信息,源码中 JavaVM 实际就是 JNIInvokeInterface 指针,在 C++ 中,_JavaVMJNIInvokeInterface 的包装类。

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
// jni.h
/*
* JNI invocation interface.
*/
struct JNIInvokeInterface {
void* reserved0;
void* reserved1;
void* reserved2;

jint (*DestroyJavaVM)(JavaVM*);
jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
jint (*DetachCurrentThread)(JavaVM*);
jint (*GetEnv)(JavaVM*, void**, jint);
jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};

/*
* C++ version.
*/
struct _JavaVM {
const struct JNIInvokeInterface* functions;

#if defined(__cplusplus)
jint DestroyJavaVM()
{ return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{ return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};

JNIEnv 源码

JNIEnv 代表了当前线程的函数表,几乎包含了所有的 JNI 函数,它是线程隔离 TLS 的,所以不能在线程间共享。JNIEnv 主要用于操作 Java 对象的类、方法、属性等,可以理解为反射的 C/C++ 实现。
JNIEnv 实际是 JNINativeInterface 结构指针,在 C++ 中,_JNIEnvJNINativeInterface 的包装类。所以它们的调用有两种风格:

  • C 风格:(*env)->NewStringUTF(env, “Hellow World!”);
  • C++ 风格: env->NewStringUTF(“Hellow World!”);
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
struct JNINativeInterface {
void* reserved0;
void* reserved1;
void* reserved2;
void* reserved3;

jint (*GetVersion)(JNIEnv *);

// 类反射
jclass (*DefineClass)(JNIEnv*, const char*, jobject,
const jbyte*, jsize);
jclass (*FindClass)(JNIEnv*, const char*);

jmethodID (*FromReflectedMethod)(JNIEnv*, jobject);
jfieldID (*FromReflectedField)(JNIEnv*, jobject);
/* spec doesn't show jboolean parameter */
jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean);

jclass (*GetSuperclass)(JNIEnv*, jclass);
jboolean (*IsAssignableFrom)(JNIEnv*, jclass, jclass);

/* spec doesn't show jboolean parameter */
jobject (*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean);

jint (*Throw)(JNIEnv*, jthrowable);
jint (*ThrowNew)(JNIEnv *, jclass, const char *);
jthrowable (*ExceptionOccurred)(JNIEnv*);
void (*ExceptionDescribe)(JNIEnv*);
void (*ExceptionClear)(JNIEnv*);
void (*FatalError)(JNIEnv*, const char*);

jint (*PushLocalFrame)(JNIEnv*, jint);
jobject (*PopLocalFrame)(JNIEnv*, jobject);

jobject (*NewGlobalRef)(JNIEnv*, jobject);
void (*DeleteGlobalRef)(JNIEnv*, jobject);
void (*DeleteLocalRef)(JNIEnv*, jobject);
jboolean (*IsSameObject)(JNIEnv*, jobject, jobject);

jobject (*NewLocalRef)(JNIEnv*, jobject);
jint (*EnsureLocalCapacity)(JNIEnv*, jint);

jobject (*AllocObject)(JNIEnv*, jclass);
jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);
jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list);
jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, const jvalue*);

jclass (*GetObjectClass)(JNIEnv*, jobject);
jboolean (*IsInstanceOf)(JNIEnv*, jobject, jclass);
// 获取方法的 ID
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);

jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...);
jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...);
jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list);
jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);
void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list);
void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*);

jobject (*CallNonvirtualObjectMethod)(JNIEnv*, jobject, jclass,
jmethodID, ...);
jobject (*CallNonvirtualObjectMethodV)(JNIEnv*, jobject, jclass,
jmethodID, va_list);
jobject (*CallNonvirtualObjectMethodA)(JNIEnv*, jobject, jclass,
jmethodID, const jvalue*);
jboolean (*CallNonvirtualBooleanMethod)(JNIEnv*, jobject, jclass,
jmethodID, ...);
jboolean (*CallNonvirtualBooleanMethodV)(JNIEnv*, jobject, jclass,
jmethodID, va_list);
jboolean (*CallNonvirtualBooleanMethodA)(JNIEnv*, jobject, jclass,
jmethodID, const jvalue*);
jbyte (*CallNonvirtualByteMethod)(JNIEnv*, jobject, jclass,
jmethodID, ...);
jbyte (*CallNonvirtualByteMethodV)(JNIEnv*, jobject, jclass,
jmethodID, va_list);
jbyte (*CallNonvirtualByteMethodA)(JNIEnv*, jobject, jclass,
jmethodID, const jvalue*);
jchar (*CallNonvirtualCharMethod)(JNIEnv*, jobject, jclass,
jmethodID, ...);
jchar (*CallNonvirtualCharMethodV)(JNIEnv*, jobject, jclass,
jmethodID, va_list);
jchar (*CallNonvirtualCharMethodA)(JNIEnv*, jobject, jclass,
jmethodID, const jvalue*);
jshort (*CallNonvirtualShortMethod)(JNIEnv*, jobject, jclass,
jmethodID, ...);
jshort (*CallNonvirtualShortMethodV)(JNIEnv*, jobject, jclass,
jmethodID, va_list);
jshort (*CallNonvirtualShortMethodA)(JNIEnv*, jobject, jclass,
jmethodID, const jvalue*);
jint (*CallNonvirtualIntMethod)(JNIEnv*, jobject, jclass,
jmethodID, ...);
jint (*CallNonvirtualIntMethodV)(JNIEnv*, jobject, jclass,
jmethodID, va_list);
jint (*CallNonvirtualIntMethodA)(JNIEnv*, jobject, jclass,
jmethodID, const jvalue*);
jlong (*CallNonvirtualLongMethod)(JNIEnv*, jobject, jclass,
jmethodID, ...);
jlong (*CallNonvirtualLongMethodV)(JNIEnv*, jobject, jclass,
jmethodID, va_list);
jlong (*CallNonvirtualLongMethodA)(JNIEnv*, jobject, jclass,
jmethodID, const jvalue*);
jfloat (*CallNonvirtualFloatMethod)(JNIEnv*, jobject, jclass,
jmethodID, ...);
jfloat (*CallNonvirtualFloatMethodV)(JNIEnv*, jobject, jclass,
jmethodID, va_list);
jfloat (*CallNonvirtualFloatMethodA)(JNIEnv*, jobject, jclass,
jmethodID, const jvalue*);
jdouble (*CallNonvirtualDoubleMethod)(JNIEnv*, jobject, jclass,
jmethodID, ...);
jdouble (*CallNonvirtualDoubleMethodV)(JNIEnv*, jobject, jclass,
jmethodID, va_list);
jdouble (*CallNonvirtualDoubleMethodA)(JNIEnv*, jobject, jclass,
jmethodID, const jvalue*);
void (*CallNonvirtualVoidMethod)(JNIEnv*, jobject, jclass,
jmethodID, ...);
void (*CallNonvirtualVoidMethodV)(JNIEnv*, jobject, jclass,
jmethodID, va_list);
void (*CallNonvirtualVoidMethodA)(JNIEnv*, jobject, jclass,
jmethodID, const jvalue*);

// 获取属性字段的 ID
jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);

jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);
jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID);
jbyte (*GetByteField)(JNIEnv*, jobject, jfieldID);
jchar (*GetCharField)(JNIEnv*, jobject, jfieldID);
jshort (*GetShortField)(JNIEnv*, jobject, jfieldID);
jint (*GetIntField)(JNIEnv*, jobject, jfieldID);
jlong (*GetLongField)(JNIEnv*, jobject, jfieldID);
jfloat (*GetFloatField)(JNIEnv*, jobject, jfieldID);
jdouble (*GetDoubleField)(JNIEnv*, jobject, jfieldID);

void (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject);
void (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean);
void (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte);
void (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar);
void (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort);
void (*SetIntField)(JNIEnv*, jobject, jfieldID, jint);
void (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong);
void (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat);
void (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble);

jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);

jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...);
jobject (*CallStaticObjectMethodV)(JNIEnv*, jclass, jmethodID, va_list);
jobject (*CallStaticObjectMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
jboolean (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...);
jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,
va_list);
jboolean (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);
jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);
jbyte (*CallStaticByteMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
jchar (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...);
jchar (*CallStaticCharMethodV)(JNIEnv*, jclass, jmethodID, va_list);
jchar (*CallStaticCharMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
jshort (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...);
jshort (*CallStaticShortMethodV)(JNIEnv*, jclass, jmethodID, va_list);
jshort (*CallStaticShortMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
jint (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...);
jint (*CallStaticIntMethodV)(JNIEnv*, jclass, jmethodID, va_list);
jint (*CallStaticIntMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
jlong (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...);
jlong (*CallStaticLongMethodV)(JNIEnv*, jclass, jmethodID, va_list);
jlong (*CallStaticLongMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
jfloat (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...);
jfloat (*CallStaticFloatMethodV)(JNIEnv*, jclass, jmethodID, va_list);
jfloat (*CallStaticFloatMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
jdouble (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...);
jdouble (*CallStaticDoubleMethodV)(JNIEnv*, jclass, jmethodID, va_list);
jdouble (*CallStaticDoubleMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);
void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list);
void (*CallStaticVoidMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*);

jfieldID (*GetStaticFieldID)(JNIEnv*, jclass, const char*,
const char*);

jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);
jboolean (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID);
jbyte (*GetStaticByteField)(JNIEnv*, jclass, jfieldID);
jchar (*GetStaticCharField)(JNIEnv*, jclass, jfieldID);
jshort (*GetStaticShortField)(JNIEnv*, jclass, jfieldID);
jint (*GetStaticIntField)(JNIEnv*, jclass, jfieldID);
jlong (*GetStaticLongField)(JNIEnv*, jclass, jfieldID);
jfloat (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID);
jdouble (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID);

void (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject);
void (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean);
void (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte);
void (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar);
void (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort);
void (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint);
void (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong);
void (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat);
void (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble);

jstring (*NewString)(JNIEnv*, const jchar*, jsize);
jsize (*GetStringLength)(JNIEnv*, jstring);
const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
jstring (*NewStringUTF)(JNIEnv*, const char*);
jsize (*GetStringUTFLength)(JNIEnv*, jstring);
/* JNI spec says this returns const jbyte*, but that's inconsistent */
const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*);
jsize (*GetArrayLength)(JNIEnv*, jarray);
jobjectArray (*NewObjectArray)(JNIEnv*, jsize, jclass, jobject);
jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, jsize);
void (*SetObjectArrayElement)(JNIEnv*, jobjectArray, jsize, jobject);

// 新建基本类型数组
jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);
jbyteArray (*NewByteArray)(JNIEnv*, jsize);
jcharArray (*NewCharArray)(JNIEnv*, jsize);
jshortArray (*NewShortArray)(JNIEnv*, jsize);
jintArray (*NewIntArray)(JNIEnv*, jsize);
jlongArray (*NewLongArray)(JNIEnv*, jsize);
jfloatArray (*NewFloatArray)(JNIEnv*, jsize);
jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize);

// 获取基本类型数组中的元素,返回对应指针类型(表示一个数组)
jboolean* (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);
jbyte* (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);
jchar* (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);
jshort* (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);
jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
jlong* (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);
jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);
jdouble* (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);

void (*ReleaseBooleanArrayElements)(JNIEnv*, jbooleanArray,
jboolean*, jint);
void (*ReleaseByteArrayElements)(JNIEnv*, jbyteArray,
jbyte*, jint);
void (*ReleaseCharArrayElements)(JNIEnv*, jcharArray,
jchar*, jint);
void (*ReleaseShortArrayElements)(JNIEnv*, jshortArray,
jshort*, jint);
void (*ReleaseIntArrayElements)(JNIEnv*, jintArray,
jint*, jint);
void (*ReleaseLongArrayElements)(JNIEnv*, jlongArray,
jlong*, jint);
void (*ReleaseFloatArrayElements)(JNIEnv*, jfloatArray,
jfloat*, jint);
void (*ReleaseDoubleArrayElements)(JNIEnv*, jdoubleArray,
jdouble*, jint);

void (*GetBooleanArrayRegion)(JNIEnv*, jbooleanArray,
jsize, jsize, jboolean*);
void (*GetByteArrayRegion)(JNIEnv*, jbyteArray,
jsize, jsize, jbyte*);
void (*GetCharArrayRegion)(JNIEnv*, jcharArray,
jsize, jsize, jchar*);
void (*GetShortArrayRegion)(JNIEnv*, jshortArray,
jsize, jsize, jshort*);
void (*GetIntArrayRegion)(JNIEnv*, jintArray,
jsize, jsize, jint*);
void (*GetLongArrayRegion)(JNIEnv*, jlongArray,
jsize, jsize, jlong*);
void (*GetFloatArrayRegion)(JNIEnv*, jfloatArray,
jsize, jsize, jfloat*);
void (*GetDoubleArrayRegion)(JNIEnv*, jdoubleArray,
jsize, jsize, jdouble*);

/* spec shows these without const; some jni.h do, some don't */
void (*SetBooleanArrayRegion)(JNIEnv*, jbooleanArray,
jsize, jsize, const jboolean*);
void (*SetByteArrayRegion)(JNIEnv*, jbyteArray,
jsize, jsize, const jbyte*);
void (*SetCharArrayRegion)(JNIEnv*, jcharArray,
jsize, jsize, const jchar*);
void (*SetShortArrayRegion)(JNIEnv*, jshortArray,
jsize, jsize, const jshort*);
void (*SetIntArrayRegion)(JNIEnv*, jintArray,
jsize, jsize, const jint*);
void (*SetLongArrayRegion)(JNIEnv*, jlongArray,
jsize, jsize, const jlong*);
void (*SetFloatArrayRegion)(JNIEnv*, jfloatArray,
jsize, jsize, const jfloat*);
void (*SetDoubleArrayRegion)(JNIEnv*, jdoubleArray,
jsize, jsize, const jdouble*);

jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*,
jint);
jint (*UnregisterNatives)(JNIEnv*, jclass);
jint (*MonitorEnter)(JNIEnv*, jobject);
jint (*MonitorExit)(JNIEnv*, jobject);
jint (*GetJavaVM)(JNIEnv*, JavaVM**);

void (*GetStringRegion)(JNIEnv*, jstring, jsize, jsize, jchar*);
void (*GetStringUTFRegion)(JNIEnv*, jstring, jsize, jsize, char*);

void* (*GetPrimitiveArrayCritical)(JNIEnv*, jarray, jboolean*);
void (*ReleasePrimitiveArrayCritical)(JNIEnv*, jarray, void*, jint);

const jchar* (*GetStringCritical)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringCritical)(JNIEnv*, jstring, const jchar*);

jweak (*NewWeakGlobalRef)(JNIEnv*, jobject);
void (*DeleteWeakGlobalRef)(JNIEnv*, jweak);

jboolean (*ExceptionCheck)(JNIEnv*);

jobject (*NewDirectByteBuffer)(JNIEnv*, void*, jlong);
void* (*GetDirectBufferAddress)(JNIEnv*, jobject);
jlong (*GetDirectBufferCapacity)(JNIEnv*, jobject);

/* added in JNI 1.6 */
jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
};

/*
* C++ object wrapper.
*
* This is usually overlaid on a C struct whose first element is a
* JNINativeInterface*. We rely somewhat on compiler behavior.
*/
struct _JNIEnv {
/* do not rename this; it does not seem to be entirely opaque */
const struct JNINativeInterface* functions;

#if defined(__cplusplus)

jint GetVersion()
{ return functions->GetVersion(this); }
...
#endif /*__cplusplus*/
};

JNI 引用

JNI 支持将类实例和数组类型(如 jobject, jclass, jstring, jarray )作为不透明的引用,通过使用 JNI 方法来得到指向该数据结构的指针的引用,也就是说 JNI 操作的内容都是引用。JNI 规范中定义了三种引用:

  • Local Reference 局部引用
  • Global Reference 全局引用
  • Weak Global Reference 弱全局引用

这三种引用的作用域和生命周期都不一样:

  • 局部引用在方法返回时会被自动释放,当局部引用在使用过程中,GC 不会回收该对象
  • 全局引用和弱全局引用,一直保存直到手动释放
  • 全局引用在释放前不会被 GC 回收;弱全局引用 GC 时会被回收

局部引用

  • 通过 NewLocalRef ;或者大部分创建接口 FindClass, NewObject, GetObjectClass, NewCharArray 等都会生成局部引用
  • 虽然局部引用在方法返回时会自动释放,但是通常如果后续并不使用该引用了,应该主动释放 DeleteLocalRef
  • 局部引用是线程相关的,不在跨函数使用,不能跨线前使用

全局引用

  • 全局引用必须主动创建 NewGlobalRef ;通过 DeleteGlobalRef 主动释放
  • 全局引用可以跨方法使用,可以在多线程中使用
  • 全局引用在使用中,引用的对象不会被 GC 回收,需要特别注意

弱全局引用

  • 弱全局引用必须主动创建 NewWeakGlobalRef ;通过 DeleteGlobalWeakRef 主动释放
  • 弱全局引用可以跨方法使用,可以在多线程中使用
  • 弱全局引用在使用中,引用的对象可能会被 GC 回收,使用时需要做空判断

引用比较

使用 IsSameObject 比较两个引用是否指向同一个对象:jboolean (IsSameObject)(JNIEnv, jobject, jobject); ;返回 JNI_TRUE 表示指向的是同一个。

API 基本功能

JNIEnv 主要是操作 Java 中的代码,类似反射,所以主要根据 Java 特性来介绍例子。先说明 JNI 中几个重要的类型:

  • jclass :表示 Java 中的类
  • jfieldID :表示 Java 中的属性字段 ID
  • jmethodID :表示 Java 中的方法 ID
  • jobject :表示 Java 中的一切对象

动态注册方法

Javanative 的方法可以通过静态注册和动态注册的方式和 C/C++ 中的代码绑定:

  • 静态注册
    必须满足命名格式:Java_packageName_className_nativeMethodName ,都是以下划线 _ 拼接的。如果满足这个格式,JavaC/C++ 会自动关联。
  • 动态注册
    不需要满足命名格式,在 so 文件加载时,通过代码动态注册来关联。

动态注册的入口函数为 JNI_OnLoad ,并通过 JNINativeMethod 结构体提供映射表,示例代码 Java 中的 dynamicRegistration 动态注册匹配 nativeDynamicRegistration 方法:

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
45
46
void nativeDynamicRegistration(){
LOGE("nativeDynamicRegistration");
}

static const JNINativeMethod gRegisterMethods[] = {
/* name, signature, funcPtr */
{"dynamicRegistration","()V",(void*)nativeDynamicRegistration},
};

jint registerNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods){
LOGE("registerNativeMethods.");

jclass clz = env->FindClass(className);
if (clz == NULL){
LOGE("can't find class = %s", className);
return JNI_ERR;
}

// 动态注册
if (env->RegisterNatives(clz, gMethods, numMethods) < 0){
LOGE("register natives error!");
return JNI_ERR;
}

env->DeleteLocalRef(clz);
return JNI_OK;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOGE("JNI_OnLoad...");

JNIEnv* env = NULL;

if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK){
LOGE("vm get env error.");
return JNI_ERR;
}

registerNativeMethods(env,
"com/***/jni/JniClient",
gRegisterMethods,
sizeof(gRegisterMethods)/sizeof(JNINativeMethod));

return JNI_VERSION_1_6;
}

这段代码中:

  • JNI_OnLoad 是入口函数,在 so 文件加载时调用,可以在该函数中使用全局变量保存 vm 变量的值
  • JNINativeMethod 定义映射数组时,每个映射方法必须按照:名称、方法签名、函数指针;格式来匹配
  • env->RegisterNatives 注册方法映射数组

字符串操作

从字符串编码那一节我们可以看到,Java, JNI, C/C++ 对字符串编码都不一样,所以 JNI 中的字符串操作需要先进行转码;可以简单的理解为:

  • jstringJavaJNI 中可以相互传递和使用
  • char *C/C++JNI 中可以相互传递和使用
  • jchar*JNI 中特有的(本文暂时没找到怎么用)

因此我们重点需要关注的就是 jstring, char* 之间的转换了;相关 API

1
2
3
4
5
6
7
8
9
10
11
12
// jchar* 和 jstring 相互转换
jstring (*NewString)(JNIEnv*, const jchar*, jsize);
jsize (*GetStringLength)(JNIEnv*, jstring);
const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);

// jstring 和 char* 相互转换
jstring (*NewStringUTF)(JNIEnv*, const char*);
jsize (*GetStringUTFLength)(JNIEnv*, jstring);
/* JNI spec says this returns const jbyte*, but that's inconsistent */
const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*);

示例代码,Java 传入的字符包含中英文,从 JNI 中返回的字符串也包含中英文:

1
2
3
String input = "abc你好";
String resultStr = sampleString(input);
Log.d(TAG, "showJNI: resultStr = " + resultStr);

JNI 对字符串的处理:

1
2
3
4
5
6
7
8
9
10
11
JNIEXPORT jstring JNICALL
Java_com_***_jni_JniClient_sampleString
(JNIEnv *env, jobject instance, jstring input){
const char *inputStr = env->GetStringUTFChars(input, NULL);
LOGE("sampleString, inputStr = %s", inputStr);
env->ReleaseStringUTFChars(input, inputStr);

const char *resultStr = "从JNI中返回的结果!";
jstring result = env->NewStringUTF(resultStr);
return result;
}

输出结果:

1
2
11:07:52.786 E/JniClient-jni: sampleString, inputStr = abc你好
11:07:52.787 D/JniSampleActivity:: showJNI: resultStr = 从JNI中返回的结果!

数组

JNI 中数组操作重要 API

  • GetIntArrayElements... 获取数组的元素,返回一个指针类型,表示指向一个数组
  • GetArrayLength 获取数组长度
  • 使用完毕后,需要使用 ReleaseIntArrayElements 释放内存
  • NewCharArray 创建数组
  • SetCharArrayRegion 拷贝内容到数组

先看 Java 端调用的代码:

1
2
3
4
5
6
7
8
public native char[] samplesArray(int[] array);

// 调用 native 方法,传入 int 数组
int[] array = {1, 2, 3, 4, 5};
char[] results = samplesArray(array);
for (int i = 0; i < results.length; ++i){
Log.d(TAG, "showJNI: c[" + i + "] = " + results[i]);
}

JNI 中的实现,先获取数组指针,再获取数组长度,遍历打印数组,最后释放内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
JNIEXPORT jcharArray JNICALL
Java_com_***_jni_JniClient_samplesArray(
JNIEnv *env, jobject instance, jintArray intArray) {
jint *array = env->GetIntArrayElements(intArray, NULL);
int length = env->GetArrayLength(intArray);
for (int i = 0; i < length; ++i) {
LOGE("samplesArray[%d]=%d", i, array[i]);
}
env->ReleaseIntArrayElements(intArray,array, NULL);

jchar result[] = {'a', 'b', 'c', 'd', 'e'};
jcharArray charArray = env->NewCharArray(length);
env->SetCharArrayRegion(charArray, 0, length, result);
return charArray;
}

创建 Java 对象

JNI 中创建 Java 对象步骤:

  • 根据 package/Classname 类名来查找对应类
  • 查找构造方法 ID ,构造方法默认名称为 <init>
  • 根据构造方法 ID 和类来创建对象
  • 对象使用完毕后,需要删除释放

查找类时,传递的参数不是类的全限定名,只需要包名和类名即可!

1
2
3
4
5
6
7
8
// Constructor
jclass clz = env->FindClass("com/***/jni/JniObject");
jmethodID defaultConstructorID=env->GetMethodID(clz, "<init>", "()V");
jobject jniObjectInstance = env->NewObject(clz, defaultConstructorID);

// release
env->DeleteLocalRef(jniObjectInstance);
env->DeleteLocalRef(clz);

获取 Java 对象的属性和方法

和反射一样,public, private 等访问控制符并不生效,JNI 中可以获取 Java 对象所有的属性和方法,包含 private 修饰的。重要 API

  • GetFieldID :获取属性 ID ;参数需要使用类型签名
  • GetMethodID :获取方法 ID ;参数需要使用方法的类型签名
  • GetObjectField/GetIntField... :获取属性对应的值,有多个基本类型变种
  • CallVoidMethod/CallBooleanMethod... :调用某个方法,根据返回值有多个基本类型变种
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
// 获取和调用 public 的属性和方法
// public field and method
jfieldID publicIntID = env->GetFieldID(clz, "publicInt", "I");
jint publicIntValue = env->GetIntField(jniObjectInstance, publicIntID);
LOGE("default publicIntValue = %d", publicIntValue);
// change value
publicIntValue = 100;
env->SetIntField(jniObjectInstance, publicIntID, publicIntValue);
// check value
jint publicIntValue1 = env->GetIntField(jniObjectInstance, publicIntID);
LOGE("modify publicIntValue1 = %d", publicIntValue1);
// call public method: setPublicInt
jmethodID setPublicIntMethodId =
env->GetMethodID(clz, "setPublicInt", "(I)V");
publicIntValue = 200;
env->CallVoidMethod(jniObjectInstance,
setPublicIntMethodId, publicIntValue);
// check value
jint publicIntValue2 = env->GetIntField(jniObjectInstance, publicIntID);
LOGE("modify by Method, publicIntValue = %d", publicIntValue2);

// 获取和调用 private 的属性和方法
// private field and method
jfieldID privateBooleanID = env->GetFieldID(clz, "privateBoolean", "Z");
jboolean privateBooleanValue =
env->GetBooleanField(jniObjectInstance, privateBooleanID);
LOGE("default privateBooleanValue = %d", privateBooleanValue);
// call private method: setPrivateBoolean
jmethodID setPrivateBooleanId =
env->GetMethodID(clz, "setPrivateBoolean", "(Z)V");
privateBooleanValue = 1;
env->CallVoidMethod(jniObjectInstance,
setPrivateBooleanId, privateBooleanValue);
// check value
jboolean privateBooleanValue2 =
env->GetBooleanField(jniObjectInstance, privateBooleanID);
LOGE("modify by private Method, privateBooleanValue = %d",
privateBooleanValue2);

获取 Javastatic 静态属性和方法

static 修饰的属性和方法是属于类的,所以在调用时,并不需要指定对象 jobject ;重要 API

  • GetStaticFieldID :获取静态 static 属性 ID ;参数需要使用类型签名
  • GetStaticMethodID :获取静态 static 方法 ID ;参数需要使用方法的类型签名
  • GetStaticObjectField/GetStaticIntField... :获取静态属性的值,有多个基本类型的变种
  • CallStaticVoidMethod/CallStaticIntMethod... :调用静态方法,根据返回值有多个基本类型的变种
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// static field and method
jfieldID publicStaticStrID =
env->GetStaticFieldID(clz, "publicStaticStr", "Ljava/lang/String;");
jstring publicStaticStrValue =
(jstring)env->GetStaticObjectField(clz, publicStaticStrID);
const char *strValue = env->GetStringUTFChars(publicStaticStrValue,NULL);
LOGE("default publicStaticStrValue = %s", strValue);
env->ReleaseStringUTFChars(publicStaticStrValue, strValue);

// call static method: changeValue
jmethodID changeValueId = env->GetStaticMethodID(clz,
"changeValue", "(Ljava/lang/String;)V");
std::string newStr = "Hello from C++, changeValue";
jstring publicStaticStrValue2 = env->NewStringUTF(newStr.c_str());
env->CallStaticVoidMethod(clz, changeValueId, publicStaticStrValue2);
// check value
jstring publicStaticStrValue3 =
(jstring)env->GetStaticObjectField(clz, publicStaticStrID);
const char *newStrValue =
env->GetStringUTFChars(publicStaticStrValue3, NULL);
LOGE("modify by static Method, publicStaticStrValue = %s", newStrValue);
env->ReleaseStringUTFChars(publicStaticStrValue3, newStrValue);

注意事项:

  • 这里在获取字符串时,jobject 可以直接强制转换为 jstring
  • jstring 打印时,需要转换为 char* 指针

JNI 中对 Java 多态的处理

Java 多态中的重载,可以通过方法签名(参数不一样)来区分;重写,通常情况下并不需要通过子类调用父类的重写方法,但是 JNI 中提供了该功能;重要 API

  • CallNonvirtualVoidMethod/CallNonvirtualIntMethod... :在重写时,直接调用父类的方法

Java 示例代码,father.show 因为重写,输出结果为 Son, show:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class JniPolymorphic {
private static final String TAG = "JniPolymorphic";

private Father father = new Son();

public class Father{
public void show(){
Log.d(TAG, "Father, show: ");
}
}

public class Son extends Father{
@Override
public void show() {
Log.d(TAG, "Son, show: ");
}
}
}

JNI 中,可以通过 CallNonvirtualVoidMethod 来直接调用,输出 Father, show:

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
// 构造 JniPolymorphic 对象
jclass clz = env->FindClass("com/***/jni/JniPolymorphic");
jmethodID defaultConstructorID = env->GetMethodID(clz, "<init>", "()V");
jobject jniObjectInstance = env->NewObject(clz, defaultConstructorID);

// 获取 father 对象
jfieldID fatherID = env->GetFieldID(clz, "father",
"Lcom/***/jni/JniPolymorphic$Father;");
jobject fatherObject = env->GetObjectField(jniObjectInstance, fatherID);

// 获取 show 方法的 id
jclass fatherClass = env->FindClass("com/***/jni/JniPolymorphic$Father");
jmethodID fatherShowMethodID =
env->GetMethodID(fatherClass, "show", "()V");

// 调用 father 对象的 show 方法,因为重写,输出的结果为 Son, show
env->CallVoidMethod(fatherObject, fatherShowMethodID);

// 调用 father 对象的 show 方法,这里调用的是 Father 父类的 show 方法
// 所以输出结果为 Father, show
env->CallNonvirtualVoidMethod(fatherObject,
fatherClass, fatherShowMethodID);

// release
env->DeleteLocalRef(jniObjectInstance);
env->DeleteLocalRef(clz);
env->DeleteLocalRef(fatherObject);
env->DeleteLocalRef(fatherClass);

注意事项:

  • 内部类的类名拼接时,需要使用美元符 $

Android NDK

概念

  • Android NDK 是一套允许使用 C/C++ 等语言,以原生代码实现部分应用的工具集,Google NDK 官网
  • CMake 一款外部构建工具,可与 Gradle 搭配使用来构建原生库;Android Studio 中已经使用 Cmake 替换原来的 ndk-build
  • LLDB 一种调试程序,Android Studio 使用它来调试原生代码

CPU 架构及 ABI

不同的 CPU 架构:ARMv5, ARMv7, x86, MIPS, ARMv8, MIPS64, x86_64 都关联着一个对应的 ABIABI(Application Binary Interface) 决定了二进制文件如何与系统进行交互。CPU 对应的 ABIarmeabi, armeabi­v7a, arm64­v8a, x86, x86_64, mips, mips64Android NDK 支持的 ABI,查看官网

  • armeabi
    将创建以基于 ARM v5 的设备为目标的库。使用软件浮点运算,可以在所有 ARM 设备上运行;NDK 中已经弃用。
  • armeabi-v7a
    创建支持基于 ARM v7 的设备的库,并将使用硬件 FPU 指令,支持硬件浮点运算及高级扩展功能。与 ARMv5、v6 设备不兼容。
  • mips
    是世界上很流行的一种 RISC 处理器,其机制是尽量利用软件办法避免流水线中的数据相关问题;NDK 已弃用。
  • x86
    支持基于硬件的浮点运算的 IA-32 指令集。x86 是可以兼容 armeabi 平台运行的,无论是 armeabi-v7a 还是 armeabi,同时带来的也是性能上的损耗。

当前现状,几个大厂的 APK (微信,美团等) 都只使用了 armeabi-v7a,可能是 Android 手机碎片化严重,为了能支持更多的设备。Gradle 中如果不指定,默认会生成所有架构对应的 so ,导致 apk 很大。这里可以参考大厂,只指定 armeabi-v7a !在 defaultConfig 中增加如下代码:

1
2
3
ndk{
abiFilter 'armeabi-v7a'
}

Android Studio 设置快速生成 h 文件

这个方法是使用命令对 Java 中的 native 方法,手动生成对应头文件,非常方便。

AndroidStudio 中快速生成 h 文件,查看大图

0112-android-jni-AndroidStudio_ndk_javah.png

对应填写的参数如下:

1
2
3
$JDKPath$\bin\javah.exe
-classpath . -jni -encoding $FileEncoding$ -d $ModuleFileDir$\src\main\jni $FileClass$
$ModuleFileDir$\src\main\java

搭建 JNI 编译环境

Android Studio 中搭建好 JNI 编译环境后,所有的 native 代码会自动生成对应的 JNI 代码。

  • 新建 jni 目录 Folder -> JNI Folder ,并创建 JNI 对应的 cpp 文件
  • 新建 CMakeLists.txt 文件,可以拷贝一份模板并将 cpp 文件和生成库的名称替换下
  • build.gradle 中添加 CMakeLists.txt 路径,并重新 sync 工程确保能正确编译
  • Java 文件中添加 native 方法,测试是否能自动生成对应的 JNI
    如果是新建 Native 工程,则会自动添加;但是手动新建的,native 方法总是生成对应的 .c 文件,需要拷贝到 .cpp 中;如果使用了动态注册,则 Android Studio 中对 native 方法不再出现小红灯提示,这个时候只能手动生成头文件了。

0112-android-jni-AndroidStudio-red-tips.png

C/C++ 中的 LOG 打印

  • Cmake中 增加 log 模块

    1
    2
    target_link_libraries(hello-jni
    log)
  • C/C++ 包含头文件
    #include <android/log.h>

  • 自定义 LOG 输出

    1
    2
    #define LOGI(...) \
    ((void)__android_log_print(ANDROID_LOG_INFO, "MyTag::", __VA_ARGS__))
  • C/C++ 代码中使用 LOG 打印
    LOGI("sum %d", sum);

调试

JNI 代码出错后,会导致 crash ,根据 logcatbacktrace 输出的出错地址,来找到原始代码出错行。工具主要是 ndk 提供的 addr2linendk-stack

示例

出错的源码:

1
2
3
4
5
Java_com_***_jni_JniClient_sampleReflectJNI(
JNIEnv *env, jobject instance) {
jclass clz = env->FindClass("Lcom/***/jni/JniObject;");
...
}

出错原因是 env->FindClass 时,找不到指定类 Lcom/***/jni/JniObject;FindClass 方法参数中类名不需要添加 L; ,直接使用 package/Classname ),在 logcat 中输出了如下错误信息:

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
    --------- beginning of crash
2019-04-15 10:11:59.805 10175-10175/com.*** A/libc: Fatal signal 6 (SIGABRT), code -6 in tid 10175 (dbasicknowledge), pid 10175 (dbasicknowledge)
2019-04-15 10:11:59.805 10175-10175/com.*** A/zygote: runtime.cc:531]
2019-04-15 10:11:59.918 10224-10224/? I/crash_dump32: obtaining output fd from tombstoned, type: kDebuggerdTombstone
2019-04-15 10:11:59.919 796-796/? I//system/bin/tombstoned: received crash request for pid 10175
2019-04-15 10:11:59.920 10224-10224/? I/crash_dump32: performing dump of process 10175 (target tid = 10175)
2019-04-15 10:11:59.920 10224-10224/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2019-04-15 10:11:59.921 10224-10224/? A/DEBUG: Build fingerprint: 'Panasonic/ELUGA_Ray_710/ELUGA_Ray_710:8.1.0/OPM1.171019.011/013015:user/release-keys'
2019-04-15 10:11:59.921 10224-10224/? A/DEBUG: Revision: '0'
2019-04-15 10:11:59.921 10224-10224/? A/DEBUG: ABI: 'arm'
2019-04-15 10:11:59.921 10224-10224/? A/DEBUG: pid: 10175, tid: 10175, name: dbasicknowledge >>> com.*** <<<
2019-04-15 10:11:59.921 10224-10224/? A/DEBUG: signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
2019-04-15 10:11:59.927 10224-10224/? A/DEBUG: Abort message: 'java_vm_ext.cc:534] JNI DETECTED ERROR IN APPLICATION: illegal class name 'Lcom/***/jni/JniObject;''
2019-04-15 10:11:59.928 10224-10224/? A/DEBUG: r0 00000000 r1 000027bf r2 00000006 r3 00000008
2019-04-15 10:11:59.928 10224-10224/? A/DEBUG: r4 000027bf r5 000027bf r6 ffc85f8c r7 0000010c
2019-04-15 10:11:59.928 10224-10224/? A/DEBUG: r8 00000000 r9 e940c1a0 sl 0000000a fp ffc85fd8
2019-04-15 10:11:59.928 10224-10224/? A/DEBUG: ip cdf4ac20 sp ffc85f78 lr ea4f7c6d pc ea4f170e cpsr 200f0030
2019-04-15 10:11:59.936 10224-10224/? A/DEBUG: backtrace:
2019-04-15 10:11:59.936 10224-10224/? A/DEBUG: #00 pc 0001a70e /system/lib/libc.so (abort+63)
2019-04-15 10:11:59.936 10224-10224/? A/DEBUG: #01 pc 0035ad89 /system/lib/libart.so (art::Runtime::Abort(char const*)+392)
2019-04-15 10:11:59.936 10224-10224/? A/DEBUG: #02 pc 0041a62d /system/lib/libart.so (android::base::LogMessage::~LogMessage()+452)
2019-04-15 10:11:59.936 10224-10224/? A/DEBUG: #03 pc 0024d2f9 /system/lib/libart.so (art::JavaVMExt::JniAbort(char const*, char const*)+1212)
2019-04-15 10:11:59.936 10224-10224/? A/DEBUG: #04 pc 0024d47b /system/lib/libart.so (art::JavaVMExt::JniAbortV(char const*, char const*, std::__va_list)+58)
2019-04-15 10:11:59.936 10224-10224/? A/DEBUG: #05 pc 000d5bd3 /system/lib/libart.so (art::ScopedCheck::AbortF(char const*, ...)+42)
2019-04-15 10:11:59.936 10224-10224/? A/DEBUG: #06 pc 000c126d /system/lib/libart.so (art::CheckJNI::FindClass(_JNIEnv*, char const*)+496)
2019-04-15 10:11:59.936 10224-10224/? A/DEBUG: #07 pc 000081c5 /data/app/com.***-cnsQVEa2GWUFUs2IvyISdw==/lib/arm/libjni-sample.so (_JNIEnv::FindClass(char const*)+36)
2019-04-15 10:11:59.936 10224-10224/? A/DEBUG: #08 pc 0000812d /data/app/com.***-cnsQVEa2GWUFUs2IvyISdw==/lib/arm/libjni-sample.so (Java_com_***_jni_JniClient_sampleReflectJNI+24)
2019-04-15 10:11:59.936 10224-10224/? A/DEBUG: #09 pc 0000a0fb /data/app/com.***-cnsQVEa2GWUFUs2IvyISdw==/oat/arm/base.odex (offset 0xa000)
2019-04-15 10:12:01.314 796-796/? E//system/bin/tombstoned: Tombstone written to: /data/tombstones/tombstone_05

也可以将 tombstone 取出来获取更详细的信息。

addr2line 工具

addr2line 工具在 ndk 的交叉编译工具链中,根据当前调试手机平台选择具体的交叉编译工具,示例代码使用的是 armeabindk-bundle\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin ,找到 arm-linux-androideabi-addr2line.exe 文件,它就是 addr2line 工具。
命令格式: addr2line -e so文件 出错地址 ;上面示例中,so 文件为 libjni-sample.so ,这个库文件在 Android Studioapp\build\intermediates\cmake\debug\obj\armeabi-v7a\ 目录下;错误地址是 0000812d ,完整命令如下:

1
2
C:\**\ndk-bundle\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin>arm-linux-androideabi-addr2line.exe -e E:\***\app\build\intermediates\cmake\debug\obj\armeabi-v7a\libjni-sample.so 0x0000812d
E:\***\app\src\main\jni/JniClient.cpp:26

得到的解析结果为源码 JniClient.cpp 的第 26 行错误。

ndk-stack 工具

ndk-stack 工具在 ndk 的当前目录 $NDK_HOME 下,是一个脚本文件 ndk-stack.cmd ,不需要指定交叉编译版本,取出 tombstone 文件后,使用 ndk-stack 自动解析出错误行。
命令格式: ndk-stack -sym so所在文件目录 -dump tobstone > 1.txt ;上面示例中,不需要指定 so 文件名,只需要支持所在目录就行;完整命令如下:

1
C:\***\ndk-bundle\ndk-stack.cmd -sym E:\***\app\build\intermediates\cmake\debug\obj\armeabi-v7a\ -dump tombstone_05 > 1.txt

我们打开 1.txt 文件,可以查看到解析的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
********** Crash dump: **********
Build fingerprint: 'Panasonic/ELUGA_Ray_710/ELUGA_Ray_710:8.1.0/OPM1.171019.011/013015:user/release-keys'
Abort message: 'java_vm_ext.cc:534] JNI DETECTED ERROR IN APPLICATION: illegal class name 'L/com/***/jni/JniObject;''
#00 0x0001a70e /system/lib/libc.so (abort+63)
#01 0x0035ad89 /system/lib/libart.so (art::Runtime::Abort(char const*)+392)
#02 0x0041a62d /system/lib/libart.so (android::base::LogMessage::~LogMessage()+452)
#03 0x0024d2f9 /system/lib/libart.so (art::JavaVMExt::JniAbort(char const*, char const*)+1212)
#04 0x0024d47b /system/lib/libart.so (art::JavaVMExt::JniAbortV(char const*, char const*, std::__va_list)+58)
#05 0x000d5bd3 /system/lib/libart.so (art::ScopedCheck::AbortF(char const*, ...)+42)
#06 0x000c126d /system/lib/libart.so (art::CheckJNI::FindClass(_JNIEnv*, char const*)+496)
#07 0x000081c5 /data/app/com.***-cnsQVEa2GWUFUs2IvyISdw==/lib/arm/libjni-sample.so (_JNIEnv::FindClass(char const*)+36)
_JNIEnv::FindClass(char const*)
C:/***/ndk-bundle/toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/include\jni.h:504:14
#08 0x0000812d /data/app/com.***-cnsQVEa2GWUFUs2IvyISdw==/lib/arm/libjni-sample.so (Java_com_***_jni_JniClient_sampleReflectJNI+24)
Java_com_***_jni_JniClient_sampleReflectJNI
E:\***\app\src\main\jni\JniClient.cpp:26:23
#09 0x0000a0fb /data/app/com.***-cnsQVEa2GWUFUs2IvyISdw==/oat/arm/base.odex (offset 0xa000)
Crash dump is completed

得到了同样的解析结果: JniClient.cpp 的第 26 行错误。

其他

  • JNI 默认对应的是 C 代码,所以在 C++ 代码中需要做如下声明:extern "C"
  • Androidjni.h 定义的函数,都是在 art/runtime/jni_internal.cc 中实现的

参考文档

0%