Skip to content

Latest commit

 

History

History
316 lines (244 loc) · 11.6 KB

安卓 10 源码开发定制 (28)System.loadLibrary 流程分析.md

File metadata and controls

316 lines (244 loc) · 11.6 KB

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

一、前言

    在 Android App 开发中,如果涉及到 jni 开发,常常会使用 System.loadLibray 来加载生成的 so 文件。以下将通过安卓 10 的源码,追踪 System.loadLibrary 的内部流程。

**二、**System 类调用分析

     System 类源码路径如下:

libcore\ojluni\src\main\java\java\lang\System.java

  在该类中 loadLibrary 函数代码如下:

   public static void loadLibrary(String libname) {
        Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
    }

    有以上代码可分析到调用类转到了 Runtime 类中。

三、Runtime 类调用分析

      Runtime 类源码路径:

libcore\ojluni\src\main\java\java\lang\Runtime.java

     loadLibrary0 函数实现如下:

 //
 void loadLibrary0(Class<?> fromClass, String libname) {
        ClassLoader classLoader = ClassLoader.getClassLoader(fromClass);
        loadLibrary0(classLoader, fromClass, libname);
    }
private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {
        if (libname.indexOf((int)File.separatorChar) != -1) {
            throw new UnsatisfiedLinkError(
    "Directory separator should not appear in library name: " + libname);
        }
        String libraryName = libname;
        // Android-note: BootClassLoader doesn't implement findLibrary(). http://b/111850480
        // Android's class.getClassLoader() can return BootClassLoader where the RI would
        // have returned null; therefore we treat BootClassLoader the same as null here.
        if (loader != null && !(loader instanceof BootClassLoader)) {
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
                // It's not necessarily true that the ClassLoader used
                // System.mapLibraryName, but the default setup does, and it's
                // misleading to say we didn't find "libMyLibrary.so" when we
                // actually searched for "liblibMyLibrary.so.so".
                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                                               System.mapLibraryName(libraryName) + "\"");
            }
            String error = nativeLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }
        // We know some apps use mLibPaths directly, potentially assuming it's not null.
        // Initialize it here to make sure apps see a non-null value.
        getLibPaths();
        String filename = System.mapLibraryName(libraryName);
        String error = nativeLoad(filename, loader, callerClass);
        if (error != null) {
            throw new UnsatisfiedLinkError(error);
        }
    }

     不具体讨论函数中的详细逻辑流程,loadLibrary0so 未加载情况下会调用 nativeLoad 方法。nativeLoad 函数如下:

 private static String nativeLoad(String filename, ClassLoader loader) {
        return nativeLoad(filename, loader, null);
    }
private static native String nativeLoad(String filename, ClassLoader loader, Class<?> caller);

     最终调用的是 native 方法 nativeLoad,所以接下来需要找到该方法的 jni 层实现。Runtime.java 中的 jni 方法是由 Runtime.c 注册实现的。接下来分析 Runtime.c 中的流程。    

四、Runtime.c 中的流程分析

    ** Runtime.c** 源码路径位于:

libcore\ojluni\src\main\native\Runtime.c

    在该文件中 javanative 方法 nativeLoad 对应的 jni 层实现函数为 Runtime_nativeLoad, 函数实现如下:

JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
                   jobject javaLoader, jclass caller)
{
    return JVM_NativeLoad(env, javaFilename, javaLoader, caller);
}

     有以上代码逻辑中可分析调用了 JVM_NativeLoad 函数。通过强大的 xxgrep 搜索命令,搜到该方法被定义在 jvm.h 文件中, 实现文件在 OpenjdkJvm.cc 中。

jvm.h 文件路径:

libcore\ojluni\src\main\native\jvm.h

    OpenjdkJvm.cc 文件路径:

art/openjdkjvm/OpenjdkJvm.cc

     接下来分析 OpenjdkJvm 的实现。

五、OpenjdkJvm.cc 中的流程分析

    在 OpenjdkJvm.ccJVM_NativeLoad 的实现代码如下:

JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
                                 jstring javaFilename,
                                 jobject javaLoader,
                                 jclass caller) {
  ScopedUtfChars filename(env, javaFilename);
  if (filename.c_str() == nullptr) {
    return nullptr;
  }
  std::string error_msg;
  {
    art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
    bool success = vm->LoadNativeLibrary(env,
                                         filename.c_str(),
                                         javaLoader,
                                         caller,
                                         &error_msg);
    if (success) {
      return nullptr;
    }
  }
  // Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
  env->ExceptionClear();
  return env->NewStringUTF(error_msg.c_str());
}

      以上代码中核心调用变为了 JavaVMExt->LoadNativeLibrary

**六、**JavaVMExt 中的流程分析

  

      JavaVMExt.cc 源码路径位于:

art\runtime\jni\java_vm_ext.cc

      该文件中 LoadNativeLibrary 的函数实现如下:

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
                                  const std::string& path,
                                  jobject class_loader,
                                  jclass caller_class,
                                  std::string* error_msg) {
  //..。省略     
  //使用OpenNativeLibrary打开so库                           
  void* handle = android::OpenNativeLibrary(
      env,
      runtime_->GetTargetSdkVersion(),
      path_str,
      class_loader,
      (caller_location.empty() ? nullptr : caller_location.c_str()),
      library_path.get(),
      &needs_native_bridge,
      &nativeloader_error_msg);
  VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";
   //...省略
   //查找JNI_OnLoad方法,所以我们在jni开发中动态注册就需要重写这个方法是有道理额
  void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
  if (sym == nullptr) {
    VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
    was_successful = true;
  } else {
    // Call JNI_OnLoad.  We have to override the current class
    // loader, which will always be "null" since the stuff at the
    // top of the stack is around Runtime.loadLibrary().  (See
    // the comments in the JNI FindClass function.)
    ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
    self->SetClassLoaderOverride(class_loader);
    VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
    using JNI_OnLoadFn = int(*)(JavaVM*, void*);
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
    //主动去调用JNI_OnLoad方法
    int version = (*jni_on_load)(this, nullptr);
    //...省略
  }
   //...省略
  }

      以上方法中会打开 so 库并调用 JNI_OnLoad 方法。以上打开 so 使用了 "android::OpenNativeLibrary", 下面准备追踪一下该方法的内部实现机制。

七、OpenNativeLibrary 实现

       在源码中搜索,找到 OpenNativeLibrary 实现源码路径如下:

system\core\libnativeloader\native_loader.cpp

       该方法逻辑实现代码如下:

void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* path,
                        jobject class_loader, const char* caller_location, jstring library_path,
                        bool* needs_native_bridge, char** error_msg) {
#if defined(__ANDROID__)
  UNUSED(target_sdk_version);
  if (class_loader == nullptr) {
    *needs_native_bridge = false;
    if (caller_location != nullptr) {
      android_namespace_t* boot_namespace = FindExportedNamespace(caller_location);
      if (boot_namespace != nullptr) {
        const android_dlextinfo dlextinfo = {
            .flags = ANDROID_DLEXT_USE_NAMESPACE,
            .library_namespace = boot_namespace,
        };
        void* handle = android_dlopen_ext(path, RTLD_NOW, &dlextinfo);
        if (handle == nullptr) {
          *error_msg = strdup(dlerror());
        }
        return handle;
      }
    }
    void* handle = dlopen(path, RTLD_NOW);
    if (handle == nullptr) {
      *error_msg = strdup(dlerror());
    }
    return handle;
  }
  std::lock_guard<std::mutex> guard(g_namespaces_mutex);
  NativeLoaderNamespace* ns;
  if ((ns = g_namespaces->FindNamespaceByClassLoader(env, class_loader)) == nullptr) {
    // This is the case where the classloader was not created by ApplicationLoaders
    // In this case we create an isolated not-shared namespace for it.
    std::string create_error_msg;
    if ((ns = g_namespaces->Create(env, target_sdk_version, class_loader, false /* is_shared */,
                                   nullptr, library_path, nullptr, &create_error_msg)) == nullptr) {
      *error_msg = strdup(create_error_msg.c_str());
      return nullptr;
    }
  }
  return OpenNativeLibraryInNamespace(ns, path, needs_native_bridge, error_msg);
#else
 //...省略不相干的
#endif
}

     以上代码中会根据 class_loadercaller_location 进行判断,是直接使用 dlopen 还是 android_dlopen_extOpenNativeLibraryInNamespace 来进行加载 so。

八、总结

    (1)通过分析 System.loadLibrary 之后就可以非常清楚为什么我们动态注册的时候需要重写 JNI_OnLoad 方法。

  (2)System.loadLibrary 流程总结如下:

System(Java).loadLibrary->
Runtime(Java).loadLibrary0->
Runtime(Java).nativeLoad->
Runtime_nativeLoad(Runtime.cc)->
JVM_NativeLoad(OpenjdkJvm.cc)->
LoadNativeLibrary(java_vm_exe.cc)->
OpenNativeLibrary(native_loader.cpp)

图片

如果你对安卓系统相关的开发学习感兴趣:

       可加作者的 QQ 群(1017017661), 本群专注安卓系统方面的技术,欢迎加群技术交流。

点击屏末 | 阅读原文******** |** 查看更多文章**