Android Native线程找不到class的原因

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ini复制代码// 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” ,下面我们来探究一下出现这个问题的原因。

1
2
3
4
typescript复制代码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在虚拟机启动的时候创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
arduino复制代码// 调用栈
// 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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
scss复制代码// 调用栈
// 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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typescript复制代码    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,理所当然出现上面那个崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
scss复制代码  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是否可行,发现时可以的,没有报错。

1
2
3
4
5
6
7
8
9
10
11
12
ini复制代码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方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
ini复制代码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();

}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%