系列前言

从明天开始,我会花较多的工夫来跟大家一起学习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