Android Runtime Java到Native调用转换原理
一、调用转换概述
在Android Runtime(ART)中,Java到Native的调用转换是实现Java与C/C++代码交互的核心机制。这种跨语言调用涉及多个复杂步骤,包括方法查找、参数转换、JNI环境准备、栈帧切换和返回值处理等。理解这一过程对于开发高性能JNI应用和深入理解Android系统架构至关重要。
从源码角度来看,调用转换涉及art/runtime目录下的多个核心模块。jni_env.cc和jni_internal.h定义了JNI环境和内部接口,interpreter目录实现了解释执行相关逻辑,quick目录包含了快速调用路径的实现,stack目录处理栈帧管理。接下来,我们将深入分析每个关键步骤的原理与实现细节。
二、方法查找与解析
2.1 方法查找流程
当Java代码调用Native方法时,首先需要在运行时查找对应的本地方法实现。在art/runtime/jni_env.cc中,CallVoidMethod等JNI调用接口会触发方法查找:
// JNIEnv::CallVoidMethod 实现
void JNIEnv::CallVoidMethod(jobject obj, jmethodID methodID, ...) {
va_list args;
va_start(args, methodID);
CallVoidMethodV(obj, methodID, args);
va_end(args);
}
// JNIEnv::CallVoidMethodV 实现
void JNIEnv::CallVoidMethodV(jobject obj, jmethodID methodID, va_list args) {
ScopedFastNativeObjectAccess soa(self_);
mirror::ArtMethod* method = soa.DecodeMethod(methodID);
// 检查方法是否为本地方法
if (method->IsNative()) {
// 处理本地方法调用
InvokeNativeMethodV(soa, method, obj, args, kVoid);
} else {
// 处理Java方法调用
InvokeVirtualMethodV(soa, method, obj, args, kVoid);
}
}
这段代码展示了JNI调用的入口点,其中会检查方法是否为本地方法并进行相应处理。
2.2 本地方法解析
对于本地方法,需要解析其具体实现。在art/runtime/jni/java_vm_ext.cc中,定义了本地方法解析的核心逻辑:
// 解析本地方法
void* JNIEnvExt::ResolveNativeMethod(mirror::ArtMethod* method) {
// 检查方法是否已经解析
if (method->IsNative() && method->GetEntryPointFromQuickCompiledCode() != nullptr) {
return method->GetEntryPointFromQuickCompiledCode();
}
// 尝试静态注册
void* result = nullptr;
if (!method->IsResolved()) {
result = LookupNativeMethod(method);
if (result != nullptr) {
method->SetEntryPointFromQuickCompiledCode(result);
method->SetResolved(true);
return result;
}
}
// 尝试动态注册
result = FindRegisteredNativeMethod(method);
if (result != nullptr) {
method->SetEntryPointFromQuickCompiledCode(result);
method->SetResolved(true);
return result;
}
// 未找到本地方法实现
return nullptr;
}
这段代码展示了本地方法解析的过程,首先检查方法是否已经解析,然后尝试静态注册和动态注册查找本地方法实现。
三、JNI环境准备
3.1 JNIEnv结构初始化
在进行Java到Native的调用前,需要准备JNI环境。JNIEnv是一个指向JNI函数表的指针,为本地代码提供访问Java虚拟机功能的接口。在art/runtime/jni_env_ext.h中,JNIEnvExt类的定义如下:
class JNIEnvExt : public JNIEnv {
public:
// 线程指针
Thread* self_;
// JNI函数表
const JNINativeInterface* functions_;
// 局部引用表
JniLocalRefTable* local_ref_table_;
// 异常信息
GcRoot<mirror::Throwable> pending_exception_;
//...其他成员变量和方法
};
每个线程都有自己的JNIEnvExt实例,用于管理本地方法调用的上下文。
3.2 局部引用表管理
JNI局部引用表用于管理本地代码中创建的Java对象引用。在art/runtime/jni_local_ref_table.cc中,定义了局部引用表的管理逻辑:
// 添加局部引用
jobject JniLocalRefTable::AddLocalReference(Thread* self, jobject obj) {
// 检查是否需要扩容
if (num_refs_ >= capacity_) {
if (!Resize(self)) {
return nullptr; // 扩容失败
}
}
// 添加引用到表中
refs_[num_refs_] = obj;
return reinterpret_cast<jobject>(reinterpret_cast<uintptr_t>(kLocalRefBase) | num_refs_++);
}
// 释放局部引用
bool JniLocalRefTable::DeleteLocalReference(Thread* self, jobject ref) {
uint32_t index = GetLocalRefIndex(ref);
if (index >= num_refs_) {
return false; // 无效引用
}
// 移除引用
refs_[index] = nullptr;
// 压缩引用表(如果需要)
CompressIfNeeded();
return true;
}
局部引用表确保本地代码中使用的Java对象在调用期间不会被垃圾回收。
四、参数传递与转换
4.1 基本数据类型传递
基本数据类型(如int、float等)在Java和Native之间的传递相对简单,直接进行值传递。在art/runtime/jni_bridge.cc中,定义了基本数据类型的参数传递逻辑:
// 处理基本数据类型参数传递
void SetupPrimitiveArgs(mirror::ArtMethod* method, uint8_t* args, va_list ap) {
// 获取方法参数类型
const char* signature = method->GetSignature()->ToModifiedUtf8().c_str();
// 跳过方法描述符的起始字符 '('
const char* p = signature + 1;
// 遍历每个参数
while (*p != ')') {
switch (*p) {
case 'Z': // boolean
*reinterpret_cast<bool*>(args) = va_arg(ap, int);
args += sizeof(bool);
break;
case 'B': // byte
*reinterpret_cast<int8_t*>(args) = va_arg(ap, int);
args += sizeof(int8_t);
break;
case 'C': // char
*reinterpret_cast<uint16_t*>(args) = va_arg(ap, int);
args += sizeof(uint16_t);
break;
case 'S': // short
*reinterpret_cast<int16_t*>(args) = va_arg(ap, int);
args += sizeof(int16_t);
break;
case 'I': // int
*reinterpret_cast<int32_t*>(args) = va_arg(ap, int);
args += sizeof(int32_t);
break;
case 'J': // long
*reinterpret_cast<int64_t*>(args) = va_arg(ap, int64_t);
args += sizeof(int64_t);
break;
case 'F': // float
*reinterpret_cast<float*>(args) = va_arg(ap, double);
args += sizeof(float);
break;
case 'D': // double
*reinterpret_cast<double*>(args) = va_arg(ap, double);
args += sizeof(double);
break;
// 处理数组和对象类型
case '[':
case 'L':
// 对象类型参数处理逻辑
SetupObjectArg(args, va_arg(ap, jobject));
args += sizeof(jobject);
p = SkipObjectType(p);
continue;
default:
// 无效类型
LOG(FATAL) << "Invalid argument type: " << *p;
break;
}
p++;
}
}
4.2 对象引用传递
对象引用在Java和Native之间的传递需要特殊处理。本地代码不能直接访问Java对象,而是通过JNI提供的接口操作对象。在art/runtime/jni_bridge.cc中,定义了对象引用的传递逻辑:
// 处理对象类型参数传递
void SetupObjectArg(uint8_t* args, jobject obj) {
// 将Java对象引用转换为JNI引用
if (obj == nullptr) {
*reinterpret_cast<jobject*>(args) = nullptr;
} else {
// 创建局部引用
Thread* self = Thread::Current();
JNIEnvExt* env = self->GetJniEnv();
jobject local_ref = env->NewLocalRef(obj);
*reinterpret_cast<jobject*>(args) = local_ref;
}
}
本地代码通过JNI接口操作这些引用,而不是直接访问Java对象。
4.3 字符串与数组处理
字符串和数组是特殊的对象类型,需要专门的转换逻辑。在art/runtime/jni_string.cc和art/runtime/jni_array.cc中,定义了字符串和数组的处理逻辑:
// 将Java字符串转换为C字符串
const char* JNIEnvExt::GetStringUTFChars(jstring string, jboolean* isCopy) {
if (string == nullptr) {
return nullptr;
}
// 获取Java字符串对象
mirror::String* str = Decode<mirror::String*>(string);
// 转换为UTF-8编码
std::string utf8_str = str->ToModifiedUtf8();
// 分配内存并复制字符串
char* result = static_cast<char*>(malloc(utf8_str.length() + 1));
if (result == nullptr) {
return nullptr;
}
memcpy(result, utf8_str.c_str(), utf8_str.length() + 1);
// 记录分配的内存,以便后续释放
AddPendingLocalFree(result);
if (isCopy != nullptr) {
*isCopy = JNI_TRUE; // 总是返回副本
}
return result;
}
// 释放C字符串
void JNIEnvExt::ReleaseStringUTFChars(jstring string, const char* utf) {
// 查找并释放之前分配的内存
RemovePendingLocalFree(const_cast<char*>(utf));
free(const_cast<char*>(utf));
}
五、栈帧转换与调用
5.1 栈帧结构
ART使用不同的栈帧结构来支持Java和Native代码执行。在art/runtime/stack_frame.h中,定义了栈帧的基本结构:
class StackFrame {
public:
// 栈帧类型
enum class Type {
kFrameInterpreter, // 解释器栈帧
kFrameQuick, // 快速编译栈帧
kFrameJni, // JNI栈帧
kFrameJniTransition, // JNI转换栈帧
//...其他类型
};
// 获取栈帧类型
Type GetType() const;
// 获取栈帧大小
size_t GetSize() const;
// 获取调用者栈帧
StackFrame* GetCaller() const;
//...其他方法
};
5.2 栈帧转换过程
从Java到Native的调用需要进行栈帧转换。在art/runtime/jni_bridge.cc中,定义了栈帧转换的核心逻辑:
// 执行JNI调用
void JniBridge(mirror::ArtMethod* method, uint32_t* args, JValue* result) {
Thread* self = Thread::Current();
JNIEnvExt* env = self->GetJniEnv();
// 保存当前Java栈状态
StackHandleScope<1> hs(self);
Handle<mirror::Object> receiver(hs.NewHandle(
reinterpret_cast<mirror::Object*>(args[0])));
// 创建JNI调用栈帧
JniTransition jni_transition(self);
// 获取本地方法指针
void* native_method = method->GetEntryPointFromQuickCompiledCode();
// 准备调用参数
uint8_t* native_args = jni_transition.GetNativeArgumentBuffer();
SetupArgs(method, native_args, args);
// 调用本地方法
typedef void (*NativeMethod)(JNIEnv*, jobject, ...);
NativeMethod native_fn = reinterpret_cast<NativeMethod>(native_method);
// 根据返回类型调用不同的函数
switch (method->GetReturnType()) {
case Primitive::kPrimVoid:
native_fn(env, receiver.Get(), native_args);
result->SetVoid();
break;
case Primitive::kPrimBoolean:
result->SetZ(native_fn(env, receiver.Get(), native_args));
break;
case Primitive::kPrimByte:
result->SetB(native_fn(env, receiver.Get(), native_args));
break;
//...其他返回类型处理
}
// 恢复Java栈状态
jni_transition.Finish();
}
5.3 调用约定适配
不同架构有不同的调用约定,ART需要进行适配。在art/runtime/arch目录下,为不同架构提供了特定的实现:
// ARM架构的JNI调用桥接函数
void art_quick_generic_jni_trampoline(uint32_t* args, JValue* result, size_t args_size) {
// 获取方法和JNI环境
mirror::ArtMethod* method = reinterpret_cast<mirror::ArtMethod*>(args[0]);
Thread* self = Thread::Current();
JNIEnvExt* env = self->GetJniEnv();
// 提取参数
uint8_t* native_args = &args[1];
// 获取本地方法指针
void* native_method = method->GetEntryPointFromQuickCompiledCode();
// 调用本地方法
typedef void (*NativeMethod)(JNIEnv*, jobject, ...);
NativeMethod native_fn = reinterpret_cast<NativeMethod>(native_method);
// 根据返回类型调用不同的函数
//...
}
六、本地方法执行
6.1 本地方法入口
本地方法通过JNI函数表提供的接口访问Java虚拟机功能。在art/runtime/jni_env_ext.cc中,定义了JNI函数表的初始化:
// 初始化JNI函数表
void JNIEnvExt::InitFunctions() {
functions_ = &gNativeInterface;
// 设置JNI函数表的各个函数指针
functions_->GetVersion = &JNIEnvExt::GetVersion;
functions_->DefineClass = &JNIEnvExt::DefineClass;
functions_->FindClass = &JNIEnvExt::FindClass;
//...其他函数
}
6.2 JNI接口实现
JNI接口的实现在art/runtime/jni目录下。例如,FindClass函数的实现:
// 查找Java类
jclass JNIEnvExt::FindClass(const char* name) {
ScopedObjectAccess soa(self_);
// 将类名转换为内部格式
std::string descriptor = DescriptorFromDotName(name);
// 查找类
mirror::Class* result = nullptr;
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
result = class_linker->FindClass(soa.Self(), descriptor.c_str(), nullptr);
// 创建局部引用并返回
return soa.AddLocalReference<jclass>(result);
}
6.3 本地方法调用Java代码
本地方法可以通过JNI接口调用Java代码。在art/runtime/jni_env_ext.cc中,定义了这些调用的实现:
// 调用Java方法
jint JNIEnvExt::CallIntMethod(jobject obj, jmethodID methodID, ...) {
va_list args;
va_start(args, methodID);
jint result = CallIntMethodV(obj, methodID, args);
va_end(args);
return result;
}
// 调用Java静态方法
jint JNIEnvExt::CallStaticIntMethod(jclass clazz, jmethodID methodID, ...) {
va_list args;
va_start(args, methodID);
jint result = CallStaticIntMethodV(clazz, methodID, args);
va_end(args);
return result;
}
七、返回值处理
7.1 基本数据类型返回
基本数据类型的返回值处理相对简单,直接从本地方法返回并转换为Java类型。在art/runtime/jni_bridge.cc中,定义了返回值处理逻辑:
// 处理基本数据类型返回值
void HandlePrimitiveReturn(mirror::ArtMethod* method, JValue* result, uint32_t* args) {
switch (method->GetReturnType()) {
case Primitive::kPrimBoolean:
args[0] = result->GetZ();
break;
case Primitive::kPrimByte:
args[0] = result->GetB();
break;
case Primitive::kPrimChar:
args[0] = result->GetC();
break;
case Primitive::kPrimShort:
args[0] = result->GetS();
break;
case Primitive::kPrimInt:
args[0] = result->GetI();
break;
case Primitive::kPrimLong:
// 长整型需要特殊处理,因为它占用两个寄存器
*reinterpret_cast<int64_t*>(args) = result->GetJ();
break;
case Primitive::kPrimFloat:
args[0] = bit_cast<uint32_t>(result->GetF());
break;
case Primitive::kPrimDouble:
// 双精度浮点型需要特殊处理
*reinterpret_cast<double*>(args) = result->GetD();
break;
case Primitive::kPrimVoid:
// 无返回值
break;
default:
LOG(FATAL) << "Invalid return type: " << method->GetReturnType();
break;
}
}
7.2 对象引用返回
对象引用返回时,需要将本地引用转换为Java对象引用。在art/runtime/jni_bridge.cc中,定义了对象返回值的处理逻辑:
// 处理对象返回值
void HandleObjectReturn(mirror::ArtMethod* method, JValue* result, uint32_t* args) {
jobject obj = result->GetL();
if (obj == nullptr) {
args[0] = 0; // null引用
return;
}
// 将JNI引用转换为Java对象
Thread* self = Thread::Current();
JNIEnvExt* env = self->GetJniEnv();
mirror::Object* java_obj = env->DecodeLocalRef(obj);
// 设置返回值
args[0] = reinterpret_cast<uintptr_t>(java_obj);
}
八、异常处理
8.1 异常传递机制
当本地方法抛出异常时,需要将异常传递回Java层。在art/runtime/jni_env_ext.cc中,定义了异常处理的核心逻辑:
// 抛出异常
jint JNIEnvExt::Throw(jthrowable obj) {
if (obj == nullptr) {
return JNI_ERR;
}
Thread* self = Thread::Current();
// 检查是否已有挂起的异常
if (self->IsExceptionPending()) {
return JNI_EDETACHED;
}
// 设置挂起的异常
self->SetPendingException(Decode<mirror::Throwable*>(obj));
return JNI_OK;
}
// 检查是否有异常挂起
jint JNIEnvExt::ExceptionCheck() {
Thread* self = Thread::Current();
return self->IsExceptionPending() ? JNI_TRUE : JNI_FALSE;
}
// 获取挂起的异常
jthrowable JNIEnvExt::ExceptionOccurred() {
Thread* self = Thread::Current();
mirror::Throwable* exception = self->GetException();
if (exception == nullptr) {
return nullptr;
}
// 创建异常的局部引用
return AddLocalReference<jthrowable>(exception);
}
8.2 异常处理流程
在JNI调用返回时,需要检查是否有异常挂起。在art/runtime/jni_bridge.cc中,定义了异常检查的逻辑:
// JNI调用后检查异常
bool CheckExceptionAfterJniCall(Thread* self) {
if (self->IsExceptionPending()) {
// 有异常挂起,清除JNI局部引用表
self->GetJniEnv()->PopLocalFrame(nullptr);
return true;
}
return false;
}
九、性能优化
8.1 快速JNI调用路径
ART提供了快速JNI调用路径,减少了传统JNI调用的开销。在art/runtime/quick目录下,定义了快速JNI调用的实现:
// 快速JNI调用桥接函数
extern "C" void art_quick_generic_jni_trampoline(uint32_t* args, JValue* result, size_t args_size) {
// 快速JNI调用实现,直接跳转到本地方法
// 减少了传统JNI调用的开销
//...
}
8.2 缓存与预取
为了提高性能,ART缓存了常用的JNI函数和类引用。在art/runtime/jni_env_ext.cc中,定义了缓存机制:
// 获取缓存的类引用
jclass JNIEnvExt::GetCachedClass(const char* descriptor) {
// 检查缓存
ClassCache* cache = self_->GetClassCache();
jclass result = cache->Get(descriptor);
if (result == nullptr) {
// 缓存未命中,查找类并添加到缓存
result = FindClass(descriptor);
if (result != nullptr) {
cache->Put(descriptor, result);
}
}
return result;
}
十、安全与限制
10.1 JNI访问限制
JNI提供了有限的Java虚拟机功能访问,确保本地代码不会破坏Java虚拟机的安全性。在art/runtime/jni目录下,实现了各种访问限制:
// 检查权限
bool JNIEnvExt::CheckAccessPermission(const char* class_name) {
// 检查是否有访问该类的权限
//...
}
10.2 引用管理规则
JNI引用管理有严格的规则,本地代码必须遵守这些规则以避免内存泄漏。在art/runtime/jni_local_ref_table.cc中,实现了引用管理的核心逻辑:
// 确保引用在作用域内有效
bool JNIEnvExt::EnsureReferenceValid(jobject ref) {
// 检查引用是否有效
//...
}
十一、未来发展趋势
11.1 更高效的调用机制
未来可能会引入更高效的Java到Native调用机制,进一步减少调用开销。例如,利用硬件特性优化栈帧转换和参数传递。
11.2 与新编程语言的集成
随着新编程语言在Android开发中的普及,Java到Native的调用机制可能需要适应这些新语言的特性。例如,支持Kotlin的协程和值类。
11.3 安全增强
未来的调用机制可能会加强安全检查,防止恶意本地代码破坏Java虚拟机的安全性。例如,引入更严格的权限检查和内存访问控制。