关于android:Android插件化系列一Binder机制ClassLoader

26次阅读

共计 5559 个字符,预计需要花费 14 分钟才能阅读完成。

系列前言

从明天开始,我会花较多的工夫来跟大家一起学习 Android 插件化。这一篇文章是 Android 插件化的启动篇。

Android 插件化是之前几年里的一个很火的技术概念。从 2012 年开始就有人在钻研这门技术。从毛糙的 AndroidDynamicLoader 框架,到第一代的 DroidPlugin 等,继而倒退到第二代的 VirtualApk,Replugin 等,再到现如今的 VirtualApp,Atlas。插件化在国内逐步的倒退和欠缺,却也在近几年呈现了 RN 等替代品当前缓缓会走向弱势。

只管插件化技术的钻研热潮曾经过来,然而这门技术自身还是有着大量的技术实际,对于咱们理解 Android 机制很有帮忙。所以从这篇文章开始我会写一系列的文章,加上本人对插件化的实际,最初会去从源码角度剖析几个优良的插件化库,造成一套残缺的插件化的理论体系。

上面是插件化的技术框架,也是我这个系列文章的行文思路,

一. Binder 机制

网上剖析 Binder 机制的文章曾经很多了,在这篇文章里,我不会去解说 Binder 的应用,而是会去解说分明 Binder 的设计思路,设计原理和对于插件化的应用。

为什么须要 Binder

首先咱们晓得,Android 是基于 Linux 内核开发的。对于 Linux 来说,操作系统为一个二进制可执行文件创立了一个载有该文件本人的栈,堆、数据映射以及共享库的内存片段,还为其调配非凡的外部治理构造。这就是一个过程。操作系统必须提供偏心的应用机制,使得每个过程能失常的开始,执行和终结。

这样呢,就引入了一个问题。一个过程能不能去操作别的过程的数据呢?咱们能够想一下,这是相对不能呈现的,尤其是零碎级的过程,如果被别的过程影响了可能会造成整个零碎的崩塌。所以咱们很天然的想到,咱们应该把过程隔离起来,linux 也是这样做的,它的虚拟内存机制为每个过程调配间断的内存空间,过程只能操作本人的虚拟内存空间。同时,还必须满足过程之间放弃通信的能力,毕竟团结力量大,单凭单个过程的独立运作是不能撑起操作系统的性能需要的。

为了解决这个问题,Linux 引进了用户空间 User Space 和内核空间 Kernel Space 的区别。用户空间要想拜访内核空间,惟一形式就是零碎调用。内核空间通过接口把应用程序申请传给内核解决后返回给应用程序。同时,用户空间过程如果想降级为内核空间过程,须要进行安全检查。

补充常识:零碎调用次要通过两个办法:

  • copy\_from\_user():将用户空间的数据拷贝到内核空间
  • copy\_to\_user():将内核空间的数据拷贝到用户空间

以上就是 linux 零碎的跨过程通信机制。而咱们马上要说的 Binder,就是跨过程的一种形式

Binder 模型

Binder 是一种过程间通信 (IPC) 形式,Android 常见的过程中通信形式有文件共享,Bundle,AIDL,Messenger,ContentProvider,Socket。其中 AIDL,Messenger,ContentProvider 都是基于 Binder。Linux 零碎通过 Binder 驱动来治理 Binder 机制。

Binder 的实现基于 mmap()零碎调用,只用拷贝一次,比惯例文件页操作更快。微信开源的 MMKV 等也是基于此。有趣味的能够理解一下。

首先 Binder 机制有四个参与者,Server,Client 两个过程,ServiceManager,Binder 驱动(内核空间)。其中 ServiceManager 和 Binder 驱动都是零碎实现的,而 Server 和 Client 是须要开发者本人实现的。四者之中只有 Binder 驱动是运行在内核空间的。

这里的 ServiceManager 作为 Manager,承当着 Binder 通信的建设,Binder 的注册和传递的能力。Service 负责创立 Binder,并为他起一个字符模式的名字,而后把 Binder 和名字通过 通过 Binder 驱动,借助于 ServiceManager 自带的 Binder向 ServiceManager 注册。留神这里,因为 Service 和 ServiceManager 也是跨过程通信须要 Binder,ServerManager 是自带 Binder 的,所以绝对 ServiceManager 来说 Service 也就相当于 Client 了。

Service 注册了这个 Binder 当前,Client 就能通过名字取得 Binder 的援用了。这里的跨过程通信单方就变成了 Client 和 ServiceManager,而后 ServiceManager 从 Binder 表取出 Binder 的援用 返给 Client,这样的话如果有多个 Client 的话,屡次返回援用就行了,然而事实上援用的都是放在 ServiceManager 中的 Service。

当 Client 通过 Binder 驱动跟 Service 通信的时候,往往须要获取到 Service 的某个对象 object。这时候为了平安思考,Binder 会把 object 的代理对象 proxyobject 返回,这个对象领有截然不同的办法,然而没有具体能力,只负责接管参数传给真正的 object 应用。

所以残缺的 Binder 通信过程是

OK,跨过程通信就讲清楚了。接下来咱们讲讲插件化中的 Binder。

Binder 与插件化

首先,咱们先回顾一下 Activity 的启动过程,Instrumentation 调用了 ActivityManagerNative,这个 AMN 是咱们的本地对象,而后 AMN 调用 getDefault 拿到了 ActivityManagerProxy,这个人 AMP 就是 AMS 在本地的代理。相当于 binder 模型中的 Client,而这个 AMP 继承的是 IActivityManager,领有四大组件的所有须要 AMS 参加的办法。本地通过调用这个 AMP 办法来间接地调用 AMS。这样,咱们就调用到了 AMS 启动了 Activity。

那么,AMS 如何与 Client 进行通信呢?当初咱们通过 Launcher 启动了 Activity,必定要通知 Launcher“没你什么事了,你洗洗睡吧”。大家能够看到,这里单方的角色就产生了扭转,AMS 须要去发消息,承当 Client 的角色,而 Launcher 这时候作为 Service 提供服务。而这次通信同样也是应用的 Binder 机制。AMS 这边保留了一个 ApplicationThreadProxy 对象,这个对象就是 Launcher 的 ApplicationThread 的代理。AMS 通过 ATP 给 App 发消息,App 通过 ApplicationThread 解决。

以上,就是 Binder 机制在 Android 中的使用,咱们前面会通过 hook 这个过程实现插件化。

总结

看了这么多,可能还是很多敌人不懂 Binder。我也是这样,很长一段时间都不晓得 Binder 到底指的是啥。起初我看到了这样一种定义:

  • 从过程间通信的角度看,Binder 是一种过程间通信的机制;
  • 从 Server 过程的角度看,Binder 指的是 Server 中的 Binder 实体对象;
  • 从 Client 过程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个近程代理
  • 从传输过程的角度看,Binder 是一个能够跨过程传输的对象;Binder 驱动会对这个逾越过程边界的对象对一点点非凡解决,主动实现代理对象和本地对象之间的转换。

相干视频:无所不能的 Binder 底层原理解析;插件化

二. ClassLoader

双亲委托模型

Java 中默认有三种 ClassLoader。别离是:

  • BootStrap ClassLoader:启动类加载器,最顶层的加载器。次要负责加载 JDK 中的外围类。在 JVM 启动后也随着启动,并结构 Ext ClassLoader 和 App ClassLoader。
  • Extension ClassLoader:扩大类加载器,负责加载 Java 的扩大类库。
  • App ClassLoader:零碎类加载器,负责加载应用程序的所有 jar 和 class 文件。
  • 自定义 ClassLoader:须要继承自 ClassLoader 类。

ClassLoader 默认应用双亲委托模型来搜寻类。每个 ClassLoader 都有一个父类的援用。当 ClassLoader 须要加载某个类时,先判断是否加载过,如果加载过就返回 Class 对象。否则交给他的父类去加载,持续判断是否加载过。这样 层层判断 ,就到了最顶层的 BootStrap ClassLoader 来试图加载。如果连最顶层的 Bootstrap ClassLoader 都没加载过,那就加载。如果加载失败,就转交给子 ClassLoader, 层层加载,直到最底层。如果还不能加载的话那就只能抛出异样了。

通过这种双亲委托模型,益处是:

  • 更高效,父类加载一次就能够防止了子类多次重复加载
  • 更平安,防止了外界伪造 java 外围类。

Android 中的 ClassLoader

android 从 5.0 开始应用 art 虚拟机,这种虚拟机在程序运行时也须要 ClassLoader 将类加载到内存中,然而与 java 不同的是,java 虚拟机通过读取 class 字节码来加载,然而 art 则是通过 dex 字节码来加载。这是一种优化,能够合并多个 class 文件为一个 classes.dex 文件。

android 一共有三品种加载器:

  • BootClassLoader:父类结构器
  • PathClassLoader:个别是加载指定门路 /data/app 中的 apk,也就是装置到手机中的 apk。所以个别作为默认的加载器。
  • DexClassLoader:从蕴含 classes.dex 的 jar 或者 apk 中,加载类的加载器,可用于动静加载。

看 PathClassLoader 和 DexClassLoader 源码,都是继承自 BaseDexClassLoader。

public DexClassLoader(String dexPath, String optimizedDirectory,
      String librarySearchPath, ClassLoader parent) {super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}

public class PathClassLoader extends BaseDexClassLoader {public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);  // 见下文
    }
}

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
        String libraryPath, ClassLoader parent) {super(parent);  // 见下文
        // 收集 dex 文件和 Native 动静库【见大节 3.2】this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
}

咱们能够看到 PathClassLoader 的两个参数都为 null,表明只能承受固定的 dex 文件,而这个文件是只能在装置后呈现的。而 DexClassLoader 中 optimizedDirectory,和 librarySearchPath 都是能够本人定义的,阐明咱们能够传入一个 jar 或者 apk 包,保障解压缩后是一个 dex 文件就能够操作了。因而,咱们通常应用 DexClassLoader 来进行插件化和热修复。

能够看到,BaseDexClassLoader 有一个相当重要的过程就是初始化 DexPathList。初始化 DexPathList 的过程次要是收集 dexElements 和 nativeLibraryPathElements。一个 Classloader 能够蕴含多个 dex 文件,每个 dex 文件被封装到一个 Element 对象。这 element 对象在初始化和热修复逻辑中是相当重要的。当查找某个类时,会遍历 dexElements,如果找到就返回,否则持续遍历。所以当多个 dex 中有雷同的类,只会加载后面的 dex 中的类。上面是这段逻辑的具体实现

public Class findClass(String name, List<Throwable> suppressed) {for (Element element : dexElements) {
        DexFile dex = element.dexFile;
        if (dex != null) {
            // 找到指标类,则间接返回
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {return clazz;}
        }
    }
    return null;
}

总结

咱们先是解说了 Java 中类加载的双亲委托机制,而后介绍了 Android 中的几种 ClassLoader,从源码角度介绍了两种 ClassLoader 加载机制的不同。当前的插件化实际中,咱们会常常用到 DexClassLoader。

视频:Android 程序员备战 2022FrameWork 必问全套教程:WMS/AMS/Handler/Binder/ 事件散发机制 / 屏幕适配 / 插件化
原文:https://juejin.cn/post/6844903936508297224

正文完
 0