源码分析 — Sytem.loadLibrary 解析

一、概述

在我们实际开发中,经常会调用so库的一些功能,那么他们是如何工作的呢? 本文我们就来分析一下 so库的加载原理。

Android SDK: Pie 9.0.0_r3

so的加载分为两个Java层、Native层两部分,下面我们以加载一个 native-lib.so 为例来具体分析一下它的加载流程。

二、Java 层

Java 加载 so 库的方式有两种:

  • System.loadLibrary(“lib_name”)
  • System.load(“full path”)

示例: 以加载一个 native-lib.so 为例。

//加载的是libnative-lib.so,注意的是这边只需要传入"native-lib"
System.loadLibrary("native-lib");
//传入的是so文件完整的绝对路径
System.load("/data/data/应用包名/lib/libnative-lib.so")

下面先来看下 System.load() 部分的调用流程。

2.1 System.load()

System.class

public static void load(String filename) {
    Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
}

Runtime.class

synchronized void load0(Class<?> fromClass, String filename) {
	// 传入的文件名如果不是路径的话则抛出异常。
    if (!(new File(filename).isAbsolute())) {
        throw new UnsatisfiedLinkError("Error msg");
    }
    if (filename == null) {
        throw new NullPointerException("filename == null");
    }
    // 调用native方法nativeLoad()进入JNI层。
    String error = nativeLoad(filename, fromClass.getClassLoader());
    if (error != null) {
        throw new UnsatisfiedLinkError(error);
    }
}

private static String nativeLoad(String filename, ClassLoader loader) {
    return nativeLoad(filename, loader, null);
}

// native代码
private static native String nativeLoad(String filename, ClassLoader loader, Class<?> caller);

System.load() 方法最终调用 Runtime.nativeLoad() 方法进入 JNI 层。

下面先来看下 System.loadLibrary() 部分的调用流程。

2.2 System.loadLibrary()

System.class

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

public class Reflection {
    @CallerSensitive
    public static native Class<?> getCallerClass();
}

Runtime.class

void loadLibrary0(Class<?> fromClass, String libname) {
    ClassLoader classLoader = ClassLoader.getClassLoader(fromClass);
    loadLibrary0(classLoader, fromClass, libname);
}

Runtime.loadLibrary0()

private synchronized void loadLibrary0(ClassLoader loader, Class<?> callerClass, String libname) {
	// 传入的文件名libName如果是路径(包含"/")的话,就抛出异常。
    if (libname.indexOf((int)File.separatorChar) != -1) {
        throw new UnsatisfiedLinkError("Msg");
    }
    
    String libraryName = libname;
    if (loader != null && !(loader instanceof BootClassLoader)) {
    	// classLoader存在时,调用loader.findLibrary()方法查询so库完整路径。
    	// loader实际为BaseDexClassLoader
        String filename = loader.findLibrary(libraryName);
        if (filename == null &&
                (loader.getClass() == PathClassLoader.class ||
                 loader.getClass() == DelegateLastClassLoader.class)) {
            // System.mapLibraryName()方法会在输入的libName前后加字符串。
            // 例如:传入native-lib,返回libnative-lib.so。
            filename = System.mapLibraryName(libraryName);
        }
        if (filename == null) {
            throw new UnsatisfiedLinkError("Msg");
        }
        String error = nativeLoad(filename, loader);
        if (error != null) {
            throw new UnsatisfiedLinkError(error);
        }
        return;
    }
    // 执行到这里,说明ClassLoader为null。接下来通过getLibPaths方式获取到so路径。
    getLibPaths();
    String filename = System.mapLibraryName(libraryName);
    String error = nativeLoad(filename, loader, callerClass);
    if (error != null) {
        throw new UnsatisfiedLinkError(error);
    }
}

private static String nativeLoad(String filename, ClassLoader loader) {
    return nativeLoad(filename, loader, null);
}

// native代码
private static native String nativeLoad(String filename, ClassLoader loader, Class<?> caller);

小结:
loadLibrary0() 方法主要做了3件事:

  1. classLoader存在时,通过classLoader.findLibrary(libraryName) 来获取存放指定so文件的路径。
  2. classLoader不存在时,则通过 getLibPaths() 接口来获取。
  3. 最后调用 nativeLoad 加载指定路径的so文件。

2.3 System.mapLibraryName()

下面分析一下 System.mapLibraryName() 的作用。

System.class

public static native String mapLibraryName(String libname);

对应的JNI层代码在 System.cc 中。

System.cc

// jvm_md.h
JNI_LIB_PREFIX = "lib"
JNI_LIB_SUFFIX = ".so"

// System.cc
JNIEXPORT jstring JNICALL
System_mapLibraryName(JNIEnv *env, jclass ign, jstring libname)
{
    int len;
    int prefix_len = (int) strlen(JNI_LIB_PREFIX);
    int suffix_len = (int) strlen(JNI_LIB_SUFFIX);

    jchar chars[256];
    if (libname == NULL) {
        JNU_ThrowNullPointerException(env, 0);
        return NULL;
    }
    len = (*env)->GetStringLength(env, libname);
    // libName名字太长也会报错。
    if (len > 240) {
        JNU_ThrowIllegalArgumentException(env, "name too long");
        return NULL;
    }
    // 将JNI_LIB_PREFIX写入chars数组。
    cpchars(chars, JNI_LIB_PREFIX, prefix_len);
    // 将libname写入chars数组。
    (*env)->GetStringRegion(env, libname, 0, len, chars + prefix_len);
    len += prefix_len;
    // 将JNI_LIB_SUFFIX写入chars数组。
    cpchars(chars + len, JNI_LIB_SUFFIX, suffix_len);
    len += suffix_len;

    return (*env)->NewString(env, chars, len);
}

static void cpchars(jchar *dst, char *src, int n)
{
    int i;
    for (i = 0; i < n; i++) {
        dst[i] = src[i];
    }
}

小结:

System.mapLibraryName(libraryName)方法会将libraryName名字前加上lib的前缀,在名字后面加上.so后缀。即输入 “native-lib”,输出 “libnative-lib.so”。


2.4 ClassLoader.findLibrary()

Android 中 ClassLoader 可以看文章:Android 中的 ClassLoader 体系

通过代码,我们可以看到 BaseDexClassLoader 里面实现了 findLibrary() 方法。

BaseDexClassLoader.class

DexPathList pathList;
public String findLibrary(String name) {
    return pathList.findLibrary(name);
}

DexPathList.class

// List of native library path elements.
NativeLibraryElement[] nativeLibraryPathElements;

public String findLibrary(String libraryName) {
	// 这里又调用System.mapLibraryName()方法。
    String fileName = System.mapLibraryName(libraryName);
    for (NativeLibraryElement element : nativeLibraryPathElements) {
        String path = element.findNativeLibrary(fileName);
        if (path != null) {
            return path;
        }
    }
    return null;
}

NativeLibraryElement.class

private final File path;
private final String zipDir;
private ClassPathURLStreamHandler urlHandler;
private boolean initialized;

public NativeLibraryElement(File dir) {
    this.path = dir;
    this.zipDir = null;
}

public String findNativeLibrary(String name) {
    maybeInit();
    // 非zip文件
    if (zipDir == null) {
    	// 路径与文件名拼接
        String entryPath = new File(path, name).getPath();
        if (IoUtils.canOpenReadOnly(entryPath)) {
            return entryPath;
        }
    } else if (urlHandler != null) {
    	// zip文件
        String entryName = zipDir + '/' + name;
        if (urlHandler.isEntryStored(entryName)) {
          return path.getPath() + zipSeparator + entryName;
        }
    }
    return null;
}

IoUtils.class

public static boolean canOpenReadOnly(String path) {
    try {
    	// 通过只读模式打开文件,来判断文件是否存在。
        FileDescriptor fd = Libcore.os.open(path, O_RDONLY, 0);
        Libcore.os.close(fd);
        return true;
    } catch (ErrnoException errnoException) {
        return false;
    }
}

小结:

  • ClassLoader.findLibrary() 方法传入一个 libName 的文件名,返回 libName 文件的全路径。
  • 传入的是libName文件名,对应的文件路径是在 NativeLibraryElement 中保存的。相当于是拿着 libName 去 List 这些文件夹下进行匹配。
  • 通过 IoUtils.canOpenReadOnly(fullPathName) 尝试能否打开文件来判断文件是否存在。

2.5 小结

  • loadLibrary 传入编译脚本生成的 so文件名即可,而 load 需要传入完整的so文件路径。
  • load 相比 loadLibrary 少了路径查找的过程。
  • load 并不是随便路径都可以,只支持两类路径。
    • 应用本地存储路径: /data/data/${package-name}/
    • 系统lib路径: system/lib
  • load 不支持直接加载 sdcard 路径的 so库。如果要加载,可以先将将sdcard下的so文件复制到应用本地存储路径下再进行加载。
  • loadLibrary 加载的都是一开始就已经打包进Apk或系统的so文件;而 load 可以是一开始就打包进来的so文件,也可以加载从网络下载的外部导入的so文件。最终都是调用 nativeLoad 加载指定路径的so文件。
  • so 的瘦包方案就是通过 System.load() 来实现so文件的动态化加载。

三、Native 层

接下来我们分析一下 so 加载的 JNI 部分。

NDK(五):JNI静态注册与动态注册 一文的动态化注册部分,我们介绍了 System.nativeLoad() 方法最终会调用到 JNI 层 Runtime.c 文件中的 Runtime_nativeLoad() 方法。

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

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

JVM_NativeLoad 方法在 OpenjdkJvm.cc 中。

/art/openjdkjvm/OpenjdkJvm.cc

JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
                                 jstring javaFilename,
                                 jobject javaLoader) {
  ScopedUtfChars filename(env, javaFilename);
  if (filename.c_str() == NULL) {
    return NULL;
  }

  std::string error_msg;
  {
    // 获取JavaVM实例
    art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();
    // 实际执行so加载的操作调用
    bool success = vm->LoadNativeLibrary(env,
                                         filename.c_str(),
                                         javaLoader,
                                         &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());
}

OpenjdkJvm.cc 内部是通过 art 虚拟机实例调用 LoadNativeLibrary() 来实际加载so的。

/art/runtime/java_vm_ext.cc

bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path,
                jobject class_loader, std::string* error_msg) {
  error_msg->clear();
  
  SharedLibrary* library;
  Thread* self = Thread::Current();
  // 1.先尝试从缓存查找,如果找到缓存说明上次初始化过。
  {
    // TODO: move the locking (and more of this logic) into Libraries.
    MutexLock mu(self, *Locks::jni_libraries_lock_);
    // 从缓存中查找,避免多次重复初始化。
    library = libraries_->Get(path);
  }
  // 2.查找加载so的ClassLoader。
  void* class_loader_allocator = nullptr;
  {
    ScopedObjectAccess soa(env);
    ObjPtr<mirror::ClassLoader> loader = soa.Decode<mirror::ClassLoader>(class_loader);

    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
    // ClassLoader不能是BootClassLoader。
    if (class_linker->IsBootClassLoader(soa, loader.Ptr())) {
      loader = nullptr;
      class_loader = nullptr;
    }

    class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader.Ptr());
    CHECK(class_loader_allocator != nullptr);
  }
  // 3.如果library缓存存在,则还要比较前后两次加载so的ClassLoader是否相同。
  if (library != nullptr) {
    // 如果library被加载过,则继续校验ClassLoader是否相同。
    if (library->GetClassLoaderAllocator() != class_loader_allocator) {
      // ...略...
      LOG(WARNING) << "当前的so已经被其它ClassLoader加载过,不能被其它ClassLoader再加载。";
      return false;
    }
	
	// 校验上次加载是否成功。
    if (!library->CheckOnLoadResult()) {
      return false;
    }
    return true;
  }
  // 4.缓存不存在,则开始执行加载so的流程。
  ScopedLocalRef<jstring> library_path(env, GetLibrarySearchPath(env, class_loader));

  Locks::mutator_lock_->AssertNotHeld(self);
  const char* path_str = path.empty() ? nullptr : path.c_str();
  bool needs_native_bridge = false;
  // 5.实际调用android::OpenNativeLibrary()加载so。
  // 参数patch_str传递的是动态库的全路径,之所以还要传递搜索路径,是因为可能包含它的依赖库。
  void* handle = android::OpenNativeLibrary(env,
                                            runtime_->GetTargetSdkVersion(),
                                            path_str,
                                            class_loader,
                                            library_path.get(),
                                            &needs_native_bridge,
                                            error_msg);

  if (handle == nullptr) {
  	// so加载失败
    return false;
  }

  if (env->ExceptionCheck() == JNI_TRUE) {
    LOG(ERROR) << "Unexpected exception:";
    env->ExceptionDescribe();
    env->ExceptionClear();
  }

  bool created_library = false;
  {
  	// 6.创建一个SharedLibrary对象,并关联so的句柄handle,然后会将创建的对象添加到缓存池libraries_中。
    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_中。
      libraries_->Put(path, library);
      // 标记设置为true。
      created_library = true;
    }
  }
  if (!created_library) {
    return library->CheckOnLoadResult();
  }
  
  bool was_successful = false;
  // 7.so加载成功后,去查看该so是否有"JNI_OnLoad"方法,有的话则进行调用。(so动态初始化的入口)
  void* sym = library->FindSymbol("JNI_OnLoad", nullptr);
  // so中没有JNI_OnLoad符号。
  if (sym == nullptr) {
    was_successful = true;
  } else {
	// so中有JNI_OnLoad符号,则调用JNI_OnLoad方法。
    ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
    self->SetClassLoaderOverride(class_loader);

    VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
    // 8.调用so库里的JNI_OnLoad方法。
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
    int version = (*jni_on_load)(this, nullptr);

    if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
      // Make sure that sigchain owns SIGSEGV.
      EnsureFrontOfChain(SIGSEGV);
    }

    self->SetClassLoaderOverride(old_class_loader.get());

    if (version == JNI_ERR) {
      // error msg
    } else {
      was_successful = true;
    }
  }
  // 9.给library设置是否加载成功的状态,用于前面流程的校验。
  library->SetResult(was_successful);
  return was_successful;
}

小结:

  1. 先尝试从 libraries_ 缓存池中查找,如果找到缓存说明上次初始化过。
  2. 查找本次加载so的ClassLoader。
  3. 缓存存在:so被加载过,则进一步判断前后两次加载so的ClassLoader是否相同。
    • ClassLoader 不同,则返回加载失败。
    • ClassLoader 项同,则进一步判断上次 so 的加载状态(是否加载成功),并返回对应加载状态。
  4. 缓存不存在:
    • 调用android::OpenNativeLibrary()加载so,返回一个操作so的句柄handle。
    • 创建一个SharedLibrary对象,并关联so的句柄handle,然后将创建的SharedLibrary对象添加到缓存池libraries_中,用于前面的查询使用,避免同一个so被多次加载。
    • so加载成功后,去查看该so是否有"JNI_OnLoad"符号,有的话则进行调用。(so动态初始化的入口)
      • so中没有JNI_OnLoad符号,则直接进入下一步。
      • so中有JNI_OnLoad符号,则调用JNI_OnLoad方法。
    • 给library设置是否加载成功的状态,用于前面流程的校验。
    • 返回当前so加载的状态:成功 or 失败。

四、小结

Java层:

  • Java 支持两种 so 的加载方式:System.loadLibrary()、System.load()
  • 两种方式最终都会调用 Runtime.nativeLoad() 这个 native 方法。
  • load 并不是随便路径都可以,只支持两类路径。
    • 应用本地存储路径: /data/data/${package-name}/
    • 系统lib路径: system/lib
  • 两者的差异:
    • 参数差异:loadLibrary 传入 so文件名即可,而 load 需传入完整的so文件路径。
    • 动态化差异:loadLibrary 只支持加载 Apk安装时就存在的so,以及系统的so库。 load 支持加载网络下载的 so库(需要将下载的so库复制到 /data/data/${package-name}/ 目录)。

JNI 层

  • 先进行缓存查找,如果找到缓存,还需要进一步比较 ClassLoader 是否相同。
  • 缓存没有,则进行 so 初始化。
    • 先加载 so,并返回一个handle 句柄。
    • 构建一个 SharedLibrary 对象,然后与 so 的句柄 handle 进行关联。
    • 将上面创建的 SharedLibrary 对象存放到缓存池 libraries_ 中。
    • 查找 so 中是否有 "JNI_OnLoad"符号,如果有,则调用该方法。
    • 设置 so 的加载状态给 SharedLibrary 对象。
    • 返回 so 加载状态到上一步。
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
System.loadLibrary()是Java中用于加载本地库文件(.so文件)的方法。它会根据给定的库名在系统lib路径和应用本地存储路径中查找对应的库文件,并加载到Java虚拟机中供程序使用。\[2\] loadLibrary()方法的参数只需要传入库文件的名称,而不需要传入完整的路径。它可以加载已经存在于Apk安装时的本地库文件,以及系统的库文件。\[2\] 在loadLibrary()方法内部,会通过ClassLoader来查找存放指定库文件的路径。如果存在ClassLoader,则会调用ClassLoader.findLibrary(libraryName)方法来获取路径;如果不存在ClassLoader,则会通过getLibPaths()接口来获取路径。最后,调用nativeLoad()方法加载指定路径的库文件。\[3\] 总结起来,System.loadLibrary()方法是Java中用于加载本地库文件的方法,它可以加载已存在于Apk安装时的本地库文件和系统的库文件,通过ClassLoader或getLibPaths()来获取库文件的路径,并最终调用nativeLoad()方法加载库文件。 #### 引用[.reference_title] - *1* *2* *3* [源码分析Sytem.loadLibrary 解析](https://blog.csdn.net/Love667767/article/details/129741512)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值