在native创建线程,想调用java层时,通常会去获取到java层class,如下代码:
1 | ini复制代码// native线程 |
但是却发生了如下崩溃,报Didn’t find class “com.hyc.jni_demo.NativeCall” ,下面我们来探究一下出现这个问题的原因。
1 | 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]] |
在这之前我们还需要了解一下什么是JavaVM和JNIEnv。
JavaVM和JNIEnv
JNIEnv可以单纯的理解为java层和native层之间的桥梁,每个java线程都有一个自己的JNIEnv。而JavaVM是管理JNIEnv的,它它可以创建新的JNIEnv,获取当前线程的JNIEnv,以及销毁JNIEnv。
JavaVM创建
JavaVM在虚拟机里面只有一个实例,JavaVM在虚拟机启动的时候创建。
1 | arduino复制代码// 调用栈 |
SystemClassLoader的创建
在创建JavaVM后,会创建SystemClassLoader,并设置给JavaVM,这个就是在native线程,我们能拿到的默认ClassLoader。
1 | scss复制代码// 调用栈 |
java层逻辑,根据启动jvm传入参数创建PathClassLoader。
1 | typescript复制代码 public static ClassLoader getSystemClassLoader() { |
查找class
在查找Class时,先去获取当前的ClassLoader:
查找当前调用的java方法,如果能拿到方法,那么返回方法所在的Class的ClassLoader,如果为空,那么使用虚拟机创建时创建的SystemClassLoader。此时是native线程刚绑定jvm虚拟机,所以方法为空,返回SystemClassLoader。
而这个ClassLoader时在Zygote进程创建时,传入虚拟机配置参数路径创建的PathClassLoader,只会包含系统相关路径,不会有上层App的dex,所以我们就不能通过这个ClassLoader获取到我们自己的Class,理所当然出现上面那个崩溃。
1 | scss复制代码 static jclass FindClass(JNIEnv* env, const char* name) { |
结合上面的代码,我们验证一下加载系统的Class是否可行,发现时可以的,没有报错。
1 | ini复制代码void ThreadTest::callJava(void *data) { |
如何解决?
知道出现的原因后就很好解决了,我们不能在一个线程调用另一个线程的JNIEnv,所以就不能缓存有正确ClassLoader的JNIEnv,然后调用其FindClass方法。我们在java线程中初始化时就去获取出相应的jclass,进行缓存。这种方法不是很通用,我们可以在正确的线程下对ClassLoader进行缓存,然后再在另一个线程调用这个ClassLoader的loadClass方法。
1 | ini复制代码extern "C" |
本文转载自: 掘金