Android Native线程找不到class的原因

在native创建线程,想调用java层时,通常会去获取到java层class,如下代码:

// native线程
void ThreadTest::callJava(void *data) {
    ThreadTest *threadTest = (ThreadTest *) data;
    JNIEnv *env= nullptr;
    threadTest->vm->AttachCurrentThread(&env, nullptr);
    // 查找class
    jclass clazz = env->FindClass("com/hyc/jni_demo/NativeCall");
                                          className);
    jmethodID methodId = env->GetStaticMethodID(clazz, "callStatic", "()I");
    jint result = env->CallStaticIntMethod(clazz, methodId);
    LOGD("result1: %d", result);
    jmethodID methodId2 = env->GetMethodID(clazz, "callNormal", "()I");
​
    jfieldID field = env->GetStaticFieldID(clazz, "INSTANCE", "Lcom/hyc/jni_demo/NativeCall;");
    jobject nativeCall = env->GetStaticObjectField(clazz, field);
    jint result2 = env->CallIntMethod(nativeCall, methodId2, nullptr);
    LOGD("result2: %d", result2);
    threadTest->vm->DetachCurrentThread();
​
}

但是却发生了如下崩溃,报Didn’t find class “com.hyc.jni_demo.NativeCall” ,下面我们来探究一下出现这个问题的原因。

Abort message: 'JNI DETECTED ERROR IN APPLICATION: JNI GetStaticMethodID called with pending exception java.lang.ClassNotFoundException: Didn't find class "com.hyc.jni_demo.NativeCall" on path: DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /system/lib64, /system_ext/lib64]]
                                                                                                      at java.lang.Class dalvik.system.BaseDexClassLoader.findClass(java.lang.String) (BaseDexClassLoader.java:259)
                                                                                                      at java.lang.Class java.lang.ClassLoader.loadClass(java.lang.String, boolean) (ClassLoader.java:379)
                                                                                                      at java.lang.Class java.lang.ClassLoader.loadClass(java.lang.String) (ClassLoader.java:312)

在这之前我们还需要了解一下什么是JavaVM和JNIEnv。

JavaVM和JNIEnv

JNIEnv可以单纯的理解为java层和native层之间的桥梁,每个java线程都有一个自己的JNIEnv。而JavaVM是管理JNIEnv的,它它可以创建新的JNIEnv,获取当前线程的JNIEnv,以及销毁JNIEnv。

JavaVM创建

JavaVM在虚拟机里面只有一个实例,JavaVM在虚拟机启动的时候创建。

// 调用栈
// art/runtime/runtime.cc Runtime::Init
// art/runtime/runtime.cc Runtime::Create
// art/runtime/jni/java_vm_ext.cc JNI_CreateJavaVM
// frameworks/base/core/jni/AndroidRuntime.cpp AndroidRuntime::startVm
// frameworks/base/core/jni/AndroidRuntime.cpp AndroidRuntime::start
// frameworks/base/cmds/app_process/app_main.cpp main
​
​
bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {
  ........
  java_vm_ = JavaVMExt::Create(this, runtime_options, &error_msg);
  ........  
}
​
std::unique_ptr<JavaVMExt> JavaVMExt::Create(Runtime* runtime,
                                             const RuntimeArgumentMap& runtime_options,
                                             std::string* error_msg) NO_THREAD_SAFETY_ANALYSIS {
  std::unique_ptr<JavaVMExt> java_vm(new JavaVMExt(runtime, runtime_options, error_msg));
  if (java_vm && java_vm->globals_.IsValid() && java_vm->weak_globals_.IsValid()) {
    return java_vm;
  }
  return nullptr;
}
​
JavaVMExt::JavaVMExt(Runtime* runtime,
                     const RuntimeArgumentMap& runtime_options,
                     std::string* error_msg)
    : runtime_(runtime),
      ..................
      // 配置接口    
      unchecked_functions_(&gJniInvokeInterface),
      ..................
      old_allocation_tracking_state_(false) {
  functions = unchecked_functions_;
  SetCheckJniEnabled(runtime_options.Exists(RuntimeArgumentMap::CheckJni) || kIsDebugBuild);
}
​
const JNIInvokeInterface gJniInvokeInterface = {
  nullptr,  // reserved0
  nullptr,  // reserved1
  nullptr,  // reserved2
  JII::DestroyJavaVM,
  JII::AttachCurrentThread,
  JII::DetachCurrentThread,
  JII::GetEnv,
  JII::AttachCurrentThreadAsDaemon
};
​
// App层可以调用到的接口
struct _JavaVM {
    const struct JNIInvokeInterface* functions;
​
#if defined(__cplusplus)
    jint DestroyJavaVM()
    { return functions->DestroyJavaVM(this); }
    jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThread(this, p_env, thr_args); }
    jint DetachCurrentThread()
    { return functions->DetachCurrentThread(this); }
    jint GetEnv(void** env, jint version)
    { return functions->GetEnv(this, env, version); }
    jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};

SystemClassLoader的创建

在创建JavaVM后,会创建SystemClassLoader,并设置给JavaVM,这个就是在native线程,我们能拿到的默认ClassLoader。

// 调用栈
// art/runtime/runtime.cc Runtime::Start
// art/runtime/jni/java_vm_ext.cc JNI_CreateJavaVM
// frameworks/base/core/jni/AndroidRuntime.cpp AndroidRuntime::startVm
// frameworks/base/core/jni/AndroidRuntime.cpp AndroidRuntime::start
// frameworks/base/cmds/app_process/app_main.cpp main
​
bool Runtime::Start() {
    .............
    system_class_loader_ = CreateSystemClassLoader(this);
    .............
}
​
​
static jobject CreateSystemClassLoader(Runtime* runtime) {
  if (runtime->IsAotCompiler() && !runtime->GetCompilerCallbacks()->IsBootImage()) {
    return nullptr;
  }
​
  ScopedObjectAccess soa(Thread::Current());
  ClassLinker* cl = Runtime::Current()->GetClassLinker();
  auto pointer_size = cl->GetImagePointerSize();
​
  StackHandleScope<2> hs(soa.Self());
  Handle<mirror::Class> class_loader_class(
      hs.NewHandle(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_ClassLoader)));
  CHECK(cl->EnsureInitialized(soa.Self(), class_loader_class, true, true));
  // 获取到ClassLoader的getSystemClassLoader方法
  ArtMethod* getSystemClassLoader = class_loader_class->FindClassMethod(
      "getSystemClassLoader", "()Ljava/lang/ClassLoader;", pointer_size);
  CHECK(getSystemClassLoader != nullptr);
  CHECK(getSystemClassLoader->IsStatic());
  // 执行getSystemClassLoader方法
  JValue result = InvokeWithJValues(soa,
                                    nullptr,
                                    getSystemClassLoader,
                                    nullptr);
  JNIEnv* env = soa.Self()->GetJniEnv();
  // 获取到local Ref
  ScopedLocalRef<jobject> system_class_loader(env, soa.AddLocalReference<jobject>(result.GetL()));
  CHECK(system_class_loader.get() != nullptr);
  // 保存
  soa.Self()->SetClassLoaderOverride(system_class_loader.get());
​
  Handle<mirror::Class> thread_class(
      hs.NewHandle(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_Thread)));
  CHECK(cl->EnsureInitialized(soa.Self(), thread_class, true, true));
​
  ArtField* contextClassLoader =
      thread_class->FindDeclaredInstanceField("contextClassLoader", "Ljava/lang/ClassLoader;");
  CHECK(contextClassLoader != nullptr);
​
  // We can't run in a transaction yet.
  contextClassLoader->SetObject<false>(
      soa.Self()->GetPeer(),
      soa.Decode<mirror::ClassLoader>(system_class_loader.get()).Ptr());
  // 返回global ref
  return env->NewGlobalRef(system_class_loader.get());
}
​
​

java层逻辑,根据启动jvm传入参数创建PathClassLoader。

    public static ClassLoader getSystemClassLoader() {
        return SystemClassLoader.loader;
    }
    static private class SystemClassLoader {
        public static ClassLoader loader = ClassLoader.createSystemClassLoader();
    }
​
    private static ClassLoader createSystemClassLoader() {
        String classPath = System.getProperty("java.class.path", ".");
        String librarySearchPath = System.getProperty("java.library.path", "");
​
        // String[] paths = classPath.split(":");
        // URL[] urls = new URL[paths.length];
        // for (int i = 0; i < paths.length; i++) {
        // try {
        // urls[i] = new URL("file://" + paths[i]);
        // }
        // catch (Exception ex) {
        // ex.printStackTrace();
        // }
        // }
        //
        // return new java.net.URLClassLoader(urls, null);
​
        // TODO Make this a java.net.URLClassLoader once we have those?
        return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
    }

查找class

在查找Class时,先去获取当前的ClassLoader:

查找当前调用的java方法,如果能拿到方法,那么返回方法所在的Class的ClassLoader,如果为空,那么使用虚拟机创建时创建的SystemClassLoader。此时是native线程刚绑定jvm虚拟机,所以方法为空,返回SystemClassLoader。

而这个ClassLoader时在Zygote进程创建时,传入虚拟机配置参数路径创建的PathClassLoader,只会包含系统相关路径,不会有上层App的dex,所以我们就不能通过这个ClassLoader获取到我们自己的Class,理所当然出现上面那个崩溃。

  static jclass FindClass(JNIEnv* env, const char* name) {
    CHECK_NON_NULL_ARGUMENT(name);
    Runtime* runtime = Runtime::Current();
    ClassLinker* class_linker = runtime->GetClassLinker();
    std::string descriptor(NormalizeJniClassDescriptor(name));
    ScopedObjectAccess soa(env);
    ObjPtr<mirror::Class> c = nullptr;
    if (runtime->IsStarted()) {
      StackHandleScope<1> hs(soa.Self());
      // 查找当前class loader  
      Handle<mirror::ClassLoader> class_loader(hs.NewHandle(GetClassLoader<kEnableIndexIds>(soa)));
      c = class_linker->FindClass(soa.Self(), descriptor.c_str(), class_loader);
    } else {
      c = class_linker->FindSystemClass(soa.Self(), descriptor.c_str());
    }
    return soa.AddLocalReference<jclass>(c);
  }
​
template<bool kEnableIndexIds>
static ObjPtr<mirror::ClassLoader> GetClassLoader(const ScopedObjectAccess& soa)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  // 查找当前调用的java方法,此时是native线程刚绑定jvm虚拟机,所以为空
  ArtMethod* method = soa.Self()->GetCurrentMethod(nullptr);
  // If we are running Runtime.nativeLoad, use the overriding ClassLoader it set.
  if (method ==
      jni::DecodeArtMethod<kEnableIndexIds>(WellKnownClasses::java_lang_Runtime_nativeLoad)) {
    return soa.Decode<mirror::ClassLoader>(soa.Self()->GetClassLoaderOverride());
  }
  // If we have a method, use its ClassLoader for context.
  // 如果不为空,那么获取方法所在的class的class loader  
  if (method != nullptr) {
    return method->GetDeclaringClass()->GetClassLoader();
  }
  // 如果为空,那么获取SystemClassLoader
  ObjPtr<mirror::ClassLoader> class_loader =
      soa.Decode<mirror::ClassLoader>(Runtime::Current()->GetSystemClassLoader());
  if (class_loader != nullptr) {
    return class_loader;
  }
  // See if the override ClassLoader is set for gtests.
  class_loader = soa.Decode<mirror::ClassLoader>(soa.Self()->GetClassLoaderOverride());
  if (class_loader != nullptr) {
    // If so, CommonCompilerTest should have marked the runtime as a compiler not compiling an
    // image.
    CHECK(Runtime::Current()->IsAotCompiler());
    CHECK(!Runtime::Current()->IsCompilingBootImage());
    return class_loader;
  }
  // Use the BOOTCLASSPATH.
  return nullptr;
}
​

结合上面的代码,我们验证一下加载系统的Class是否可行,发现时可以的,没有报错。

void ThreadTest::callJava(void *data) {
    ThreadTest *threadTest = (ThreadTest *) data;
    JNIEnv *env= nullptr;
    threadTest->vm->AttachCurrentThread(&env, nullptr);
    jclass clazz = env->FindClass("java/lang/Object");
    jmethodID methodId = env->GetMethodID(clazz, "<init>", "()V");
    jobject obj = env->NewObject(clazz, methodId);
    jmethodID methodId2 = env->GetMethodID(clazz, "toString", "()Ljava/lang/String;");
    jstring result = (jstring)env->CallObjectMethod(obj, methodId2);
    const char *str = env->GetStringUTFChars(result, nullptr);
}
// 打印 result: java.lang.Object@e7b9a76

如何解决?

知道出现的原因后就很好解决了,我们不能在一个线程调用另一个线程的JNIEnv,所以就不能缓存有正确ClassLoader的JNIEnv,然后调用其FindClass方法。我们在java线程中初始化时就去获取出相应的jclass,进行缓存。这种方法不是很通用,我们可以在正确的线程下对ClassLoader进行缓存,然后再在另一个线程调用这个ClassLoader的loadClass方法。

extern "C"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    // 此时运行在一个java线程中(真正绑定了jvm环境的线程),其ClassLoader是调用loadLibrary所在的Class对应的ClassLoader,在这里就是加载MainActivity的ClassLoader
    JNIEnv* env = nullptr;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }
    threadTest = new ThreadTest(vm);
    threadTest->initClassLoader(env);
    return JNI_VERSION_1_6;
}
​
void ThreadTest::initClassLoader(JNIEnv *env) {
    jclass clazz = env->FindClass("com/hyc/jni_demo/TestClassLoader");
    jmethodID methodId = env->GetStaticMethodID(clazz, "getClassLoader", "()Ljava/lang/ClassLoader;");
    jobject loader = env->CallStaticObjectMethod(clazz, methodId);
    classLoader = env->NewGlobalRef(loader);
}
​
void ThreadTest::callJava(void *data) {
    ThreadTest *threadTest = (ThreadTest *) data;
    JNIEnv *env= nullptr;
    threadTest->vm->AttachCurrentThread(&env, nullptr);
​
    jclass classLoaderClass = env->GetObjectClass(threadTest->classLoader);
    jstring className = env->NewStringUTF("com.hyc.jni_demo.NativeCall");
    jmethodID loadClassMethod = env->GetMethodID(classLoaderClass, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
    jclass clazz = (jclass)env->CallObjectMethod(threadTest->classLoader, loadClassMethod,
                                                 className);
    jmethodID methodId = env->GetStaticMethodID(clazz, "callStatic", "()I");
    jint result = env->CallStaticIntMethod(clazz, methodId);
    LOGD("result1: %d", result);
    jmethodID methodId2 = env->GetMethodID(clazz, "callNormal", "()I");
​
    jfieldID field = env->GetStaticFieldID(clazz, "INSTANCE", "Lcom/hyc/jni_demo/NativeCall;");
    jobject nativeCall = env->GetStaticObjectField(clazz, field);
    jint result2 = env->CallIntMethod(nativeCall, methodId2, nullptr);
    LOGD("result2: %d", result2);
    threadTest->vm->DetachCurrentThread();
​
}

如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料


在这里插入图片描述
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值