栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Android7 so库加载流程梳理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Android7 so库加载流程梳理

Java层通过System.load或System.loadLibrary来加载一个so文件,它的定义在Android源码中的路径为libcore/ojluni/src/main/java/java/lang/System.java,执行流程如下:

接下来,让我们具体看下System.loadLibrary这个方法的实现。

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

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

    synchronized void loadLibrary0(ClassLoader loader, String libname) {
        if (libname.indexOf((int)File.separatorChar) != -1) {
            throw new UnsatisfiedLinkError(
    "Directory separator should not appear in library name: " + libname);
        }
        String libraryName = libname;
        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 candidates = new ArrayList();
        String lastError = null;
        for (String directory : getLibPaths()) {
            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. 若ClassLoader非空,则利用ClassLoader的findLibrary方法来获取library的path;
  2. 若ClassLoader为空,则根据传递进来的libraryName,获取到library file的name(比如传递“test”进来,经过System.mapLibraryName方法的调用,返回的会是“libtest.so”)。然后再在一个path list(getLibPath函数)(即下面代码中的mLibPaths)中查找到这个library file,并最终确定library 的path;
  3. 调用nativeLoad这个jni方法来load library。
    private volatile String[] mLibPaths = null;

    private String[] getLibPaths() {
        if (mLibPaths == null) {
            synchronized(this) {
                if (mLibPaths == null) {
                    mLibPaths = initLibPaths();
                }
            }
        }
        return mLibPaths;
    }

然而,这里其实又牵扯出了几个问题:首先,可用的library path都是哪些?这实际上也决定了我们的so文件放在哪些目录下,才可以真正的被load起来。其次,在native层的nativeLoad又是如何实现加载的?下面会对这两个问题,逐一分析介绍。。

So的加载路径 分支1

先来看看当传入的ClassLoader为空的情况(执行System.loadLibrary时并不会发生),那么就需要关注下mLibPaths的赋值,相应代码如下:

    private static String[] initLibPaths() {
        String javaLibraryPath = System.getProperty("java.library.path");
        if (javaLibraryPath == null) {
            return EmptyArray.STRING;
        }
        String[] paths = javaLibraryPath.split(":");
        // Add a '/' to the end of each directory so we don't have to do it every time.
        for (int i = 0; i < paths.length; ++i) {
            if (!paths[i].endsWith("/")) {
                paths[i] += "/";
            }
        }
        return paths;
    }

这里paths实际上读取自一个system property:java.library.path,直接到System.java下查看初始化代码,它其实是LD_LIBRARY_PATH环境变量的值,具体内容可以查看注释,为"/vendor/lib:/system/lib"

    public static String getProperty(String key) {
        checkKey(key);

        return props.getProperty(key);
    }
    static {
        unchangeableProps = initUnchangeableSystemProperties();
        props = initProperties();
    private static Properties initProperties() {
        Properties p = new PropertiesWithNonOverrideableDefaults(unchangeableProps);
        setDefaultChangeableProperties(p);
        return p;
    }
    private static Properties initUnchangeableSystemProperties() {
        VMRuntime runtime = VMRuntime.getRuntime();
        Properties p = new Properties();

        // Set non-static properties.
        p.put("java.boot.class.path", runtime.bootClassPath());
        p.put("java.class.path", runtime.classPath());

        // TODO: does this make any sense? Should we just leave java.home unset?
        String javaHome = getenv("JAVA_HOME");
        if (javaHome == null) {
            javaHome = "/system";
        }
        p.put("java.home", javaHome);

        p.put("java.vm.version", runtime.vmVersion());

        try {
            StructPasswd passwd = Libcore.os.getpwuid(Libcore.os.getuid());
            p.put("user.name", passwd.pw_name);
        } catch (ErrnoException exception) {
            throw new AssertionError(exception);
        }

        StructUtsname info = Libcore.os.uname();
        p.put("os.arch", info.machine);
        if (p.get("os.name") != null && !p.get("os.name").equals(info.sysname)) {
            logE("Wrong compile-time assumption for os.name: " + p.get("os.name") + " vs " +
                    info.sysname);
            p.put("os.name", info.sysname);
        }
        p.put("os.version", info.release);

        // Undocumented Android-only properties.
        p.put("android.icu.library.version", ICU.getIcuVersion());
        p.put("android.icu.unicode.version", ICU.getUnicodeVersion());
        p.put("android.icu.cldr.version", ICU.getCldrVersion());

        // Property override for ICU4J : this is the location of the ICU4C data. This
        // is prioritized over the properties in ICUConfig.properties. The issue with using
        // that is that it doesn't play well with jarjar and it needs complicated build rules
        // to change its default value.
        p.put("android.icu.impl.ICUBinary.dataPath", getenv("ANDROID_ROOT") + "/usr/icu");

        parsePropertyAssignments(p, specialProperties());

        // Override built-in properties with settings from the command line.
        // Note: it is not possible to override hardcoded values.
        parsePropertyAssignments(p, runtime.properties());


        // Set static hardcoded properties.
        // These come last, as they must be guaranteed to agree with what a backend compiler
        // may assume when compiling the boot image on Android.
        for (String[] pair : AndroidHardcodedSystemProperties.STATIC_PROPERTIES) {
            if (p.containsKey(pair[0])) {
                logE("Ignoring command line argument: -D" + pair[0]);
            }
            if (pair[1] == null) {
                p.remove(pair[0]);
            } else {
                p.put(pair[0], pair[1]);
            }
        }

        return p;
    }

其中parsePropertyAssignments(p, specialProperties());是通过下面的native方法实现

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

static jobjectArray System_specialProperties(JNIEnv* env, jclass ignored) {
    jclass stringClass = (*env)->FindClass(env, "java/lang/String");
    jobjectArray result = (*env)->NewObjectArray(env, 4, stringClass, NULL);

    char path[PATH_MAX];
    char* process_path = getcwd(path, sizeof(path));
    char user_dir[PATH_MAX + 10] = "user.dir=";
    strncat(user_dir, process_path, PATH_MAX);
    jstring user_dir_str = (*env)->NewStringUTF(env, user_dir);
    if ((*env)->ExceptionCheck(env)) {
        return NULL;
    }
    (*env)->SetObjectArrayElement(env, result, 0, user_dir_str);
    if ((*env)->ExceptionCheck(env)) {
        return NULL;
    }
    jstring zlib_str = (*env)->NewStringUTF(env, "android.zlib.version=" ZLIB_VERSION);
    if ((*env)->ExceptionCheck(env)) {
        return NULL;
    }
    (*env)->SetObjectArrayElement(env, result, 1, zlib_str);
    if ((*env)->ExceptionCheck(env)) {
        return NULL;
    }
    jstring ssl_str = (*env)->NewStringUTF(env, "android.openssl.version=" OPENSSL_VERSION_TEXT);
    if ((*env)->ExceptionCheck(env)) {
        return NULL;
    }
    (*env)->SetObjectArrayElement(env, result, 2, ssl_str);
    if ((*env)->ExceptionCheck(env)) {
        return NULL;
    }


    const char* library_path = getenv("LD_LIBRARY_PATH");
#if defined(__ANDROID__)
    if (library_path == NULL) {
        android_get_LD_LIBRARY_PATH(path, sizeof(path));
        library_path = path;
    }
#endif
    if (library_path == NULL) {
        library_path = "";
    }
    char* java_path = malloc(strlen("java.library.path=") + strlen(library_path) + 1);
    strcpy(java_path, "java.library.path=");
    strcat(java_path, library_path);
    jstring java_path_str = (*env)->NewStringUTF(env, java_path);
    free((void*)java_path);
    if ((*env)->ExceptionCheck(env)) {
        return NULL;
    }
    (*env)->SetObjectArrayElement(env, result, 3, java_path_str);
    if ((*env)->ExceptionCheck(env)) {
        return NULL;
    }

    return result;
}

可见LD_LIBRARY_PATH的值就是java.library.path的值

分支2

然后我继续分析 ClassLoader.findLibrary() 和 nativeLoad() 方法

这里使用的 ClassLoader 是 PatchClassLoader ,其并没有实现 findLibrary() 方法,而是由其子类 BaseDexClassLoader 实现的,源码如下:

libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

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

这里交由 DexPathList 的 findLibrary() 方法去实现,如下:

    
    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() 去查找 so 库的完整名称,具体的可以看后面针对这个函数的分析,总之这里会从你传入的 native-lib 名称,变成 libnative-lib.so 这样完整的名称。

拿到完整的 so 库的名称之后,会通过遍历 nativeLibraryPathElements 去查找 so 库,那么 nativeLibraryPathElemts 是怎么初始化的?如下:

        // 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 (librarySearchPath):
        //   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(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
                                                          suppressedExceptions,
                                                          definingContext);

nativeLibraryDirectories 的获取路径是在安装过程,赋值到 ApplicationInfo 中去的,如下:

public class ApplicationInfo extends PackageItemInfo implements Parcelable {

public String nativeLibraryDir;
}

具体细节不分析,总的来说,如果 apk 是系统应用,则 nativeLibraryDir 为 /system/lib/xxx 或者 system/app/xxx/lib。但是对于我们自己的应用来说,这个值为 data/app/包名/lib。

以华为手机,安装抖音极速版为例:

抖音极速版的lib 目录为 /data/data/com.ss.android.ugc.aweme.lite/lib,其中 com.ss.android.ugc.aweme.lite 为包名。

systemNativeLibraryDirectories 是通过静态方法 getProperty() 去获取,这里的代码比较复杂,直接跳过,最终我们获取到的路径是:

bionic/linker/linker.cpp

#if defined(__LP64__)
static const char* const kSystemLibDir     = "/system/lib64";
static const char* const kVendorLibDir     = "/vendor/lib64";
static const char* const kAsanSystemLibDir = "/data/lib64";
static const char* const kAsanVendorLibDir = "/data/vendor/lib64";
#else
static const char* const kSystemLibDir     = "/system/lib";
static const char* const kVendorLibDir     = "/vendor/lib";
static const char* const kAsanSystemLibDir = "/data/lib";
static const char* const kAsanVendorLibDir = "/data/vendor/lib";
#endif

static const char* const kDefaultLdPaths[] = {
  kSystemLibDir,
  kVendorLibDir,
  nullptr
};

static const char* const kAsanDefaultLdPaths[] = {
  kAsanSystemLibDir,
  kSystemLibDir,
  kAsanVendorLibDir,
  kVendorLibDir,
  nullptr
};

最终,调用 NativeLibraryElemt 的 findLibrary()方法,如下:

    public String findNativeLibrary(String name) {
        maybeInit();

        if (zipDir == null) {//这里为 null
            String entryPath = new File(path, name).getPath();
            if (IoUtils.canOpenReadOnly(entryPath)) {
                return entryPath;
            }
        } else if (urlHandler != null) {
            // Having a urlHandler means the element has a zip file.
            // In this case Android supports loading the library iff
            // it is stored in the zip uncompressed.
            String entryName = zipDir + '/' + name;
            if (urlHandler.isEntryStored(entryName)) {
              return path.getPath() + zipSeparator + entryName; 
            }
        }

        return null;
    }

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/878597.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号