码字不易,请大佬们点点关注,谢谢~
一、方法注册概述
在Android Runtime(ART)中,方法注册是连接Java层和Native层的桥梁,它允许Java代码调用本地方法(如C/C++实现的方法)。方法注册分为静态注册和动态注册两种方式,每种方式都有其独特的流程和应用场景。
从源码角度来看,方法注册涉及art/runtime目录下多个核心模块。jni_env.cc和jni_internal.h定义了JNI环境和内部接口,native_method_registration.cc实现了方法注册的具体逻辑,class_linker.cc负责类的链接和方法解析。接下来,我们将深入分析这两种注册方式的原理与实现细节。
二、静态注册原理
2.1 静态注册基本流程
静态注册是基于方法签名的自动映射机制。当Java代码第一次调用Native方法时,VM会通过JNI规范定义的命名规则查找对应的本地函数。例如,Java方法package.name.ClassName.nativeMethod()会映射到C/C++函数Java_package_name_ClassName_nativeMethod(JNIEnv*, jobject)。
在art/runtime/jni_env.cc中,静态注册的核心逻辑如下:
// JNIEnv::GetMethodID 方法调用时触发静态注册查找
jmethodID JNIEnv::GetMethodID(jclass clazz, const char* name, const char* sig) {
// 检查方法是否已注册
mirror::ArtMethod* method = FindMethod(clazz, name, sig, kInstanceMethod);
if (method == nullptr) {
// 方法未找到,尝试通过静态注册规则查找本地方法
method = LookupNativeMethod(clazz, name, sig);
if (method != nullptr) {
// 找到本地方法,完成注册和链接
RegisterNativeMethod(method);
}
}
return method;
}
这段代码展示了在获取方法ID时,如果方法未找到,会尝试通过静态注册规则查找本地方法并完成注册。
2.2 方法签名解析
静态注册依赖于方法签名的正确解析。在art/runtime/jni/java_vm_ext.cc中,定义了方法签名解析的核心逻辑:
// 解析方法签名,提取返回类型和参数类型
bool ParseMethodDescriptor(const char* descriptor,
std::string* return_type,
std::vector<std::string>* param_types) {
// 跳过起始字符 '('
if (*descriptor++ != '(') {
return false;
}
// 解析参数类型
while (*descriptor != ')') {
std::string param_type;
if (!ParseTypeDescriptor(&descriptor, ¶m_type)) {
return false;
}
param_types->push_back(param_type);
}
descriptor++; // 跳过 ')'
// 解析返回类型
return ParseTypeDescriptor(&descriptor, return_type);
}
方法签名解析器将形如(Ljava/lang/String;I)V的描述符解析为参数类型列表和返回类型,这对于正确映射本地方法至关重要。
2.3 命名规则与符号查找
静态注册遵循严格的命名规则。在art/runtime/native_method_registration.cc中,实现了根据Java类名和方法名生成本地函数名的逻辑:
// 根据Java类名和方法名生成本地函数名
std::string GenerateNativeMethodName(const char* class_name,
const char* method_name,
bool is_static) {
std::string native_name = "Java_";
// 替换类名中的 '/' 为 '_'
for (const char* p = class_name; *p != '\0'; ++p) {
native_name += (*p == '/') ? '_' : *p;
}
native_name += '_';
// 处理方法名中的特殊字符
for (const char* p = method_name; *p != '\0'; ++p) {
if (*p == '_') {
native_name += "_1";
} else if (*p == ';') {
native_name += "_2";
} else if (*p == '[') {
native_name += "_3";
} else {
native_name += *p;
}
}
return native_name;
}
生成的本地函数名会通过符号查找机制(如dlsym)在本地库中查找对应的函数实现。
三、动态注册原理
3.1 动态注册基本流程
动态注册通过JNI_OnLoad函数在库加载时手动将Java方法与本地函数关联。这种方式更加灵活,不依赖于严格的命名规则。在art/runtime/jni/java_vm_ext.cc中,定义了动态注册的核心接口:
// JNIEnv::RegisterNatives 方法实现动态注册
jint JNIEnv::RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods) {
ScopedFastNativeObjectAccess soa(soa_self);
mirror::Class* c = soa.Decode<mirror::Class*>(clazz);
// 遍历并注册每个本地方法
for (jint i = 0; i < nMethods; ++i) {
const char* name = methods[i].name;
const char* signature = methods[i].signature;
void* fnPtr = methods[i].fnPtr;
// 查找或创建对应的ArtMethod
mirror::ArtMethod* method = FindMethod(c, name, signature, kInstanceMethod);
if (method == nullptr) {
method = CreateMethod(c, name, signature, kInstanceMethod);
}
// 设置本地方法指针
method->SetEntryPointFromQuickCompiledCode(
reinterpret_cast<void*>(fnPtr));
method->SetNativeMethod(true);
}
return JNI_OK;
}
这段代码展示了动态注册的核心逻辑:遍历方法数组,查找或创建对应的ArtMethod对象,并设置其本地方法指针。
3.2 JNI_OnLoad函数
动态注册通常在JNI_OnLoad函数中完成。在art/runtime/jni/java_vm_ext.cc中,定义了JNI_OnLoad的调用流程:
// 调用本地库的JNI_OnLoad函数
jint JNIEnvExt::Call_JNI_OnLoad(JavaVM* vm, const char* path, void* handle) {
// 获取JNI_OnLoad函数指针
jni::OnLoadFunction on_load =
reinterpret_cast<jni::OnLoadFunction>(dlsym(handle, "JNI_OnLoad"));
if (on_load == nullptr) {
return JNI_VERSION_1_4; // 没有JNI_OnLoad函数,使用默认版本
}
// 调用JNI_OnLoad函数
return on_load(vm, nullptr);
}
JNI_OnLoad函数通常会调用RegisterNatives完成方法注册,并返回当前使用的JNI版本。
3.3 Native方法数组结构
动态注册使用JNINativeMethod数组定义方法映射关系。在jni.h中,该结构定义如下:
typedef struct {
const char* name; // Java方法名
const char* signature; // 方法签名
void* fnPtr; // 本地函数指针
} JNINativeMethod;
例如,以下是一个典型的JNINativeMethod数组示例:
static JNINativeMethod gMethods[] = {
{"nativeMethod1", "()V", (void*)NativeMethod1},
{"nativeMethod2", "(Ljava/lang/String;I)Z", (void*)NativeMethod2},
};
在JNI_OnLoad中,这个数组会被传递给RegisterNatives方法完成注册。
四、静态注册与动态注册对比
4.1 性能对比
静态注册在首次调用时需要进行方法查找和符号解析,有一定的启动开销;而动态注册在库加载时完成注册,后续调用无需查找,性能更优。在art/runtime/jni/java_vm_ext.cc中,静态注册的符号查找逻辑如下:
// 静态注册中的符号查找
void* FindNativeMethod(const std::string& native_name, void* handle) {
// 使用dlsym查找符号
void* result = dlsym(handle, native_name.c_str());
if (result == nullptr) {
// 尝试查找带JNIEXPORT前缀的符号
std::string prefixed_name = "JNIEXPORT_" + native_name;
result = dlsym(handle, prefixed_name.c_str());
}
return result;
}
动态注册避免了这种每次调用的符号查找开销。
4.2 灵活性对比
静态注册依赖严格的命名规则,修改Java类名或方法名需要同步修改本地函数名;而动态注册通过手动映射,更加灵活。例如,在大型项目中,动态注册可以更好地管理命名冲突和代码组织。
4.3 适用场景对比
静态注册适用于小型项目或快速开发,无需额外编写注册代码;动态注册适用于性能敏感的应用或需要灵活管理方法映射的场景。例如,Android系统框架中的大量JNI方法都采用动态注册方式。
五、方法注册的数据结构
5.1 ArtMethod结构
ArtMethod是ART中表示方法的核心数据结构。在art/runtime/mirror/art_method.h中,其定义包含了方法的各种属性:
class ArtMethod FINAL : public Object {
public:
// 方法访问标志
uint32_t access_flags_;
// 方法所属类
GcRoot<mirror::Class> declaring_class_;
// 方法名
GcRoot<mirror::String> name_;
// 方法签名
GcRoot<mirror::Signature> signature_;
// 方法实现相关字段
union {
// 解释执行入口
void* entry_point_from_interpreter_;
// 快速编译执行入口(本地方法)
void* entry_point_from_quick_compiled_code_;
// 优化编译执行入口
void* entry_point_from_optimized_compiled_code_;
};
// 是否为本地方法标志
bool is_native_;
//...其他字段和方法
};
方法注册的核心就是设置ArtMethod的相关字段,特别是本地方法的入口点。
5.2 方法查找表
ART使用多种数据结构加速方法查找。在art/runtime/class_linker.cc中,类加载时会构建方法查找表:
// 构建类的方法查找表
void ClassLinker::BuildMethodTable(mirror::Class* klass) {
// 获取类的所有方法
uint32_t method_count = klass->NumDirectMethods() + klass->NumVirtualMethods();
mirror::ArtMethod** methods = klass->GetMethodsPtr();
// 构建方法名到方法的映射
for (uint32_t i = 0; i < method_count; ++i) {
mirror::ArtMethod* method = methods[i];
const char* name = method->GetName()->ToModifiedUtf8().c_str();
const char* signature = method->GetSignature()->ToModifiedUtf8().c_str();
// 将方法添加到查找表
method_table_.Insert(name, signature, method);
}
}
这个查找表在静态注册和动态注册中都起到关键作用。
六、方法注册的具体流程
6.1 静态注册完整流程
静态注册的完整流程涉及多个步骤。在art/runtime/jni_env.cc中,GetMethodID方法触发的静态注册流程如下:
jmethodID JNIEnv::GetMethodID(jclass clazz, const char* name, const char* sig) {
ScopedFastNativeObjectAccess soa(self_);
mirror::Class* c = soa.Decode<mirror::Class*>(clazz);
// 1. 查找类中是否已存在该方法
mirror::ArtMethod* method = FindMethod(c, name, sig, kInstanceMethod);
if (method != nullptr) {
return method;
}
// 2. 检查是否为本地方法并尝试静态注册
if (IsNativeMethod(c, name, sig)) {
// 2.1 生成本地方法名
std::string native_name = GenerateNativeMethodName(
c->GetDescriptor().c_str(), name, false);
// 2.2 查找本地库中的函数
void* fn_ptr = FindNativeMethod(native_name, c->GetDexCache()->GetResolvedClasses());
if (fn_ptr != nullptr) {
// 2.3 创建并初始化ArtMethod
method = CreateMethod(c, name, sig, kInstanceMethod);
method->SetEntryPointFromQuickCompiledCode(fn_ptr);
method->SetNativeMethod(true);
// 2.4 链接方法
LinkMethod(method);
return method;
}
}
// 方法未找到,抛出异常
ThrowNoSuchMethodError(c, name, sig);
return nullptr;
}
6.2 动态注册完整流程
动态注册的完整流程在JNI_OnLoad调用时完成。在art/runtime/jni/java_vm_ext.cc中,核心流程如下:
jint JNIEnvExt::RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods) {
ScopedFastNativeObjectAccess soa(self_);
mirror::Class* c = soa.Decode<mirror::Class*>(clazz);
// 1. 验证类是否已初始化
if (!c->IsInitialized()) {
// 类未初始化,需要先初始化
if (!EnsureClassInitialized(c, true, true)) {
return JNI_ERR;
}
}
// 2. 遍历并注册每个本地方法
for (jint i = 0; i < nMethods; ++i) {
const char* name = methods[i].name;
const char* signature = methods[i].signature;
void* fnPtr = methods[i].fnPtr;
// 2.1 查找或创建方法
mirror::ArtMethod* method = FindMethod(c, name, signature, kInstanceMethod);
if (method == nullptr) {
method = CreateMethod(c, name, signature, kInstanceMethod);
}
// 2.2 设置方法属性
method->SetEntryPointFromQuickCompiledCode(fnPtr);
method->SetNativeMethod(true);
method->SetAccessFlags(method->GetAccessFlags() | kAccNative);
// 2.3 链接方法
LinkMethod(method);
}
return JNI_OK;
}
七、方法注册的错误处理
7.1 注册失败的常见原因
方法注册可能因多种原因失败,包括:
- 本地方法名与Java方法不匹配
- 方法签名错误
- 本地库未正确加载
- 符号查找失败
在art/runtime/jni/java_vm_ext.cc中,处理注册失败的代码如下:
// 处理方法注册失败
void HandleRegistrationFailure(mirror::Class* clazz,
const char* name,
const char* signature,
const char* error_msg) {
// 记录错误日志
LOG(ERROR) << "Failed to register native method "
<< clazz->GetDescriptor().c_str() << "." << name
<< signature << ": " << error_msg;
// 抛出异常
ScopedObjectAccess soa(Thread::Current());
soa.Env()->ThrowNew(soa.Env()->FindClass("java/lang/RuntimeException"), error_msg);
}
7.2 异常处理机制
当方法注册失败时,ART会抛出适当的异常。例如,在静态注册中,如果找不到对应的本地方法,会抛出NoSuchMethodError:
// 抛出NoSuchMethodError异常
void JNIEnv::ThrowNoSuchMethodError(mirror::Class* clazz,
const char* name,
const char* sig) {
ScopedObjectAccess soa(self_);
jclass exception_class = soa.Env()->FindClass("java/lang/NoSuchMethodError");
std::string msg = "Native method not found: ";
msg += clazz->GetDescriptor().c_str();
msg += ".";
msg += name;
msg += sig;
soa.Env()->ThrowNew(exception_class, msg.c_str());
}
八、方法注册的性能优化
8.1 预注册机制
为了减少首次调用本地方法的开销,ART引入了预注册机制。在art/runtime/jni/java_vm_ext.cc中,定义了预注册的接口:
// 预注册本地方法
jint JNIEnvExt::PreRegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods) {
// 与RegisterNatives类似,但不执行完整的方法链接
// 只记录方法映射关系,延迟到首次调用时再完成链接
//...
}
预注册机制在应用启动时记录方法映射,避免首次调用时的查找开销。
8.2 方法查找缓存
ART使用方法查找缓存加速后续方法调用。在art/runtime/mirror/art_method.h中,定义了方法查找缓存的相关字段:
class ArtMethod FINAL : public Object {
public:
// 方法查找缓存
mutable GcRoot<mirror::Object> invocation_cache_;
// 获取方法查找缓存
mirror::Object* GetInvocationCache() const {
return invocation_cache_.Read();
}
// 设置方法查找缓存
void SetInvocationCache(mirror::Object* cache) {
invocation_cache_.Assign(cache);
}
};
方法查找缓存存储了方法调用的相关信息,避免每次调用都进行完整的查找。
九、方法注册在不同场景下的应用与挑战
9.1 系统级应用场景
在Android系统框架中,大量使用JNI实现Java层与C/C++层的交互。例如,Android的多媒体框架、图形系统等都依赖JNI方法注册。这些系统级应用通常采用动态注册方式,以获得更好的性能和可维护性。
9.2 应用开发场景
在应用开发中,JNI方法注册常用于性能优化或使用现有的C/C++库。例如,游戏开发中常用的渲染引擎、音频处理库等。应用开发中可以根据需求选择静态注册或动态注册,但动态注册更为常见,因为它提供了更大的灵活性。
9.3 多架构支持挑战
在支持多种硬件架构(如ARM、x86等)时,方法注册面临挑战。不同架构的本地库可能有不同的符号命名规则和调用约定。ART通过统一的JNI接口和架构特定的适配层来解决这些问题。在art/runtime/arch目录下,为不同架构提供了特定的实现。
十、方法注册与其他ART组件的交互
10.1 与类加载系统的交互
方法注册与类加载系统密切相关。在类加载过程中,需要解析和链接类的方法。在art/runtime/class_linker.cc中,类链接器负责处理方法注册相关的工作:
// 链接类的方法
void ClassLinker::LinkMethods(mirror::Class* klass) {
// 遍历类的所有方法
uint32_t method_count = klass->NumDirectMethods() + klass->NumVirtualMethods();
mirror::ArtMethod** methods = klass->GetMethodsPtr();
for (uint32_t i = 0; i < method_count; ++i) {
mirror::ArtMethod* method = methods[i];
// 如果是本地方法,确保已注册
if (method->IsNative()) {
EnsureNativeMethodRegistered(method);
}
// 链接方法
LinkMethod(method);
}
}
10.2 与垃圾回收系统的交互
方法注册也与垃圾回收系统交互。本地方法可能引用Java对象,这些引用需要被垃圾回收器正确处理。在art/runtime/gc目录下,定义了处理JNI引用的相关逻辑:
// 处理JNI局部引用
void JniLocalRefTable::AddLocalReference(Thread* self, jobject obj) {
// 将JNI局部引用添加到引用表
// 垃圾回收器会扫描这些引用表,确保引用的对象不被回收
//...
}
10.3 与JIT/AOT编译器的交互
方法注册影响JIT(即时编译)和AOT(预编译)编译器的优化。在art/runtime/jit和art/runtime/aot目录下,编译器会根据方法是否为本地方法进行不同的优化处理:
// 编译方法
bool JitCompiler::CompileMethod(mirror::ArtMethod* method, Thread* self) {
// 如果是本地方法,处理方式与Java方法不同
if (method->IsNative()) {
// 为本地方法生成特殊的调用桩
GenerateNativeMethodStub(method);
return true;
}
// 编译Java方法
//...
}
十一、方法注册的未来发展趋势
11.1 更高效的注册机制
未来,ART可能会引入更高效的方法注册机制,进一步减少注册开销。例如,利用编译时信息预生成注册代码,或优化符号查找算法。
11.2 与新编程语言的集成
随着Kotlin等新编程语言在Android开发中的普及,方法注册机制可能需要适应这些新语言的特性。例如,支持Kotlin的协程、扩展函数等特性在JNI中的映射。
11.3 安全与隐私增强
未来的方法注册机制可能会加强安全与隐私保护。例如,对本地方法的访问权限进行更精细的控制,防止恶意代码通过JNI接口进行攻击。