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方法进行加载。