Android Framework与JNI

ops/2024/11/17 8:06:52/

本文以 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 定义:

// 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 的时候使用了双引号)

// 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

// 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

// libnativehelper/JNIHelp.cint 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 声明和一个全局数组:

// frameworks/base/core/jni/AndroidRuntime.cppusing 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

// 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 NDEBUGALOGD("----------!!! %s failed to load\n", array[i].mName);
#endifreturn -1;}}return 0;
}

http://www.ppmy.cn/ops/134368.html

相关文章

Java-02 深入浅出 MyBatis - MyBatis 快速入门(无 Spring) POM Mapper 核心文件 增删改查

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…

freemarker 读取template.xml ,通过response 输出文件,解决中文乱码问题

采用 try (Writer writer new OutputStreamWriter(os, “UTF-8”)) UTF-8 内容转换 public static void setResponseHeader(HttpServletResponse response, String fileName) {try {// fileName "中文.xls";try {fileName new String(fileName.getBytes(),"…

PostgreSQL中的COPY命令:高效数据导入与导出

在PostgreSQL数据库中&#xff0c;数据导入和导出是日常工作中常见的操作。传统的插入&#xff08;INSERT&#xff09;方法虽然可以实现数据的导入&#xff0c;但在处理大量数据时效率较低。而COPY命令则提供了一个快速、高效的方式来完成这一任务。COPY命令不仅可以用于将数据…

在启动 Spring Boot 项目时,报找不到 slf4j 的错误

而且 tomcat 的启动信息不知道为什么输出出来了 问 AI 得到的解决方案&#xff1a; 将 pom.xml 中的如下配置替换成这样&#xff0c;排除这个插件 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring - boot - starter - …

论文阅读--supervised learning with quantum enhanced feature spaces

简略摘要 量子算法实现计算加速的核心要素是通过可控纠缠和干涉利用指数级大的量子态空间。本文在超导处理器上提出并实验实现了两种量子算法。这两种方法的一个关键组成部分是使用量子态空间作为特征空间。只有在量子计算机上才能有效访问的量子增强特征空间的使用为量子优势提…

【数字静态时序分析】复杂时钟树的时序约束SDC写法

以上图为例&#xff0c;SoC芯片上往往存在几种不同的时钟源&#xff0c;有pll时钟、环振时钟、外部的晶振时钟&#xff0c;在SoC不同的模块或者不同的运行阶段使用的时钟也往往不同&#xff0c;所以在使用的时候&#xff0c;相同的模块会出现选择不同的时钟源的情况。上图的情形…

SQL笔试题笔记(1)

下列选项中关于数据库事务的特性描述正确的是&#xff08;&#xff09; A.事务允许继续分割B.多个事务在执行事务前后对同一个数据读取的结果是不同的C.一个事务对数据库中数据的改变是暂时的D.并发访问数据库时&#xff0c;各并发事务之间数据库是独立的 答案解析&#xff1a…

基于微信小程序的校园超市购物系统设计与实现,LW+源码+讲解

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本超市购物系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&a…