前言
本文介绍System.loadLibrary原理。
Java层源码流程
System#loadLibrary
-> Runtime#loadLibrary0
–> BaseDexClassLoader#findLibrary
–> DexPathList#findNativeLibrary
->Runtime#nativeLoad,走向c层源码
- 主要是从
nativeLibraryPathElements
中找到so路径,遍历所有路径找到存在的so,不存在则抛出UnsatisfiedLinkError
。在c层进行打开so逻辑,如果c层返回false,就会拼接error字符串,Java层收到后也会抛出UnsatisfiedLinkError
。
- 可以通过反射打印下nativeLibraryPathElements,以更好的理解这块代码
Field pathListF = BaseDexClassLoader.class.getDeclaredField("pathList");
pathListF.setAccessible(true);
ClassLoader dexPathClassLoader = TestSocketFragment.class.getClassLoader();
Object pathList = pathListF.get(dexPathClassLoader);
Field nativeLibraryPathElementsF = Class.forName("dalvik.system.DexPathList").getDeclaredField("nativeLibraryPathElements");
nativeLibraryPathElementsF.setAccessible(true);
Object nativeLibraryPathElements = nativeLibraryPathElementsF.get(pathList);
android.util.Log.e("mLogU", nativeLibraryPathElements.toString());
Object[] arr = (Object[]) nativeLibraryPathElements;
for (Object o : arr) {
android.util.Log.e("mLogU", o.toString());
}
native层源码流程
从c层加载so,返回false就会在Java层抛出UnsatisfiedLinkError
,流程如下:
-> nativeLoad
->libcore/ojluni/src/main/native/Runtime.c#Runtime_nativeLoad
->art/openjdkjvm/OpenjdkJvm.cc#JVM_NativeLoad
->art/runtime/jni/java_vm_ext.cc#LoadNativeLibrary
–>art/libnativeloader/native_loader.cpp#OpenNativeLibrary
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
std::string* error_msg) {
error_msg->clear();
/*
* 1. 从缓存中获取该library,
* 2. 如果存在则比较classLoader是否相同,如果相同则返回成功。不同则返回失败。
* 3. 不存在则进行加载,并放入缓存中
*/
SharedLibrary* library;
Thread* self = Thread::Current();
{
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);
}
void* class_loader_allocator = nullptr;
{
ScopedObjectAccess soa(env);
ObjPtr<mirror::ClassLoader> loader = soa.Decode<mirror::ClassLoader>(class_loader);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
if (class_linker->IsBootClassLoader(soa, loader.Ptr())) {
loader = nullptr;
class_loader = nullptr;
}
// 获取当前classloader的Allocator
class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader.Ptr());
CHECK(class_loader_allocator != nullptr);
}
/*
* The JNI spec says we can't load the same library into more than one class loader.
* JNI规范要求不能加载相同库到同一个classLoader中
*/
if (library != nullptr) {
// Use the allocator pointers for class loader equality to avoid unnecessary weak root decode.
if (library->GetClassLoaderAllocator() != class_loader_allocator) {
// .... 拼接一大堆str
return false;
}
// 已经加载过了,问题不大
if (!library->CheckOnLoadResult()) {
return false;
}
return true;
}
// dlopen该库,并返回句柄(指针)
void* handle = android::OpenNativeLibrary(env,
runtime_->GetTargetSdkVersion(),
path_str,
class_loader,
library_path.get(),
&needs_native_bridge,
error_msg);
if (handle == nullptr) {
// 打开失败
return false;
}
bool created_library = false;
{
std::unique_ptr<SharedLibrary> new_library(
new SharedLibrary(env,
self,
path,
handle,
needs_native_bridge,
class_loader,
class_loader_allocator));
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);
if (library == nullptr) { // We won race to get libraries_lock.
library = new_library.release();
// 放入缓存
libraries_->Put(path, library);
created_library = true;
}
}
if (!created_library) {
return library->CheckOnLoadResult();
}
/*
* 加载成功,dlsym获取JNI_OnLoad句柄,并调用JNI_OnLoad方法,根据返回值判断设置是否加载成功
*/
bool was_successful = false;
void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
if (sym == nullptr) {
was_successful = true;
} else {
JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
int version = (*jni_on_load)(this, nullptr);
if (version == JNI_ERR) {
StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
} else if (JavaVMExt::IsBadJniVersion(version)) {
StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
path.c_str(), version);
} else {
was_successful = true;
}
}
library->SetResult(was_successful);
return was_successful;
}
结语
从源码可以看出System#loadLibrary在正常情况下只会调用一次JNI_OnLoad
方法;其中SharedLibrary在析构函数里会调用dlclose关闭该库;并且从官方文档JNI tips可以看出一般放在静态代码块里加载so,该so生命周期绑定class生命周期。
本文梳理了System#loadLibrary源码流程,若有错误敬请指正。