Android Class loader介绍

在 Android 中,这是一个非常经典且常见的 JNI 问题。核心原因在于 类加载器(ClassLoader)的差异 以及 JNI 如何查找类 的机制。

以下是详细的机制分析:

1. 核心原因:ClassLoader 查找机制

在 Java 中,类的加载是由 ClassLoader 完成的。Android 中主要有两种 ClassLoader:

  • **System ClassLoader (BootClassLoader)**:负责加载 Android 系统核心库(如 java.lang.*, android.os.* 等)。
  • **Application ClassLoader (PathClassLoader)**:负责加载你的 APK 里的代码(包括你的自定义类 com.didi...)。

FindClass 的逻辑:
当你调用 env->FindClass 时,它会去寻找合适的 ClassLoader:

  1. 如果当前线程的调用栈里有 Java 方法,JNI 会使用调用该 Native 方法的那个 Java 类的 ClassLoader
  2. 关键点: 如果你在 C++ 中自行创建了一个线程(如 pthread_createstd::thread),这个线程的调用栈里没有任何 Java 帧。
  3. 当你通过 AttachCurrentThread 将该线程挂载到 JVM 时,JNI 默认会使用 System ClassLoader

结论: System ClassLoader 只能找到系统类,它根本不知道你的 APK 里定义的 com.didi.hawaii.mapsdkv2.jni.ApolloJni 在哪里,所以会报 ClassNotFoundException


2. 为什么在主线程(或 Java 调用的线程)没问题?

当 Java 代码调用一个 native 方法时,JVM 知道是哪个类发起的调用。此时 FindClass 会自动使用这个类的 PathClassLoader,它能看到 APK 内部的所有类。


3. 具体的运行机制流程

  1. C++ 创建线程pthread_create -> 产生一个纯底层的 Linux 线程。
  2. 挂载 JVM:调用 JavaVM->AttachCurrentThread。此时 JVM 为该线程分配了一个 JNIEnv,但该线程的上下文是“空”的。
  3. 调用 FindClassFindClass 检查调用栈,发现没有 Java 方法。
  4. 回退机制:JNI 引擎回退到 System ClassLoader
  5. 查找失败System ClassLoader 在系统的 boot.artsystem/framework 下找不到 com.didi...,返回 NULL 并抛出异常。

4. 解决方案

要解决这个问题,通常有以下几种标准做法:

方法 A:在 JNI_OnLoad 中缓存全局引用(推荐)

JNI_OnLoad 会在库被加载时(通常在主线程)自动执行。此时的 ClassLoader 是正确的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jclass g_apollo_class = nullptr;

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}

// 此时在主线程环境,能找到自定义类
jclass localClass = env->FindClass("com/didi/hawaii/mapsdkv2/jni/ApolloJni");
// 必须创建全局引用,否则 localClass 退出函数就失效了
g_apollo_class = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));

return JNI_VERSION_1_6;
}

// 之后在任何线程直接使用 g_apollo_class 即可

方法 B:缓存 Application ClassLoader

JNI_OnLoad 里手动获取并保存 Application 的 ClassLoader 对象,在子线程中通过调用 Java 的 loadClass 方法来查找类。这种方法比较复杂,一般用于插件化或特殊框架。

方法 C:由 Java 层将 Class 传进来

如果你的 native 线程是由 Java 触发的,可以在 Java 层先获取 Class 对象,通过参数传给底层。


总结

之所以找不到,是因为 C++ 异步线程丢失了 Application ClassLoader 的上下文。默认的 System ClassLoader 搜索范围不包括你的 APK。最好的实践是在 JNI_OnLoad 中预先查找类并保存为 NewGlobalRef