一直以来,与 iOS 相比,Android在性能、安全、功耗、碎片化(兼容性)问题上一直被诟病。知耻而后勇,知弱而图强。自 Android 4.0以来,Google一直致力于解决性能(Project Butter/OpenGLRenderer/RenderThread/ART)、安全(SELinux/FDE/FBE/VerifiedBoot)、功耗问题(Project Volta/Doze/App Standby/Background Execution Limits)。到了Android8.0,提出了Treble架构,致力于解决碎片化问题,在Android发展史上留下浓重的一笔。
到了P版本,Android似乎停止了演进,在架构上没有出现可圈可点的变化。然而并没有,Google正在酝酿下一个版本(Q)的大招——APEX。
APEX与Treble有着千丝万缕的关系,它们同为解决碎片化(兼容性)问题而生。Treble使得用户快速地更新系统版本。简单来说,Android系统由厂商代码(QCOM/MTK)以及AOSP代码两部分组成。厂商代码主要是硬件相关的,AOSP则代表Android系统版本。在没有Treble之前,这两部分代码是耦合在一起的,导致升级AOSP代码的同时,也需要同步升级厂商代码。
由于这两部分代码是不同的公司维护的,同时升级是一件不容易的事情,需要花费大量的精力,导致Android的最新版本迟迟不能推送到用户手上。
Treble通过HIDL和VNDK两个技术实现了厂商代码和AOSP代码的解耦。解耦后,就可以实现各自独立升级,极大地降低手机厂商升级Android版本的难度。对用户来说,甚至可以不用等待手机厂商最新的定制版Android系统,而直接刷入最新的原生Android系统。
Treble似乎很完美,但是仔细观察,升级流程还是太重了。上一个版本的某一个系统模块出现了问题(安全、兼容性等),需要等到下一个OTA版本才能解决。通过OTA升级版本是一个很重的流程,而且它是以整个Android系统为目标进行升级的。因此,如果只是针对特定的模块,通过OTA的方式来解决显然是不合适的,杀鸡焉用牛刀!
APEX呼之欲出,它的目标比Treble更进一步,要能够独立升级特定的系统模块,就像独立升级APK一样。事实上,APEX文件与APK文件的结构基本上是一样的,而且都是zip压缩格式。
APEX包内部包含两个配置文件AndroidManifest.xml和manifest.json,两者的内容是一样的。
AndroidManifest.xml
manifest.json
从配置文件的内容可以看到,APEX模块具有包名和版本号。目前从公开的Android Q代码可以看到,系统中存在以下这些APEX模块:
这些APEX文件保存在/system/apex或者/data/apex目录中。其中,保存在/data/apex目录中的APEX文件就是动态更新下载的,用来更新原来的APEX模块。
APEX包的主角是image.img文件,这是一个EXT2格式的Image文件:
它的内容由content和vbmeta两部分组成:
vbmeta包含content的签名信息。APEX模块激活后,content的内容受到dm-verity完整性保护,防止持久化攻击。dm-verity就是通过vbmeta的信息对content的内容进行完整性保护的。这与system分区受到的完整性保护机制是一致的。
下面我们主要介绍content中的内容。其中,manifest.json是配置文件,和APEX包根目录下的manifest.json内容是一样的。file1和file2是要更新的系统文件。
file_contexts文件用来指定file1和file2文件的SELinux上下文:
fs_config文件用来指定file1和file2文件的UID/GID:
如果APEX模块包含bin文件,那么可以在Image文件中指定ld.config.txt文件,用来描述该bin文件的LinkerNamespace。LinkerNamespace也是Treble引进的技术,用来隔离VNDK中不同Linker Namespace中的共享库,以确保具有相同库名称和不同符号的库不会发生冲突。
ld.config.txt的部分内容如下所示:
其中,第1行和第6行表示,当执行/apex/com.android.apex.text/bin目录下的bin文件时,将/apex/com.android.apex.test/${LIB}增加到它的动态库搜索路径中去。这样更新后的bin可以使用自带的so库代替原系统中的so库。
APEX模块是由系统服务ApexService管理的,它主要提供的功能有:
在系统正常运行期间,系统将APEX文件拷贝到/data/apex目录下。等到下次开机时,ApexService就会自动通过activatePackage激活APEX模块,实现替换系统原有的APEX模块的功能。
以com.android.apex.test为例,APEX模块的激活过程如下所示:
Step 1:从/data/apex/[email protected]中提取image.img文件。
Step 2:为image.img文件创建一个Loop Device。
Step 3:使用预置在系统中的/system/etc/security/apex/apex.key检验image.img的vbmeata签名。
Step 4:为Step 2中的Loop Device创建Verity Deivce,以便可以通过dm-verity保护image.img内容的完整性,防止持久化攻击。
Step 5:将VerityDeivce挂载在/apex/com.android.apex.test@1中。
Step 6:为/apex/com.android.apex.test@1创建符号链接/apex/com.android.apex.test。
从这个APEX模块的激活流程可以看到,一方面APEX模块受到dm-verity完整性保护,另一方面APEX模块通过版本号管理。其中,/apex/总是链接到最新版本的APEX模块。
APEX模块激活之后,它是如何替换系统中原来的模块的呢?被替换的模块可能是:
不同类型的文件使用的替换方式是不一样的,我们分别介绍。
首先是配置文件,以com.android.tzdata为例,TimeZone信息是由libc负责读取的,它在读取的过程中,会优先检查/apex/com.android.tzdata下是否存在tzdata。如果存在,就会优先使用它:
*https://android.googlesource.com/platform/bionic/+/master/libc/tzcode/bionic.cpp
其次是ELF可执行文件,它所在的目录会被添加PATH环境变量中,以com.android.runtime为例,/apex/com.android.runtime/bin目录将会添加到PATH环境变量中:
*https://android.googlesource.com/platform/bionic/+/master/libc/include/paths.h
SO库文件和JAR库文件与ELF可执行文件类型,分别会被添加到各自的搜索路径中。
其中,将APEX模块中的SO库文件添加到搜索路径是在system/etc/ld.config.txt中配置的,以com.android.runtime为例:
*https://android.googlesource.com/platform/system/core/+/master/rootdir/etc/ld.config.txt
JAR库文件会被添加到BOOTCLASSPATH环境变量中,以com.android.conscrypt为例:
*https://android.googlesource.com/platform/build/+/master/target/product/base_system.mk
以上就是Android Q系统组件更新机制APEX。通过APEX,可以像更新APK一样更新系统组件,以快速解决Android系统的碎片化(兼容性)问题。我们也可以将APEX看作是Android系统的热更新机制。不过,从目前的热更新方式来看,只有预先指定的模块才可以进行热更新,不可以进行任意文件的更新。这是一个比较大的限制,不知道Google是否会考虑更灵活的方式,做到任意文件都可以进行热更新(使用Magisk的Magic Mount技术,或者overlayfs技术)。
转载自微信公众号:OPPO新技术
原文链接:https://mp.weixin.qq.com/s/9crfHyAiNUoLa6di9aQA3g