文章目录
一、概述
JNI(全名Java Native Interface)Java native接口,其可以让一个运行在Java虚拟机中的Java代码被调用或者调用native层的用C/C++编写的基于本机硬件和操作系统的程序。简单理解为就是一个连接Java层和Native层的桥梁。
开发者可以在native层通过JNI调用到Java层的代码,也可以在Java层声明native方法的调用入口。
NDK Version: 21.4.7075529
关联文章:
二、Native 的数据类型
接下来我们先介绍一下JNI层定义的数据类型。
2.1 基本类型
JNI 层的基本数据类型:
// jni.h 文件
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 */
Java 层与JNI 层基本数据类型的映射关系:
JNI 层的基本数据类型只要在 Java 的基本数据类型前加一个
j
即可。如byte -> jbyte
。
Java 类型 | Native类型 | 有无符合 | 字长 |
---|---|---|---|
boolean | jboolean | 无符号 | 8字节 |
byte | jbyte | 有符号 | 8字节 |
char | jchar | 无符号 | 16字节 |
short | jshort | 有符号 | 16字节 |
int | jint | 有符号 | 32字节 |
long | jlong | 有符号 | 64字节 |
float | jfloat | 有符号 | 32字节 |
double | jdouble | 有符号 | 64字节 |
void | void | - | - |
2.2 引用类型
JNI 层的引用类型:
如果使用C++语言编写,则所有引用派生自 jobject 根类,如下所示。
// jni.h 文件
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 {};
Java 层与JNI 层引用类型的映射关系:
JNI 中提供了一系列的引用类型,这些引用类型和Java中的类型是一一对应的。
Java 类型 | Native类型 |
---|---|
Object | jobject |
java.lang.Class | jclass |
java.lang.Throwable | jthrowable |
java.lang.String | jstring |
java.lang.Object[] | jobjectArray |
byte[] | jbyteArray |
char[] | jcharArray |
short[] | jshortArray |
int[] | jintArray |
long[] | jlongArray |
float[] | jfloatArray |
double[] | jdoubleArray |
2.3 jclass 与 jobject 的使用场景
- 为了让 JNI 层具备访问 Java 中的类和对象的能力,JNI 层使用 jobject 指代 Java 层对象,使用 jclass 指代 Java 层的类。
- jobject 与 jclass 通常作为 JNI 函数的第二个参数。
- 当所声明 Native 方法是静态方法时,对应参数类型是 jclass,因为静态方法不依赖对象实例,而依赖于类。
- 当所声明 Native 方法不是静态方法时,对应参数类型是 jobject。
下面是具体的代码演示:
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("jnitestdemo");
}
// 非静态方法
public native String stringFromJNI();
// 静态方法
public static native String staticStringFromJNI();
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_elson_jnitestdemo_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) { //传入jobject,指代MainActivity对象。
std::string hello = "invoke normal fun";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_elson_jnitestdemo_MainActivity_staticStringFromJNI(
JNIEnv *env,
jclass clazz) { //传入jclass,指代MainActivity类。
std::string hello = "invoke static fun";
return env->NewStringUTF(hello.c_str());
}
三、属性ID和方法ID
属性和方法的ID其实是一个C结构体类型的指针:
// 结构体
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID* jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID* jmethodID; /* method IDs */
小结:
- _jfieldID:表示Java层的一个类的属性类型,是一个结构体,而 jfieldID 是结构体的一个指针类型。Native层可以使用 jfieldID 这个指针对属性进行赋值。
- _jmethodID:表示Java层的某个类的方法类型,也是一个结构体,而 jmethodID 是结构体的一个指针类型。
四、方法签名(Signatures)
JNI使用的是Java虚拟机的签名描述方式,具体的类型对照关系如下表所示:
Type Signature | Java Type |
---|---|
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
L + fully-qualified-class; | 类的权限定描述符:如String -> Ljava.lang.String |
[ + type | type[] :属性描述。如 float[] -> [F |
( arg-types ) ret-type | method type:方法描述 |
下面举个实际的例子来说明一下:
Java:public long javaFun(int n, String s, int[] arr);
方法签名:(ILjava/lang/String;[I)J
五、JNINativeMethod
接下来我们看下 JNI 中表示一个方法的结构体 JNINativeMethod。
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
说明:
- 变量name:代表 Java 中函数的名字。
- 变量signature:用字符串是描述了Java中函数的参数和返回值,即方法签名。
- 变量fnPtr:是一个函数指针,指向 native 函数。前面都要接
void *
。 - 第一个变量与第三个变量是对应的,一个是java层方法名,对应着第三个参数的 native 方法名字。
示例:
下面我们以 Framework 层的 MessageQueue.java 为例来说明:
// MessageQueue.class
public final class MessageQueue {
private native static long nativeInit();
private native static void nativeWake(long ptr);
}
对应的 JNI 代码:/frameworks/base/core/jni/android_os_MessageQueue.cpp
// frameworks/base/core/jni/android_os_MessageQueue.cpp
static const JNINativeMethod gMessageQueueMethods[] = {
// 第1个参数,对应java层的方法名。
// 第2个参数,对应java层的方法签名(参数和返回值类型)。
// 第3个参数,指向native层对应方法的指针。
{ "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
{ "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
// 省略部分注册代码。
};
// 对应java层的MessageQueue.nativeInit()方法。因为nativeInit是静态方法,所以native方法对于的第2个参数是jclass类型。
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
// 对应java层的MessageQueue.nativeWake()方法。
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
小结:
- 因为Java层调用的是 static 的 native 方法,所以对应的 JNI 方法的第二个参数类型是 jclass。
- JNINativeMethod 结构体中,第一个变量对应java层的方法;第二个变量对应Java层的方法签名;第三个变量对应的是 native 方法名字。
六、全局JNINativeMethod的注册流程
我们知道在 Java 层可以直接调用 JNI 层的代码,但上面的 gMessageQueueMethods[] 数组只是将 Java 层方法与 JNI 层的方法进行映射,在实际调用之前还需要将这个映射关系注册到全局的映射表里,这样才调用 native 代码时,可以从全局映射表里直接查找对应的方法。
下面我们来分析一下它的注册流程,通过源码查看网站的全局搜索功能直接搜,可以定位到 register_android_os_MessageQueue()
方法在 AndroidRuntime.cpp
中被调用,具体如下:
// frameworks/base/core/jni/android_os_MessageQueue.cpp
int register_android_os_MessageQueue(JNIEnv* env) {
int res = RegisterMethodsOrDie(env, "android/os/MessageQueue",
gMessageQueueMethods, NELEM(gMessageQueueMethods));
jclass clazz = FindClassOrDie(env, "android/os/MessageQueue");
gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J");
gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz,
"dispatchEvents", "(II)I");
return res;
}
// frameworks/base/core/jni/AndroidRuntime.cpp
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
// 省略
// 此处就是注册我们MessageQueue映射关系的地方。
REG_JNI(register_android_os_MessageQueue),
// 省略
REG_JNI(register_android_app_Activity),
REG_JNI(register_android_app_ActivityThread),
}
// frameworks/base/core/jni/AndroidRuntime.cpp
// Register android native functions with the VM.
int AndroidRuntime::startReg(JNIEnv* env)
{
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
env->PushLocalFrame(200);
// 实际使用gRegJNI数组的地方
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL);
return 0;
}
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
static const String8 startSystemServer("start-system-server");
/* start the virtual machine */
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
// 虚拟机的启动
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
// 创建虚拟机
onVmCreated(env);
// 这里执行JNI的注册
if (startReg(env) < 0) {
return;
}
// 通过字符串拼接启动指定文件内的main方法。
// 略
}
小结:
- 在 AndroidRuntime 启动的时候,就把全局的 JNI 映射关系表注册到进程中。
六、JavaVM指针
JavaVM 是虚拟机在JNI层的代表,一个进程只有一个JavaVM,所有的线程共享。
// jni.h
struct _JavaVM;
#if defined(__cplusplus)
typedef _JavaVM JavaVM; //C++重新定义了一个名字。
struct _JavaVM {
const struct JNIInvokeInterface* functions;
// C++部分的定义
#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*/
};
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*);
};
七、JNIEnv指针
JNIEnv (Java Native Interface Environment) 是一个JNI接口指针(每个线程独有一个 JNIEnv 指针),指向了本地方法的一个函数表,该函数表中的每一个成员指向了一个JNI函数,本地的方法通过JNI函数来访问JVM中的数据结构,详情如下图:
JNIEnv 结构体中的功能大体可以分为下表所示的几个部分:
- Class操作
- 反射操作
- 对象字段 & 方法操作
- 类的静态字段 & 静态方法操作
- 字符串操作
- 锁操作
- 数组操作
- 注册和反注册native方法
- 异常Exception操作
- 引用的操作
详细分析请参考文章:NDK(三):JNIEnv解析 。
八、小结
本文主要介绍了如下几点:
- Java 层与 JNI 层的基本数据类型和引用类型的对应关系。
- jclass 与 jobject 的使用场景差异,这与 Java 层和字节码层的数据类型对应关系类似。
- 属性ID和方法ID。
- 方法签名的对应关系。
- JNINativeMethod 的结构体和 JNINativeMethod 的注册流程。
- JavaVM 指针。
- JNIEnv 指针。