缘起 Android碎片化的问题除了好多厂商加了更符合国人土豪味的特性之外,其实还有一个更基础性的问题就是升级太慢。为啥子? 不记得之前讲过没有(好像讲过),现在再讲一遍:
虽然谷歌用了很多办法来提高新系统的适配速度(有技术上的,有厂商合作方面的),但这个长长的链条摆在这,新系统普及率比ios差得不是一星半点。痛定思痛,谷歌终于发现瓶颈在哪了,其实还是技术上的:
APEX和APK类似,它把Framework层中那些关键的东西搞成一个个的模块,然后可以单独升级这些模块。这些模块和就和一个一个的APK类似,就是一个压缩包,后缀名叫.apex。来看官方文档中对.apex文件格式的描述: apex和apk类似,实际上也是一个压缩文件。只不过apk和apex的目标不一样。请大家谨记
apex相当于对系统功能进行了更细粒度的划分,可以独立升级这些功能。而从另外某个意义上来说,这种做法也更加限制了设备厂商的魔改行为——这就是谷歌mainline计划的目的。 现在,我们可以把apex看成是一个一个的系统升级包,接下来我们看看系统中有哪些模块被划归到apex里了。 了解一下,有哪些apex包? 我们以模拟器+x86_64不带谷歌服务的镜像为例,启动模拟器看一下。 /system目录下多了一个apex目录。这里存的就是可以通过apex方式升级的系统功能。现在先不谈怎么个升级法。先看看哪些功能被封装到不同的apex里了。我们看几个主要的。 com.android.runtime.debug 其核心是com.android.runtime。什么是runtime呢?就是ART虚拟机相关的东西。下面是这个apex包的成员: 图中,每一个apex包(或者是目录)都包含:
那么,com.android.runtime apex包都有哪些内容呢? 以上是runtime apex的主要内容。可看出,它包含的大部分是ART虚拟机相关的so和可执行程序。另外,bionic C库的libc.so,libdl.so,libm.so也在其中。 另外,位于/system/lib下的libc.so等也变成了链接,指向位于apex包中的这些so。来看图: 注意上图中的/apex/com.android.runtime目录,这个和apex包的处理逻辑有关,我们稍后介绍。 com.android.media com.android.media是多媒体相关的apex包,直接看它的内容: media apex包还包含了libbinder.so,libc++.so等非常关键的so。但要指出的是,/system/lib下的libc++.so却不是链接到media下的libc++.so。也就是说,系统里存在多份libc++.so。一个是/system/lib下的libc++.so,另外一个是media apex下的libc++.so(实际上,另外一个apex包,com.android.media.swcodec下还有一个libc++.so)。 除了libc++.so外,其他好几个so也存在同样的情况。对这件事,我第一感觉是有点奇怪。为了加载到正确的so,可能需要链接器linker程序做对应的修改。此问题我没有继续查下去了,欢迎有知道的童鞋指教。 了解一下,apex的安装 apex的安装(包括更新,回退)是一个比较复杂的过程。 为此,谷歌不惜对init做了重大改动。这里,我不打算对init做更详细的分析,先浮光掠影带大家看一下。有需要的话可自己看看。 总体来说,init大体上还是我八年前在《深入理解Android 卷1》里分析的那个init的样子。比如都是解析init.rc文件,然后执行对应的动作。但我感觉现在的init对初学者非常的不友好。因为代码逻辑比较复杂,而且用上了C++(应该在Android 10之前就用上C++了)。 对于Android这样的复杂系统,我感觉从老版本开始学习其实是一个合适的方法。因为老版本的思想,目的可能更单纯,更容易掌握。这有点像Linux Kernel方面的书籍,绝大部分都停留在2.x时代。这也是我为什么不愿意给深入理解系列书籍升级版本的原因。深入理解系列,宁愿开垦一个之前从未写过的方向,也不要做老版本升级。有些知识,还真不是越新越好。以我个人经验看,7.0的时候我为了做一个Framework培训,大概花了1个月左右的时间就对自己的知识进行了全面升级。现在10.0的话,我感觉在3个月内可以完成知识升级工作。 整体来说,10.0中的init将分为三个阶段执行。这三个阶段挺有意思,都是执行init,但传的参数不一样。 上图是init的main函数,稍微介绍下:
apex作为一个系统功能安装包,肯定有一个对应的服务进行管理。在Android 10中,这个服务就是apexd。代码位于/system/apex下。init通过apexd.rc启动apex相关服务。看一下这个apexd.rc文件 apexd.rc有两个服务,一个是apexd-boostrap,一个是apexd。其实对应执行的程序都是/system/bin/apexd,只不过bootstrap带了一个参数“--bootstrap”。上面两个服务中,先启动apexd-bootstrap,然后再启动apexd。下面是apexd的入口代码: apexd-bootstrap是主要处理apex包的地方。包括校验签名 ,apex 版本比较等。然后,它会将 apex 包(例如 /system/apex/com.android.runtime.debug 等)挂载到/apex目录下。使用的方法是mount的MS_BIND标志。 我们看下apexd-bootstrap执行后,apex包mount的情况, 以上两个图中,最上面的是mount命令的结果。注意,apex包的mount和kernel的dm设备有比较密切的关系。Android 10大量使用了这个dm,以后有机会我们了解下。 下面的图是/apex/目录的内容。注意,有些目录名后面带一个@和数字,比如com.android.conscrypt@290000000。这个290000000是com.android.conscrypt apex包中apex_manifest.json里的version字段。比如: /apex是系统内部其他模块引用apex包的地方,这样就不需要使用/system/apex了。比如,我们上面的com.android.runtime.debug实际bind mount到/apex后将通过com.android.runtime来引用。 bind mount其实就是可以把一个目录挂载到多个目录。看起来效果和link一样。大家可在ubunutu上mount --bind试一下。 了解一下,apex的preinstall/postinstall 如果仅仅是挂载apex包,那我们还是太小瞧apex了。com.android.runtime的apex_manifest.json是这样的: apex_manifest.json里还有preIntallHook和postInstallHook两个小东西。这两个东西指向apex包中bin/目录下的两个shell脚本。我们看一下postInstallHook的脚本(bin/art_postinstall_hook)。 这个脚本干了什么具体的事情我们先不细说,但是一个apex包升级居然还要涉及到脚本来执行,这还是第一次在Android上看到。所以,这里的复杂度比较高,很容易出错。我个人猜测未来可能会优化。 了解一下,apexservice apexd服务将注册一个名为“apexservice”的服务到系统服务里,dumpsys apexservice打印的结果如下: apexservice还有一些服务,如下: 到此,我们对apex的了解就告一段落。对一般开发者而言,只要知道apex包是被谷老大用来更新系统级功能的就行。另外,代码中有一个GSI(Generic System Image),这个GSI镜像可能包含的就是这些谷歌希望由自己来控制更新的apex包。通过这种方式,厂商能改的地方将大大受限,而能访问谷歌playstore的人们也可以第一时间享受谷歌的系统更新(但是谷歌经常也挖坑,搞出好多系统bug) 后续的安排 AOSP 10源码撸了大概五天,发现其中有一些需要了解的知识,比如APEX、ART等。接下来会对这些东西做一系列的“了解”。在此也欢迎大家提供一些目标,好让我们的"了解Android 10”系列飞得更远一点。 最后的最后
|
|