NDK(二):JNI 的数据结构

一、概述

JNI(全名Java Native Interface)Java native接口,其可以让一个运行在Java虚拟机中的Java代码被调用或者调用native层的用C/C++编写的基于本机硬件和操作系统的程序。简单理解为就是一个连接Java层和Native层的桥梁。

开发者可以在native层通过JNI调用到Java层的代码,也可以在Java层声明native方法的调用入口。

NDK Version21.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类型有无符合字长
booleanjboolean无符号8字节
bytejbyte有符号8字节
charjchar无符号16字节
shortjshort有符号16字节
intjint有符号32字节
longjlong有符号64字节
floatjfloat有符号32字节
doublejdouble有符号64字节
voidvoid--

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类型
Objectjobject
java.lang.Classjclass
java.lang.Throwablejthrowable
java.lang.Stringjstring
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 SignatureJava Type
Zboolean
Bbyte
Cchar
Sshort
Iint
Jlong
Ffloat
Ddouble
L + fully-qualified-class;类的权限定描述符:如String -> Ljava.lang.String
[ + typetype[] :属性描述。如 float[] -> [F
( arg-types ) ret-typemethod 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 指针。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值