【腾讯Bugly干货分享】动态链接库加载原理及HotFix方案介绍

本文深入探讨了Android中动态链接库(SO)的加载原理,从宏观流程到具体细节,包括ClassLoader如何寻找SO,nativeLibraryDirectories的来源,以及在不同系统环境下的查找路径。此外,文章还分析了64位的影响,如何判断SO是否已加载,以及面对加载问题的解决方案,特别是针对HotFix的方法。通过了解这些知识,开发者能更好地理解和解决动态链接库加载中遇到的各类问题。
摘要由CSDN通过智能技术生成

本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57bec216d81f2415515d3e9c

作者:陈昱全

引言

随着项目中动态链接库越来越多,我们也遇到了很多奇怪的问题,比如只在某一种 OS 上会出现的 java.lang.UnsatisfiedLinkError,但是明明我们动态库名称没错,ABI 也没错,方法也能对应的上,而且还只出现在某一些机型上,搞的我们百思不得其解。为了找到出现千奇百怪问题的原因,和能够提供一个方式来解决一些比较奇怪的动态库加载的问题,我发现了解一下 so 的加载流程是非常有必要的了,便于我们发现问题和解决问题,这就是本文的由来。

要想了解动态链接库是如何加载的,首先是查看动态链接库是怎么加载的,从我们日常调用的 System.loadLibrary 开始。

为了书写方便,后文中会用“so”来简单替代“动态链接库”概念。

1、动态链接库的加载流程

首先从宏观流程上来看,对于 load 过程我们分为 find&load,首先是要找到 so 所在的位置,然后才是 load 加载进内存,同时对于 dalvik 和 art 虚拟机来说,他们加载 so 的流程和方式也不尽相同,考虑到历史的进程我们分析 art 虚拟机的加载方式,先贴一张图看看 so 加载的大概流程。

我的疑问

  • ClassLoader 是如何去找到so的呢?
  • 如何判断这个 so 是否加载过?
  • native 库的地址是如何来的
  • so 是怎么弄到 native 库里面去的?
  • 如何决定 app 进程是32位还是64位的?

找到以上的几个问题的答案,可以帮我们了解到哪个步骤没有找到动态链接库,是因为名字不对,还是 app 安装后没有拷贝过来动态链接库还是其他原因等,我们先从第一个问题来了解。

2、ClassLoader 如何找 so 呢?

首先我们从调用源码看起,了解 System.loadLibrary 是如何去找到 so 的。

System.java

public void loadLibrary(String nickname) {
    loadLibrary(nickname, VMStack.getCallingClassLoader());
}

通过 ClassLoader 的 findLibaray 来找到 so 的地址

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;
        }

如果这里没有找到就要抛出来 so 没有找到的错误了,这个也是我们非常常见的错误。所以这里我们很需要知道这个 ClassLoader 是哪里来的。

2.1 ClassLoader 怎么来的?

这里的一切都要比较熟悉 app 的启动流程,关于 app 启动的流程网上已经说过很多了,我就不再详细说了,一个 app 的启动入口是在 ActivityThread 的 main 函数里,这里启动了我们的 UI 线程,最终启动流程会走到我们在 ActivityThread 的 handleBindApplication 函数中。

  private void handleBindApplication(AppBindData data) {
         ......
         ......
            ContextImpl instrContext = ContextImpl.createAppContext(this, pi);

            try {
                java.lang.ClassLoader cl = instrContext.getClassLoader();
                mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
            } catch (Exception e) {
                throw new RuntimeException(
                    "Unable to instantiate instrumentation "
                    + data.instrumentationName + ": " + e.toString(), e);
            }

            mInstrumentation.init(this, instrContext, appContext,
                   new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
                   data.instrumentationUiAutomationConnection);

           ......
           ......
        } finally {
            StrictMode.setThreadPolicy(savedPolicy);
        }
    }

我们找到了这个 classLoader 是从 ContextImpl 中拿过来的,有兴趣的同学可以一步步看看代码,最后的初始化其实是在 ApplicationLoaders 的 getClassLoader 中

ApplicationLoaders.java

 public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent)
    {
        ......
        ......
            if (parent == baseParent) {
                ClassLoader loader = mLoaders.get(zip);
                if (loader != null) {
                    return loader;
                }

                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
                PathClassLoader pathClassloader =
                    new PathClassLoader(zip, libPath, parent);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

                mLoaders.put(zip, pathClassloader);
                return pathClassloader;
            }

            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
            PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            return pathClassloader;
        }
    }

其实是一个 PathClassLoader,他的基类是 BaseDexClassLoader,在他其中的实现了我们上文看到的 findLibrary 这个函数,通过 DexPathList 去 findLibrary。

BaseDexClassLoader.java

   public String findLibrary(String libraryName) {
        String fileName = System.mapLibraryName(libraryName);
        for (File directory : nativeLibraryDirectories) {
            File file = new File(directory, fileName);
            if (file.exists() && file.isFile() && file.canRead()) {
                return file.getPath();
            }
        }
        return null;
    }

代码的意思很简单,其实就是首先给 so 拼成完整的名字比如a拼接成 liba.so 这样,然后再从存放 so 的文件夹中找这个 so,在哪个文件夹里面找到了,我们就返回他的绝对路径。所以这里最关键的就是如何知道这个 nativeLibraryDirectories 的值是多少,于是也引出我们下一个疑问, native 地址库是怎么来的,是多少呢?

3 nativeLibraryDirectories 是怎么来的?

通过查看 DexPathList 可以知道,这个 nativeLibraryDirectories 的值来自于2个方面,一个是来自外部传过来的 libraryPath,一个是来自 java.library.path 这个环境变量的值。

DexPathList.java

private static File[] splitLibraryPath(String path) {
        /*
         * 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
         *   2. the VM's libra
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值