只需一篇就让你详细了解 Java 中 so 文件的加载原理( 四 )


 
// android/libcore/dalvik/src/main/java/dalvik/system/// DexPathList.javapublic DexPathList(ClassLoader definingContext, String librarySearchPath) {...this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);}private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {List<File> result = new ArrayList<>();if (searchPath != null) {for (String path : searchPath.split(File.pathSeparator)) {if (directoriesOnly) {try {StructStat sb = Libcore.os.stat(path);if (!S_ISDIR(sb.st_mode)) {continue;}} catch (ErrnoException ignored) {continue;}}result.add(new File(path));}}return result;} 
系统列表则来自于系统的 path 路径,调用 splitPaths() 的第二个参数不同,促使其在分割的时候只处理目录类型的部分,纯文件的话跳过 。
 
// android/libcore/dalvik/src/main/java/dalvik/system/// DexPathList.javapublic DexPathList(ClassLoader definingContext, String librarySearchPath) {...this.systemNativeLibraryDirectories =splitPaths(System.getProperty("java.library.path"), true);...} 
拿到 path 文件列表之后就是调用 makePathElements 转成对应元素数组 。
 

  1. 按照列表长度创建等长的 Element 数组
  2. 遍历 path 列表
  3. 如果 path 包含 "!/" 的话,将其拆分为 path 和 zipDir 两部分,并创建 NativeLibraryElement 实例
  4. 反之如果是目录的话,直接用 path 创建 NativeLibraryElement 实例,zipDir 参数则为空
 
// android/libcore/dalvik/src/main/java/dalvik/system/// DexPathList.javaprivate static NativeLibraryElement[] makePathElements(List<File> files) {NativeLibraryElement[] elements = new NativeLibraryElement[files.size()];int elementsPos = 0;for (File file : files) {String path = file.getPath();if (path.contains(zipSeparator)) {String split[] = path.split(zipSeparator, 2);File zip = new File(split[0]);String dir = split[1];elements[elementsPos++] = new NativeLibraryElement(zip, dir);} else if (file.isDirectory()) {// We support directories for looking up native libraries.elements[elementsPos++] = new NativeLibraryElement(file);}}if (elementsPos != elements.length) {elements = Arrays.copyOf(elements, elementsPos);}return elements;} 
findNativeLibrary()
 
findNativeLibrary() 将先确保当 zip 目录存在的情况下内部处理 zip 的 ClassPathURLStreamHandler 实例执行了创建 。
 
  • 如果 zip 目录不存在(一般情况下都是不存在的)直接判断该路径下 lib 文件是否可读,YES 则返回 path/name、反之返回 null
  • zip 目录存在并且 ClassPathURLStreamHandler 实例也创建完毕的话,检查该 name 的 zip 文件的存在 。并在 YES 的情况下,在 path 和 name 之间跟上 zip 目录并返回,即:path/!/zipDir/name
 
// DexPathList.java// android/.../libcore/dalvik/src/main/java/dalvik/system/DexPathList.javaprivate static final String zipSeparator = "!/";static class NativeLibraryElement {public String findNativeLibrary(String name) {// 确保 element 初始化完成maybeInit();if (zipDir == null) {// 如果 zip 目录为空,则直接创建该 path 下该文件的 File 实例// 可读的话则返回String entryPath = new File(path, name).getPath();if (IoUtils.canOpenReadOnly(entryPath)) {return entryPath;}} else if (urlHandler != null) {// zip 目录并且 urlHandler 都存在// 创建该 zip 目录下 lib 文件的完整名称String entryName = zipDir + '/' + name;// 如果该名称的压缩包是否存在的话if (urlHandler.isEntryStored(entryName)) {// 返回:路径/zip目录/lib 名称的结果出去return path.getPath() + zipSeparator + entryName;}}return null;}// 主要是确保在 zipDir 不为空的情况下// 内部处理 zip 的 urlHandler 实例已经创建完毕public synchronized void maybeInit() {...}} 
/ ClassLoader#loadLibrary0() /
 
1.调用静态内部类 NativeLibrary 的 native 方法 findBuiltinLib() 检查是否是内置的动态链接库,细节见如下章节 。如果不是内置的 library,通过 AccessController 检查该 library 文件是否存在 。
 
  • 不存在则加载失败并结束
  • 存在则到本 ClassLoader 已加载 library 的 nativeLibraries Vector 或系统 class 的已加载 library Vector systemNativeLibraries 中查找是否加载过
    • 已加载过则结束
    • 反之继续加载的任务
 
2.到所有 ClassLoader 已加载过的 library Vector loadedLibraryNames 里再次检查是否加载过,如果不存在的话,抛出 UnsatisfiedLinkError:
 
Native Library xxx already loaded in another classloader


推荐阅读