Android Framework与JNI

本文以 android-12.0.0_r34 的代码进行分析。

framework中的JNI

通常来说,Android framework 中使用到的 native 函数都是动态注册的,而且注册过程有固定的套路。我们以Parcel类为例来解析套路。

首先,我们在Parcel.java中会看到很多标记为 native 的方法(为了便于演示,去掉了很多 native 方法):

java 复制代码
public final class Parcel {
    private static native long nativeCreate();
    private static native long nativeFreeBuffer(long nativePtr);
    private static native void nativeDestroy(long nativePtr);
}

然后,你可以在 Android 源码中找到相应的 jni 定义:

cpp 复制代码
// frameworks/base/core/jni/android_os_Parcel.cpp
#include "core_jni_helpers.h"

namespace android {

static struct parcel_offsets_t
{
    jclass clazz;
    jfieldID mNativePtr;
    jmethodID obtain;
    jmethodID recycle;
} gParcelOffsets;
    

static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
    Parcel* parcel = new Parcel();
    return reinterpret_cast<jlong>(parcel);
}

static void android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativePtr)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        parcel->freeData();
    }
}

static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    delete parcel;
}
}

// ----------------------------------------------------------------------------

static const JNINativeMethod gParcelMethods[] = {
    {"nativeCreate",              "()J", (void*)android_os_Parcel_create},
    {"nativeFreeBuffer",          "(J)V", (void*)android_os_Parcel_freeBuffer},
    {"nativeDestroy",             "(J)V", (void*)android_os_Parcel_destroy},
};

const char* const kParcelPathName = "android/os/Parcel";

int register_android_os_Parcel(JNIEnv* env)
{
    jclass clazz = FindClassOrDie(env, kParcelPathName);

    gParcelOffsets.clazz = MakeGlobalRefOrDie(env, clazz);
    gParcelOffsets.mNativePtr = GetFieldIDOrDie(env, clazz, "mNativePtr", "J");
    gParcelOffsets.obtain = GetStaticMethodIDOrDie(env, clazz, "obtain", "()Landroid/os/Parcel;");
    gParcelOffsets.recycle = GetMethodIDOrDie(env, clazz, "recycle", "()V");

    return RegisterMethodsOrDie(env, kParcelPathName, gParcelMethods, NELEM(gParcelMethods));
}

};

首先,我们会发现 Java 层定义的 native 方法的名字中都带有 native 前缀,但实际上,它们对应的 cpp 函数的名称都不会带有 native 前缀。

其次,源码中习惯上会使用一个全局 static 数组来统一存放 cpp 函数与 Java 层定义的映射,然后,再定义一个register_xxx的函数,此函数内部一般执行两件事,第一件是完成这个文件内所有 JNI 函数的动态注册;第二件是提前使用JNIEnv找到后续 cpp 函数回调 Java 层时可能要用到的jclassjfieldIDjmethodID,这些信息会保存在另一个全局结构体中。

RegisterMethodsOrDie

动态注册的关键在于RegisterMethodsOrDie这个函数,这个函数实际上来自于frameworks/base/core/jni/core_jni_helpers.h(正因为在同一个目录下,因此 include 的时候使用了双引号)

cpp 复制代码
// frameworks/base/core/jni/core_jni_helpers.h

#include <android_runtime/AndroidRuntime.h>
namespace android {

static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
                                       const JNINativeMethod* gMethods, int numMethods) {
    int res = AndroidRuntime::registerNativeMethods(env, className, gMethods, numMethods);
    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
    return res;
}
}

我们继续追踪AndroidRuntime.h,这个头文件位于frameworks/base/core/jni/include/android_runtime/AndroidRuntime.h,实现位于frameworks/base/core/jni/AndroidRuntime.cpp

cpp 复制代码
// frameworks/base/core/jni/AndroidRuntime.cpp

#include <nativehelper/JNIHelp.h>
namespace android {
/*
 * Register native methods using JNI.
 */
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
}

jniRegisterNativeMethods这个方法实际上由JNIHelp.h导入,实现位于libnativehelper/JNIHelp.c

cpp 复制代码
// libnativehelper/JNIHelp.c

int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* methods, int numMethods)
{
    ALOGV("Registering %s's %d native methods...", className, numMethods);
    jclass clazz = (*env)->FindClass(env, className);
    ALOG_ALWAYS_FATAL_IF(clazz == NULL,
                         "Native registration unable to find class '%s'; aborting...",
                         className);
    int result = (*env)->RegisterNatives(env, clazz, methods, numMethods);
    (*env)->DeleteLocalRef(env, clazz);
    if (result == 0) {
        return 0;
    }

    // 剩下的代码与失败处理相关, 省略
    // ...
    ALOGF("RegisterNatives failed for '%s'; aborting...", className);
    return result;
}

何时调用register

摸清楚了 Android framework 如何动态注册 JNI,另一个问题便是 framework 会在什么时候调用相应的 register 函数呢?答案依然在frameworks/base/core/jni/AndroidRuntime.cpp中。

首先,我们会在 AndroidRuntime.cpp 中看到 extern 声明和一个全局数组:

cpp 复制代码
// frameworks/base/core/jni/AndroidRuntime.cpp

using namespace android;

// 不定义在android这个命名空间内的register函数, 数量不少, 这里仅展示一小部分
extern int register_android_opengl_jni_EGL14(JNIEnv* env);
extern int register_android_opengl_jni_EGL15(JNIEnv* env);
extern int register_android_opengl_jni_EGLExt(JNIEnv* env);
// ...

namespace android {
/*
 * JNI-based registration functions.  Note these are properly contained in
 * namespace android.
 */
// 省略...
extern int register_android_os_MessageQueue(JNIEnv* env);
extern int register_android_os_Parcel(JNIEnv* env);
// 省略...

// 定义了REG_JNI, 如果没开启debug, 这个结构体就不会存储名字
#ifdef NDEBUG
    #define REG_JNI(name)      { name }
    struct RegJNIRec {
        int (*mProc)(JNIEnv*);
    };
#else
    #define REG_JNI(name)      { name, #name }
    struct RegJNIRec {
        int (*mProc)(JNIEnv*);
        const char* mName;
    };
#endif
static const RegJNIRec gRegJNI[] = {
        REG_JNI(register_android_os_Parcel),
        REG_JNI(register_android_opengl_jni_EGL14),
        REG_JNI(register_android_opengl_jni_EGL15),
        REG_JNI(register_android_opengl_jni_EGLExt),
        REG_JNI(register_android_os_MessageQueue)
};
}

gRegJNI这个数组基本上约等于存放了一堆 lambda,等待Android 虚拟机启动时,直接遍历该数组就可以完成注册。其调用链大致为:start -> startReg -> register_jni_procs

cpp 复制代码
// frameworks/base/core/jni/AndroidRuntime.cpp

/*
 * Start the Android runtime.  This involves starting the virtual machine
 * and calling the "static void main(String[] args)" method in the class
 * named by "className".
 *
 * Passes the main function two arguments, the class name and the specified
 * options string.
 */
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    // ...
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {
        return;
    }
    onVmCreated(env);

    /*
     * Register android functions.
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }
    // ...
}

/*
 * Register android native functions with the VM.
 */
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
    ATRACE_NAME("RegisterAndroidNatives");
    /*
     * This hook causes all future threads created in this process to be
     * attached to the JavaVM.  (This needs to go away in favor of JNI
     * Attach calls.)
     */
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);

    ALOGV("--- registering native functions ---\n");

    /*
     * Every "register" function calls one or more things that return
     * a local reference (e.g. FindClass).  Because we haven't really
     * started the VM yet, they're all getting stored in the base frame
     * and never released.  Use Push/Pop to manage the storage.
     */
    env->PushLocalFrame(200);

    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);

    //createJavaThread("fubar", quickTest, (void*) "hello");

    return 0;
}

static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
    for (size_t i = 0; i < count; i++) {
        if (array[i].mProc(env) < 0) {
#ifndef NDEBUG
            ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
            return -1;
        }
    }
    return 0;
}
相关推荐
reiraoy6 分钟前
缓存解决方案
java
安之若素^20 分钟前
启用不安全的HTTP方法
java·开发语言
ruanjiananquan9926 分钟前
c,c++语言的栈内存、堆内存及任意读写内存
java·c语言·c++
chuanauc1 小时前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴1 小时前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao1 小时前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc7871 小时前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
YuTaoShao4 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张34 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx7 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁