最近在做项目的时候遇到了一个类找不到的问题,相对于之前的解决方案还是有些不同,这里对问题进行记录
异常的检测和打印:
c++中Native代码调用JNI的时候如果产生了异常不会展开原生堆栈:
所以在c++JNI调用的时候构造一个FindClass时找不到类的异常,我们看到的实际的崩溃堆栈会是下面的样子,看不到c++层代码的调用链路:
在JNI调用的时候如果遇到了异常,不会立即抛出异常,当代码执行到受管理的代码时会抛出异常。
什么是受管理的代码:使用 Java 或 Kotlin 编程语言编写的代码
在进行异常检测的时候,我们可以使用ExceptionCheck或ExceptionOccurred方法,前者返回的是一个bool值,后者返回的是jthrowable,并没有直接获取jthrowable中异常message的方法,如果需要获取的话需要通过反射,自己在实践的时候使用下面的方法较为方便:
if (env->ExceptionCheck()) {
// 出现了异常,打印异常堆栈
env->ExceptionDescribe();
}
ClassNotFound
在进行JNI开发的时候,可能会遇到的一类问题就是FindClass找不到类,这里我们分两类讨论:
- 类被混淆,类名写错:这种自行检查,或者通过工具反编译查看类名称即可解决。
- 类找不到的时候查看崩溃的寻找类的path:例如下面的截图,path是 . ,表示当前是在系统类加载器中寻找我们要用的app中的类,这样的话肯定找不到。
解决方案(前三点参考官方给的解决方案):
- 在 JNI_OnLoad 中执行一次 FindClass 查找,然后缓存类引用以供日后使用。在执行 JNI_OnLoad 过程中发出的任何 FindClass 调用都会使用与调用 System.loadLibrary 的函数关联的类加载器(这是一条特殊规则,用于更方便地进行库初始化)。如果您的应用代码要加载库,FindClass 会使用正确的类加载器。
- 通过声明原生方法来获取 Class 参数,然后传入 Foo.class,从而将类的实例传递给需要它的函数。
- 在某个便捷位置缓存对 ClassLoader 对象的引用,然后直接发出 loadClass 调用。这需要花费一些精力来完成。
- env->GetObjectClass(),通过对象去获取其对应的类,
为什么FindClass会走到系统类加载器呢?查看实现:
art/runtime/jni/jni_internal.cc
static ObjPtr<mirror::ClassLoader> GetClassLoader(const ScopedObjectAccess& soa)
REQUIRES_SHARED(Locks::mutator_lock_) {
ArtMethod* method = soa.Self()->GetCurrentMethod(nullptr);
// If we are running Runtime.nativeLoad, use the overriding ClassLoader it set.
if (method == jni::DecodeArtMethod(WellKnownClasses::java_lang_Runtime_nativeLoad)) {
return soa.Decode<mirror::ClassLoader>(soa.Self()->GetClassLoaderOverride());
}
// If we have a method, use its ClassLoader for context.
if (method != nullptr) {
return method->GetDeclaringClass()->GetClassLoader();
}
// We don't have a method, so try to use the system ClassLoader.
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;
}
ArtMethod* method = soa.Self()->GetCurrentMethod(nullptr);
GetClassLoader方法最开始,先调用了上面的方法去获取method,这个方法具体做了什么呢?查看源码如下
art/runtime/thread.cc
ArtMethod* Thread::GetCurrentMethod(uint32_t* dex_pc_out,
bool check_suspended,
bool abort_on_error) const {
// Note: this visitor may return with a method set, but dex_pc_ being DexFile:kDexNoIndex. This is
// so we don't abort in a special situation (thinlocked monitor) when dumping the Java
// stack.
ArtMethod* method = nullptr;
uint32_t dex_pc = dex::kDexNoIndex;
StackVisitor::WalkStack(
[&](const StackVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
ArtMethod* m = visitor->GetMethod();
if (m->IsRuntimeMethod()) {
// Continue if this is a runtime method.
return true;
}
method = m;
dex_pc = visitor->GetDexPc(abort_on_error);
return false;
},
const_cast<Thread*>(this),
/* context= */ nullptr,
StackVisitor::StackWalkKind::kIncludeInlinedFrames,
check_suspended);
if (dex_pc_out != nullptr) {
*dex_pc_out = dex_pc;
}
return method;
}
通过上面的注释可以看到,上面的代码遍历Java Stack,寻找stack中的Java方法栈帧。
GetCurrentMethod返回后,在GetClassLoader方法中判断:if (method != nullptr) { 判断是否有Java堆栈帧,没有的话就走到下面的GetSystemClassLoader得到系统的类加载器,因此就找不到app中的类,当我们使用从c++层的线程去执行FindClass的时候会遇到上述问题,大家可以参考上面的解决方案尝试解决。
参考:
https://blog.csdn.net/u013989732/article/details/80707607
https://developer.android.com/training/articles/perf-jni?hl=zh-cn#faq:-why-didnt-findclass-find-my-class
https://cs.android.com/android/platform/superproject/+/master:art/runtime/jni/jni_internal.cc;l=2310?q=jni_internal.cc&ss=android%2Fplatform%2Fsuperproject