从今天开始,我会花较多的时间来跟你们一块儿学习Android插件化。这一篇文章是Android插件化的启动篇。java
Android插件化是以前几年里的一个很火的技术概念。从2012年开始就有人在研究这门技术。从粗糙的AndroidDynamicLoader框架,到第一代的DroidPlugin等,继而发展到第二代的VirtualApk,Replugin等,再到现现在的VirtualApp,Atlas。插件化在国内逐渐的发展和完善,却也在近几年出现了RN等替代品之后慢慢会走向弱势。linux
尽管插件化技术的研究热潮已通过去,可是这门技术自己仍是有着大量的技术实践,对于咱们了解Android机制颇有帮助。因此从这篇文章开始我会写一系列的文章,加上本身对插件化的实践,最后会去从源码角度分析几个优秀的插件化库,造成一套完整的插件化的理论体系。android
下面是插件化的技术框架,也是我这个系列文章的行文思路,git
网上分析Binder机制的文章已经不少了,在这篇文章里,我不会去讲解Binder的使用,而是会去讲解清楚Binder的设计思路,设计原理和对于插件化的使用。安全
首先咱们知道,Android是基于Linux内核开发的。对于Linux来讲,操做系统为一个二进制可执行文件建立了一个载有该文件本身的栈,堆、数据映射以及共享库的内存片断,还为其分配特殊的内部管理结构。这就是一个进程。操做系统必须提供公平的使用机制,使得每一个进程能正常的开始,执行和终结。微信
这样呢,就引入了一个问题。一个进程能不能去操道别的进程的数据呢?咱们能够想一下,这是绝对不能出现的,尤为是系统级的进程,若是被别的进程影响了可能会形成整个系统的崩塌。因此咱们很天然的想到,咱们应该把进程隔离起来,linux也是这样作的,它的虚拟内存机制为每一个进程分配连续的内存空间,进程只能操做本身的虚拟内存空间。同时,还必须知足进程之间保持通讯的能力,毕竟团结力量大,单凭单个进程的独立运做是不能撑起操做系统的功能需求的。app
为了解决这个问题,Linux引进了用户空间User Space和内核空间Kernel Space的区别。用户空间要想访问内核空间,惟一方式就是系统调用。内核空间经过接口把应用程序请求传给内核处理后返回给应用程序。同时,用户空间进程若是想升级为内核空间进程,须要进行安全检查。框架
补充知识:系统调用主要经过两个方法:ide
copy_from_user():将用户空间的数据拷贝到内核空间post
copy_to_user():将内核空间的数据拷贝到用户空间
以上就是linux系统的跨进程通讯机制。而咱们立刻要说的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。
首先,咱们先回顾一下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 驱动会对这个跨越进程边界的对象对一点点特殊处理,自动完成代理对象和本地对象之间的转换。
Java中默认有三种ClassLoader。分别是:
ClassLoader默认使用双亲委托模型来搜索类。每一个ClassLoader都有一个父类的引用。当ClassLoader须要加载某个类时,先判断是否加载过,若是加载过就返回Class对象。不然交给他的父类去加载,继续判断是否加载过。这样 层层判断
,就到了最顶层的BootStrap ClassLoader来试图加载。若是连最顶层的Bootstrap ClassLoader都没加载过,那就加载。若是加载失败,就转交给子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笨鸟之旅,一个陪着你慢慢变强的公众号,欢迎关注我一块儿学习,一块儿进步哈~