android中jni加载流程源码分析
来源:互联网 发布:知乎红人 编辑:程序博客网 时间:2024/06/05 10:02
在Java中加载一个jni库时通常会使用下面的代码加载
static { System.loadLibrary("native-lib");}
具体分析一下Android中System.loadLibrary("native-lib");
的加载流程。
JVM如何识别本地方法
带有native
关键字的方法在生成class文件的时候,方法的flags会带有ACC_NATIVE标志。
为了测试,我们新建一个JniMain.java文件:
public class JniMain{ public static native void jniMethod();}
然后使用javac JniMain.java
生成JniMain.class文件,利用javap命令查看class文件结构javap -s -p -v JniMain.class
Classfile /D:/JniMain.class Last modified 2017-8-13; size 208 bytes MD5 checksum 3e8c842f3fef9dce094959cc49b058c7 Compiled from "JniMain.java"public class JniMain minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Methodref #3.#11 // java/lang/Object."<init>":()V #2 = Class #12 // JniMain #3 = Class #13 // java/lang/Object #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 jniMethod #9 = Utf8 SourceFile #10 = Utf8 JniMain.java #11 = NameAndType #4:#5 // "<init>":()V #12 = Utf8 JniMain #13 = Utf8 java/lang/Object{ public JniMain(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 2: 0 public static native void jniMethod(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, ACC_NATIVE}
上面输出的是一个class文件结构,可以看到jniMethod的flags中包含了ACC_NATIVE标志。
System.loadLibrary源码分析
public static void loadLibrary(String libName) { Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader()); }
Runtime.java的loadLibrary方法
/* * Searches for and loads the given shared library using the given ClassLoader. */ void loadLibrary(String libraryName, ClassLoader loader) { if (loader != null) { 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 = doLoad(filename, loader); if (error != null) { throw new UnsatisfiedLinkError(error); } return; } String filename = System.mapLibraryName(libraryName); List<String> candidates = new ArrayList<String>(); String lastError = null; for (String directory : mLibPaths) { String candidate = directory + filename; candidates.add(candidate); if (IoUtils.canOpenReadOnly(candidate)) { String error = doLoad(candidate, loader); if (error == null) { return; // We successfully loaded the library. Job done. } lastError = error; } } if (lastError != null) { throw new UnsatisfiedLinkError(lastError); } throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates); }
1. 找so库的全路径
可以看到在ClassLoader不为空的前提下,会先调用String filename = loader.findLibrary(libraryName)
得到filename,然后根据filename调用Runtime.java的doLoad(filename, loader)
进行加载。
在Android中此处loader即为PathClassLoader,PathClassLoader继承自BaseDexClassLoader,并且所有实现都在BaseDexClassLoader中。所以我们先看一下BaseDexClassLoader的findLibrary方法。
BaseDexClassLoader.java@Overridepublic String findLibrary(String name) { return pathList.findLibrary(name);}
findLibrary直接返回的是pathList的findLibrary方法,pathList是BaseDexClassLoader的成员变量。
public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; /** List of native library path elements. */ private final Element[] nativeLibraryPathElements; ... /** * Constructs an instance. * * @param dexPath the list of jar/apk files containing classes and * resources, delimited by {@code File.pathSeparator}, which * defaults to {@code ":"} on Android * @param optimizedDirectory directory where optimized dex files * should be written; may be {@code null} * @param libraryPath the list of directories containing native * libraries, delimited by {@code File.pathSeparator}; may be * {@code null} * @param parent the parent class loader */ public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); } ... }
pathList在构造函数中被初始化,我们看一下DexPathList的构造方法:
public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { if (definingContext == null) { throw new NullPointerException("definingContext == null"); } if (dexPath == null) { throw new NullPointerException("dexPath == null"); } if (optimizedDirectory != null) { if (!optimizedDirectory.exists()) { throw new IllegalArgumentException( "optimizedDirectory doesn't exist: " + optimizedDirectory); } if (!(optimizedDirectory.canRead() && optimizedDirectory.canWrite())) { throw new IllegalArgumentException( "optimizedDirectory not readable/writable: " + optimizedDirectory); } } this.definingContext = definingContext; ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); // save dexPath for BaseDexClassLoader this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions); // Native libraries may exist in both the system and // application library paths, and we use this search order: // // 1. This class loader's library path for application libraries (libraryPath): // 1.1. Native library directories // 1.2. Path to libraries in apk-files // 2. The VM's library path from the system property for system libraries // also known as java.library.path // // This order was reversed prior to Gingerbread; see http://b/2933456. this.nativeLibraryDirectories = splitPaths(libraryPath, false); this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true); List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions); if (suppressedExceptions.size() > 0) { this.dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); } else { dexElementsSuppressedExceptions = null; } }
其中比较重要的一句
/*** Makes an array of dex/resource path elements, one per element of the given array.*/this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions);
现在我们看一下pathList.findLibrary(name);
到底做了啥
/** * Finds the named native code library on any of the library * directories pointed at by this instance. This will find the * one in the earliest listed directory, ignoring any that are not * readable regular files. * * @return the complete path to the library or {@code null} if no * library was found */ public String findLibrary(String libraryName) { String fileName = System.mapLibraryName(libraryName); for (Element element : nativeLibraryPathElements) { String path = element.findNativeLibrary(fileName); if (path != null) { return path; } } return null; }
首先会调 System.mapLibraryName(libraryName)根据平台相关的特殊命名,如注释所示。(7.0已经转为native 方法)
/** * Returns the platform specific file name format for the shared library * named by the argument. On Android, this would turn {@code "MyLibrary"} into * {@code "libMyLibrary.so"}. */ public static String mapLibraryName(String nickname) { if (nickname == null) { throw new NullPointerException("nickname == null"); } return "lib" + nickname + ".so"; }
然后nativeLibraryPathElements中遍历查找element.findNativeLibrary(fileName);
。
Element是DexPathList的静态内部类,每一个
/** * Element of the dex/resource file path */ /*package*/ static class Element { private final File dir; private final boolean isDirectory; private final File zip; private final DexFile dexFile; private ZipFile zipFile; private boolean initialized; public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) { this.dir = dir; this.isDirectory = isDirectory; this.zip = zip; this.dexFile = dexFile; } ...}
nativeLibraryPathElements中每一个Element中都保存着dex/resource file path信息。
//DexPathList$Elementpublic String findNativeLibrary(String name) { maybeInit(); if (isDirectory) { String path = new File(dir, name).getPath(); if (IoUtils.canOpenReadOnly(path)) { return path; } } else if (zipFile != null) { String entryName = new File(dir, name).getPath(); if (isZipEntryExistsAndStored(zipFile, entryName)) { return zip.getPath() + zipSeparator + entryName; } } return null; }
至此 获取到本地库的全路径。
2. 加载so库
Runtime.java 的doLoad方法如下
private String doLoad(String name, ClassLoader loader) { // Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH, // which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH. // The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load // libraries with no dependencies just fine, but an app that has multiple libraries that // depend on each other needed to load them in most-dependent-first order. // We added API to Android's dynamic linker so we can update the library path used for // the currently-running process. We pull the desired path out of the ClassLoader here // and pass it to nativeLoad so that it can call the private dynamic linker API. // We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the // beginning because multiple apks can run in the same process and third party code can // use its own BaseDexClassLoader. // We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any // dlopen(3) calls made from a .so's JNI_OnLoad to work too. // So, find out what the native library search path is for the ClassLoader in question... String ldLibraryPath = null; String dexPath = null; if (loader == null) { // We use the given library path for the boot class loader. This is the path // also used in loadLibraryName if loader is null. ldLibraryPath = System.getProperty("java.library.path"); } else if (loader instanceof BaseDexClassLoader) { BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader; ldLibraryPath = dexClassLoader.getLdLibraryPath(); } // nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless // of how many ClassLoaders are in the system, but dalvik doesn't support synchronized // internal natives. synchronized (this) { return nativeLoad(name, loader, ldLibraryPath); } }
最后会调用本地方法nativeLoad方法进行加载:
private static native String nativeLoad(String filename, ClassLoader loader, String ldLibraryPath);
Runtime.c中会通过动态注册的方式调用Runtime_nativeLoad方法进行加载。
- android中jni加载流程源码分析
- Android源码分析实战之JNI so库加载System.loadLibrary流程分析
- Android平台类加载流程源码分析
- Android 6.0 View加载流程源码分析
- Android Launcher加载流程源码分析
- Android Activty的加载过程 启动流程 源码分析
- Android中资源加载源码分析
- Android源码分析---JNI浅尝
- [Android源码分析]蓝牙打开流程分析——jni层之上的方方面面
- JNI so库加载流程之System.loadLibrary流程分析
- Android 添加JNI文件到源码流程
- Android 源码中新增JNI
- Android加载通话记录流程分析
- Android加载通话记录流程分析
- WordPress源码分析:首页加载流程
- Hibernate_hibernate.cfg.xml加载流程源码分析
- Dubbo SPI组件加载流程源码分析
- require.js源码分析之加载流程
- 准备 macvlan 环境
- Mysql索引的优化分析-索引性能分析
- DDNS
- 富文本设置
- 深度学习与文本分类总结第一篇--常用模型总结
- android中jni加载流程源码分析
- 平台总线设备驱动框架1
- 网易2017内推 [编程题] 等差数列@Java
- ucosii--内核的目录结构
- redis master copy slave
- linux使应用程序开机自启动
- List错误
- IOS/Andrord消息推送
- POJ 3461 Oulipo