Android Runtime Java到Native调用转换原理(68)

188 阅读11分钟

Android Runtime Java到Native调用转换原理

一、调用转换概述

在Android Runtime(ART)中,Java到Native的调用转换是实现Java与C/C++代码交互的核心机制。这种跨语言调用涉及多个复杂步骤,包括方法查找、参数转换、JNI环境准备、栈帧切换和返回值处理等。理解这一过程对于开发高性能JNI应用和深入理解Android系统架构至关重要。

从源码角度来看,调用转换涉及art/runtime目录下的多个核心模块。jni_env.ccjni_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.ccart/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虚拟机的安全性。例如,引入更严格的权限检查和内存访问控制。