在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进阶资料
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )