Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android插件化系列一:Binder机制,ClassLoader

Android插件化系列一:Binder机制,ClassLoader

作者头像
没关系再继续努力
发布于 2021-12-22 06:38:44
发布于 2021-12-22 06:38:44
55500
代码可运行
举报
文章被收录于专栏:Android面试Android面试
运行总次数:0
代码可运行

系列前言

从今天开始,我会花较多的时间来跟大家一起学习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。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中的类。下面是这段逻辑的具体实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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/事件分发机制/屏幕适配/插件化

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Android插件化原理解析
在解决插件中组件的生命周期,通常的做法是通过 Hook相应的系统对象,实现欺上瞒下,后面将通过Activity的插件化来进行讲解。
用户1205080
2019/02/26
9070
android插件化介绍
支持插件化的app可以在运行时加载和运行插件,这样便可以将app中一些不常用的功能模块做成插件,一方面减小了安装包的大小,另一方面可以实现app功能的动态扩展。
李小白是一只喵
2020/11/24
8830
android插件化介绍
深入理解Android插件化技术
插件化技术可以说是Android高级工程师所必须具备的技能之一,从2012年插件化概念的提出(Android版本),到2016年插件化的百花争艳,可以说,插件化技术引领着Android技术的进步。本篇
xiangzhihong
2018/02/06
1.8K0
深入理解Android插件化技术
Android插件化学习之路(二)之ClassLoader完全解析
Java代码都是写在Class里面的,程序运行在虚拟机上时,虚拟机需要把需要的Class加载进来才能创建实例对象并工作,而完成这一个加载工作的角色就是ClassLoader。
老马的编程之旅
2022/06/22
5930
Android插件化基础1-----加载SD上APK
大致的意思是: ClassLoader 是一个负责加载classes的对象,ClassLoader类是一个抽象类,需要给出类的二进制名称,ClassLoader尝试定位或者产生一个class数据,一个典型的策略是把二进制名字转换成文件名然后到文件系统中找到该文件 以下是ClassLoader常用到的几个方法及其重载方法:
隔壁老李头
2018/08/30
1.1K0
Android插件化基础1-----加载SD上APK
《Android插件化技术——原理篇》
| 导语 插件化技术最早从2012年诞生至今,已经走过了5个年头。从最初只支持Activity的动态加载发展到可以完全模拟app运行时的沙箱系统,各种开源项目层出不穷,在此挑选了几个代表性的框架,总结其中的技术原理。由于本人水平有限,插件化框架又相当复杂,文中若有错误或者不准确的地方望高手指点。 内容概要 一、发展历史 插件化技术最初源于免安装运行apk的想法,这个免安装的apk可以理解为插件。支持插件化的app可以在运行时加载和运行插件,这样便可以将app中一些不常用的功能模块做成插件,一方面减小了安
腾讯Bugly
2018/03/23
10K0
【Android 插件化】插件化原理 ( 类加载器 )
【Android 插件化】插件化简介 ( 组件化与插件化 ) 【Android 插件化】插件化原理 ( JVM 内存数据 | 类加载流程 ) 【Android 插件化】插件化原理 ( 类加载器 ) 【Android 插件化】“ 插桩式 “ 插件化框架 ( 原理与实现思路 ) 【Android 插件化】“ 插桩式 “ 插件化框架 ( 类加载器创建 | 资源加载 ) 【Android 插件化】“ 插桩式 “ 插件化框架 ( 注入上下文的使用 ) 【Android 插件化】“ 插桩式 “ 插件化框架 ( 获取插件入口 Activity 组件 | 加载插件 Resources 资源 ) 【Android 插件化】“ 插桩式 “ 插件化框架 ( 运行应用 | 代码整理 )
韩曙亮
2023/03/29
6650
【Android 插件化】插件化原理 ( 类加载器 )
Android ClassLoader详解
我们知道不管是插件化还是组件化,都是基于系统的ClassLoader来设计的。只不过Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码
xiangzhihong
2018/02/05
1.4K0
Android ClassLoader详解
ClassLoader解析(二):Android中的ClassLoader
main方法时ZygoteInit入口方法,其中调用了ZygoteInit的preload方法,preload方法中又调用了ZygoteInit的preloadClasses方法。
用户1205080
2019/01/23
1.5K0
ClassLoader解析(二):Android中的ClassLoader
Android解析ClassLoader(二)Android中的ClassLoader
前言 在上一篇文章我们学习了Java的ClassLoader,很多同学会把Java和Android的ClassLoader搞混,甚至会认为Android中的ClassLoader和Java中的ClassLoader是一样的,这显然是不对的。这一篇文章我们就来学习Android中的ClassLoader,来看看它和Java中的ClassLoader有何不同。 1.ClassLoader的类型 我们知道Java中的ClassLoader可以加载jar文件和Class文件(本质是加载Class文件),这一点在A
用户1269200
2018/02/01
1.4K0
Android解析ClassLoader(二)Android中的ClassLoader
Android类加载之PathClassLoader和DexClassLoader
任何结论在没有经过实际检验之前都不能够确定一定没问题。三年前写的文章回过头来发现有些部分内容是有问题的(PS:这的确比较尴尬),再次对结果进行验证之后重新修改了之前的结论。幸亏文章阅读量不是很多,希望被误导的同学能够在其他地方得到正确结论。
静默加载
2020/05/29
2.6K0
Android动态加载入坑指南
曾几何时,国内各大公司掀起了一股研究Android动态加载的技术,两年多过去了,动态加载技术俨然成了Android开发中必须掌握的技术。那么动态加载技术是什么呢,这里谈谈我的个人看法,如有雷同,纯属偶然。 什么是动态加载技术 对于动态加载的概念,没有一个权威的定义,参考网上的解释,我们举一个例子,动态加载代码就是通过在运行时加载外部代码(磁盘,网络等)改变程序行为的技术(感觉有点像装饰者模式)。主要目的是为了达到让用户不用重新安装APK就能升级应用的功能。 为了加深大家对这种概念的理解,我们结合pc端来说说
xiangzhihong
2018/02/05
2.3K0
Android动态加载入坑指南
有关Android插件化思考
最近几年移动开发业界兴起了「 插件化技术 」的旋风,各个大厂都推出了自己的插件化框架,各种开源框架都评价自身功能优越性,令人目不暇接。随着公司业务快速发展,项目增多,开发资源却有限,如何能在有限资源内满足需求和项目的增长,同时又能快速响应问题和迭代新需求,这就是一个矛盾点。此时,插件化技术正好风生水起,去了解各个主流框架实现思路,看看能对目前工作是否有帮助,是很有必要的。
用户1269200
2018/10/08
1.3K0
有关Android插件化思考
【Android 插件化】Hook 插件化框架 ( 通过反射获取 “插件包“ 中的 Element[] dexElements )
【Android 插件化】插件化简介 ( 组件化与插件化 ) 【Android 插件化】插件化原理 ( JVM 内存数据 | 类加载流程 ) 【Android 插件化】插件化原理 ( 类加载器 )
韩曙亮
2023/03/29
4760
Android中的ClassLoader分析
Dalvik是Google公司自己设计用于Android平台的Java虚拟机。它可以支持已转换为.dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,可以减少整体文件尺寸,提高I/o操作的类查找速度所以适合内存和处理器速度有限的系统。
g小志
2018/09/11
9880
Android中的ClassLoader分析
【Android 插件化】Hook 插件化框架 ( 通过反射获取 “宿主“ 应用中的 Element[] dexElements )
【Android 插件化】插件化简介 ( 组件化与插件化 ) 【Android 插件化】插件化原理 ( JVM 内存数据 | 类加载流程 ) 【Android 插件化】插件化原理 ( 类加载器 )
韩曙亮
2023/03/29
5120
Android 插件化实现原理解析
组件化开发就是将一个app分成多个模块,每个模块都是一个个组件,开发的过程中我们可以让这些组件相互依赖或者单独调试组件,但是最终发布的时候是将这些组件并成一个apk发布,而插件话 是分为一个宿主 和多个插件apk ,插件话成本高就是 适配 android版本,每个android版本的源码实现都不同,每个新版本出来,你就得去看源码然后 对这个源码做适配。
李林LiLin
2023/04/27
5980
Android 插件化原理解析——插件加载机制
上文 Activity生命周期管理 中我们地完成了『启动没有在AndroidManifest.xml中显式声明的Activity』的任务;通过Hook AMS和拦截ActivityThread中H类对于组件调度我们成功地绕过了AndroidMAnifest.xml的限制。
weishu
2018/09/05
1.8K0
Android 插件化原理解析——插件加载机制
浅谈Android热更新的前因后果
Android Studio2.0时,新增了一个 Instant Run的功能,而各大厂的热修复方案,在代码,资源等方面的实现都是很大程度上参考了Instant Run的代码。所以可以说 Instant Run 是推进Android 热修复的主因。
Rouse
2021/07/08
1.7K0
浅谈Android热更新的前因后果
android加载dex方法,android Dex文件的加载
上篇文章讲到了apk的分包,通过multidex构建出包含多个dex文件的apk,从而解决65536的方法数限制问题《Android Dex分包》。
全栈程序员站长
2022/09/07
2.1K0
相关推荐
Android插件化原理解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验