JNI原理 System.loadLibrary源码分析

前言

本文介绍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
    JNI原理 System.loadLibrary源码分析JNI原理 System.loadLibrary源码分析
  • 可以通过反射打印下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源码流程,若有错误敬请指正。

上一篇:Android:链接NDK中jar文件中的预建共享库(.so)


下一篇:Codeforces Global Round 17