设为首页 加入收藏

TOP

Android 的 so 文件加载机制(一)
2019-09-01 23:14:12 】 浏览:127
Tags:Android 文件 加载 机制

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

最近碰到一些 so 文件问题,顺便将相关知识点梳理一下。

提问

本文的结论是跟着 System.loadlibrary() 一层层源码走进去,个人对其的理解所整理的,那么开始看源码之前,先来提几个问题:

Q1:你知道 so 文件的加载流程吗?

Q2:设备存放 so 的路径有 system/lib,vendor/lib,system/lib64,vendor/lib64,知道在哪里规定了这些路径吗?清楚哪些场景下系统会去哪个目录下寻找 so 文件吗?还是说,所有的目录都会去寻找?

Q3:Zygote 进程是分 32 位和 64 位的,那么,系统是如何决定某个应用应该运行在 32 位上,还是 64 位上?

Q4:如果程序跑在 64 位的 Zygote 进程上时,可以使用 32 位的 so 文件么,即应用的 primaryCpuAbi 为 arm64-v8a,那么是否可使用 armeabi-v7a 的 so 文件,兼容的吗?

Q2,Q3,Q4,这几个问题都是基于设备支持 64 位的前提下,在旧系统版本中,只支持 32 位,也就没这么多疑问需要处理了。

源码

准备工作

由于这次的源码会涉及很多 framework 层的代码,包括 java 和 c++,直接在 AndroidStudio 跟进 SDK 的源码已不足够查看到相关的代码了。所以,此次是借助 Source Insight 软件,而源码来源如下:

https://android.googlesource.com/platform/

我并没有将所有目录下载下来,只下载了如下目录的源码:

我没有下载最新版本的代码,而是选择了 Tags 下的 More 按钮,然后选择 tag 为: android-5.1.1 r24 的代码下载。所以,此次分析的源码是基于这个版本,其余不同版本的代码可能会有所不一样,但大体流程应该都是一致的。

分析

源码分析的过程很长很长,不想看过程的话,你也可以直接跳到末尾看结论,但就会错失很多细节的分析了。

那么下面就开始来过下源码吧,分析的入口就是跟着 System.loadlibrary() 走 :

//System#loadlibrary()
public static void loadLibrary(String libName) {
    Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
}

//Runtime#loadLibrary()
void loadLibrary(String libraryName, ClassLoader loader) {
    //1. 程序中通过 System.loadlibrary() 方式,这个 loader 就不会为空,流程走这边
    if (loader != null) {
        //2. loader.findLibrary() 这是个重点,这个方法用于寻找 so 文件是否存在
        String filename = loader.findLibrary(libraryName);
        if (filename == null) {
             throw new UnsatisfiedLinkError(loader + " couldn't find \"" + System.mapLibraryName(libraryName) + "\"");
        }
        //3. 如果 so 文件找到,那么加载它
        String error = doLoad(filename, loader);
        if (error != null) {
            //4. 如果加载失败,那么抛异常
            throw new UnsatisfiedLinkError(error);
        }
        return;
    }
    
    //1.1 以下代码的运行场景我不清楚,但有几个方法可以蛮看一下
    //mapLibraryName 用于拼接 so 文件名的前缀:lib,和后缀.so
    String filename = System.mapLibraryName(libraryName);
    //...省略
    //1.2 mLibPaths 存储着设备存放 so 文件的目录地址
    for (String directory: mLibPaths) {
        String candidate = directory + filename;
        candidates.add(candidate);
        if (IoUtils.canOpenReadOnly(candidate)) 
            // 1.3 调用 native 层方法加载 so 库
            String error = doLoad(candidate, loader);
            if (error == null) {
                return; // We successfully loaded the library. Job done.
            }
            lastError = error;
        }
    }
    //...省略
}

所以,其实 System 的 loadlibrary() 是调用的 Runtime 的 loadLibrary(),不同系统版本,这些代码是有些许差别的,但不管怎样,重点都还是 loadLibrary() 中调用的一些方法,这些方法基本没变,改变的只是其他代码的优化写法。

那么,要理清 so 文件的加载流程,或者说,要找出系统是去哪些地址加载 so 文件的,就需要梳理清这些方法:

  • loader.findLibrary()
  • doLoad()

第一个方法用于寻找 so 文件,所涉及的整个流程应该都在这个方法里,如果可以找到,会返回 so 文件的绝对路径,然后交由 doLoad() 去加载。

java.library.path

但在深入去探索之前,我想先探索另一条分支,loader 为空的场景。loader 什么时候为空,什么时候不为空,我并不清楚,只是看别人的文章分析时说,程序中通过 System.loadlibrary() 方式加载 so,那么 loader 就不会为空。那,我就信你了,不然我也不知道去哪分析为不为空的场景。

既然程序不会走另一个分支,为什么我还要先来探索它呢?因为,第一个分支太不好探索了,先从另一个分支摸索点经验,而且还发现了一些感觉可以拿来讲讲的方法:

  • System.mapLibraryName()

用于拼接 so 文件名的前缀 lib,和后缀 .so

  • mLibPaths

在其他版本的源码中,可能就没有这个变量了,直接就是调用一个方法,但作用都一样,我们看看这个变量的赋值:

//Runtime.mLibPaths
private final String[] mLibPaths = initLibPaths();

//Runtime#initLibPaths()
private static String[] initLibPaths() {
    String javaLibraryPath = System.getProperty("java.library.path");
    //...省略
}

最后都是通过调用 System 的 getProperty() 方法,读取 java.library.path 的属性值。

也就是说,通过读取 java.library.path

首页 上一页 1 2 3 4 5 6 7 下一页 尾页 1/9/9
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇「Android」系统架构概述 下一篇Android ADB Server启动失败

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目