ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 12源码揭秘:ART模块化、artd守护进程与dexopt策略深度解析

Android 12源码揭秘:ART模块化、artd守护进程与dexopt策略深度解析 我记得Android 12源码刚放出那会儿身边不少做系统优化的朋友第一反应都是这版ART变化太大了得重新适应。ARTAndroid Runtime是每个App从安装、启动到运行都绕不开的底层运行时Android S这代不像往年只是修修补补而是把模块边界、编译任务调度、安装策略、底层GC全动了一遍。这篇文章不整那些源码里的高大上理论就按我在AOSP编译、刷机、抓日志、调性能的真实经验把Android S里ART到底改了哪些地方、为什么这样改、实际用起来会踩什么坑一次说透。适合做系统定制、应用性能优化或者纯粹对Android底层好奇的同学。1. Android S中ART架构大变模块化与独立升级1.1 从“单一源码树”到“模块化拆分”拿到Android 12源码之后我第一时间先去翻了art/目录发现它比Android 11瘦了一大圈。以前的ART代码基本都堆在一起运行时runtime、编译器compiler、dex2oat、profman、sigchainlib、JNI相关实现全在一个大目录里。这种结构的直接后果就是你想单独升级dex2oat基本不可能必须跟着整个系统一起走。厂商要修一个编译器的bug得重新出系统包用户得重启刷机或者等OTA非常痛苦。Android S这次把ART相关代码拆成了几个独立模块我实际编译过之后理出来的结构大概是这样的art/核心运行时承担了runtime、class linker、GC、JNI、oat文件格式定义这些最底层的东西libartservice/ART服务层重点是一个新的artd守护进程还有对应的binder接口和proto定义dex2oat/AOT编译器从art目录里独立出来单独成模块oat/、dexlayout/、profman/这些工具链也独立了libcore/Java核心库和ART的构建关系更紧密整体向OpenJDK 11看齐。这个拆分不是随便分分目录完事而是构建系统、链接依赖、模块边界都跟着改了。ART终于可以像其他Mainline模块一样通过com.android.art这个APEX独立升级。注意com.android.art从Android 10开始就作为APEX存在了但Android 12之前它更接近“整块陶瓷打包”升级起来照样费劲。这次把编译器、artd、核心运行时切成独立部分意味着Google可以在不要求厂商重新出系统包的情况下单独推送ART的更新。1.2 为什么一定要拆很多同学问拆模块图啥原来的架构不是也能跑吗但从我做系统定制的角度拆完之后的收益非常直接。第一编译边界清晰。以前改dex2oat你要重新编译整个art相关的库还得处理一堆链接依赖。现在dex2oat单独一个模块改动的风险边界小了很多测试也能更聚焦。我在本地验证一个ART编译策略的patch以前要等全量编译现在单独编dex2oat模块加push效率高很多。第二OTA升级负担变小。用户的直观感受就是系统升级后第一次开机的编译压力比Android 11时代小。因为很多ART相关改动可以走APEX模块更新不用每个版本都触发所有应用的全量重编译。第三权限和隔离更好做。Android S把编译任务从system_server中彻底剥离给artd守护进程这个我下一节细说。整体上ART服务对外的接口从“一堆私有代码”收敛成了“一个明确的binder服务”系统稳定性和崩溃隔离都受益。当然拆分也带来一些阵痛。做自定义ROM的开发者需要适应新的Android.bp构建关系一些老的编译patch不能直接用。但方向是对的尤其是对于要长期维护系统镜像的团队来说模块化带来的便利远大于适应成本。2. artd守护进程编译任务终于从system_server里搬出来了2.1 以前的老思路system_server直接fork dex2oat在Android 11及之前应用安装或者OTA之后要做dex优化流程大概是这样PackageManagerService在处理安装或启动扫描时发现某个应用需要编译然后system_server内部直接去fork一个dex2oat的子进程让这个子进程把APK里的dex字节码编译成oat文件编译完成后结果再report给system_server。这个设计的问题做过系统稳定性的人应该深有体会dex2oat非常吃CPU而且它跑在system_server的“势力范围”里。一旦编译任务多、调度不好整个系统的卡顿风险就会被放大。更难受的是system_server本身是Android里最核心的进程你在里面塞太多跟安装编译相关的任务它一旦出问题所有上层服务跟着遭殃。2.2 新架构ArtManagerLocal artd dex2oatAndroid S引入了artd这个守护进程把dexopt相关的工作全部收编进去。现在的流程变成了这样system_server里的ArtManagerLocal通过binder接口向artd提交编译请求artd自己维护一个任务队列根据优先级去fork/执行dex2oatdex2oat编译完成后结果由artd回报给上层。这带来的好处非常明显。第一编译任务和外层系统服务彻底隔离。即使dex2oat因为某些奇怪的crash崩了也不会直接拖垮system_server最多就是编译失败等下次空闲的时候重试。第二调度更集中。artd可以对正在进行的编译任务做统一管理比如低电量时推迟编译充电且空闲时才编译甚至可以根据profile和策略决定每个应用的编译优先级。这种“集中式调度”在之前system_server直接fork的模式下很难做到这么精细。第三模块升级更灵活。artd是跟着com.android.artAPEX一起发布的想升级编译策略直接更新APEX就行不用厂商重新出系统包。我在Pixel上跑Android 12用adb shell ps -A | grep artd能看到一个单独的artd进程配合dumpsys artd可以看它内部的任务队列里面有大量dexopt任务的历史记录和reason调试的时候非常直观。2.3 设备厂商和普通开发者的影响先说设备厂商如果你正在做Android 12定制有几点必须注意别再依赖老的“直接fork dex2oat”方式去干活了遇到问题要看artd的日志和状态dalvik.vm.*和pm.dexopt.*这些属性配置依然有效但是优先通过artd看真实任务状态首次开机或者OTA之后system_server会通过artd陆续安排编译。你千万不要粗暴杀掉artd进程否则会有一堆应用一直处于未优化状态用户用起来就卡。普通App开发者日常感知其实不大。**但如果你做的是大型游戏或者需要频繁启动的复杂应用安装速度和启动差距会比较明显。**Android 12上安装大APK很多设备不会再等完整编译完成而是先verify通过就让你用。这点接下来继续展开。提示我另一台测试机在Android 12刚升级完第一次开机时明显发热、耗电增加。看artd日志发现它正在疯狂编译应用。这是正常现象不是中病毒别慌。3. dexopt新策略安装更快、启动更稳后台编译说了算3.1 安装策略的变化先verify后编译Android 12给人一个特别明显的体感装大应用比原来快。这背后就是dexopt策略的调整。更早的版本安装应用时PackageManager倾向于在安装阶段直接做一次完整编译比如speed-profile或者speed这会导致安装过程被拉得非常长。Android S的取舍是安装时先verify一下验证dex字节码合法性不急着做AOT编译把完整编译放到后台异步做。我举个例子我有一个几百MB的游戏APKAndroid 11上装完可能得等一两分钟。Android 12上十几秒就能装完点击图标也能正常启动但前面几次启动时会走JIT解释执行等后台编译完成后速度才会拉满。后台编译一般会在以下条件满足时触发设备空闲电量充足或者正在充电有足够的存储空间。这本质上是个“用户体验优先”的策略安装快、首次启动略慢、几分钟后达到最优。3.2 编译过滤器speed-profile到底是什么意思说到dexopt绕不开compilation filter。Android 12支持的过滤器有这么几个extract只做apk提取验证不编译一般用于轻量场景verify验证dex格式和字节码不做AOTquicken把一部分指令快速AOT化适合小应用space-profile基于profile做空间优化编译space优先节省空间speed-profile基于profile热点做编译这是系统默认首选的策略之一speed把热路径全部编译成AOT空间换速度编译时间长everything全量编译成机器码极度占空间和时间一般只有特殊设备才用。**speed-profile是现在最常用的默认档。**它的思路是先用JIT跑一段时间记录哪些方法是热点然后根据profile只编译这些热点方法其余继续解释执行。好处是编译量小、应用启动和运行能感觉到明显优化、对磁盘空间压力也不大。3.3 实操怎么查看和手动触发编译想知道某个应用当前是哪个编译档位用这个命令adb shell dumpsys package dexopt | grep 你的包名Android 12上这个命令依然通用输出里会包含dexopt status、compilation filter、reason等关键字段。想强制某个包马上做speed-profile编译adb shell cmd package compile -m speed-profile -f com.example.app想恢复默认策略adb shell cmd package compile -m reset com.example.app如果你想看artd自己视角的任务状态用adb shell dumpsys artd这个命令会列出一堆已经完成的、排队中的编译任务每条还有reason记录比如boot-after-ota、bg-dexopt、install。调试系统编译问题的时候比只看dumpsys package dexopt全面得多。3.4 影响与坑这块我在实际项目中踩过一个典型的坑。某个厂商的Android 12预装应用后台编译策略配置有问题导致这个应用在安装后很长时间都停留在verify状态。用户打开应用一直觉得卡反馈通道被塞爆。我接手后手动执行了一次speed-profile编译问题瞬间缓解。查到最后是预置的pm.dexopt.first-boot属性配错了。**自测建议如果你负责系统预置应用的性能装完之后别急着评分。先让设备静置半小时或者手动触发一次编译再做启动性能测试。**否则很容易得出“升级系统后应用启动变慢”的错误结论实际上只是后台编译还没跑完。4. ART运行时细节更新GC、JNI、hidden API一个都不能漏4.1 垃圾回收和内存压力Android 12的ART在GC方面继续优化。比较明显的变化是并发拷贝GCConcurrent Copying GC的继续增强。Android 11引入并发拷贝后GC停顿时间已经大幅降低Android S把它做得更稳特别是针对低内存设备更友好。另外LargeObjectSpace的回收策略也做了调整。大对象比如Bitmap的底层数组以前容易造成内存碎片和GC长停顿。这版优化后大对象分配失败的概率降低GC日志里能看到更少的并发压缩次数。对普通开发者来说这并不意味着可以肆无忌惮地开大内存。Android 12上OOM表现更“抗揍”但没人能保证内存无限大图片、大列表该优化还是得优化。真出现OOM时优先查bitmap大数组和一次性加载过多数据的问题别一见OOM就甩锅给GC。4.2 JNI与native内存Android 12在JNI层也有不少细节改动。libnativehelper做了一轮重构对Java与native的互操作、局部引用的生命周期管理、native崩溃时的backtrace信息都做了改进。最直观的变化是native堆上出现非法访问时logcat里的调用栈信息比之前好定位多了。如果你开发SDK涉及大量JNI调用几个建议及时释放局部引用别依赖PopLocalFrame的侥幸能用GetByteArrayRegion这类拷贝式读取的地方就别全程持有GetByteArrayElements指针这样能减少native堆碎片在native代码里循环创建大量局部引用时主动用PushLocalFrame/PopLocalFrame包起来。Android 12的检查比之前严格不主动释放真的会崩。4.3 hidden API执行收紧这是App开发者最容易遇到的一类崩溃。Android 12在hidden API也就是非SDK接口上的限制进一步收紧。新版本里greylist浅名单、dark greylist深名单、blocklist黑名单的划分和数量都有变化一些以前能反射调用的接口现在直接抛NoSuchMethodException甚至直接abort。最典型的例子就是有些SDK喜欢反射ActivityThread的内部方法。Android 12上这一套越来越不好使。实测下来如果你的App在Android 12上莫名其妙启动崩溃且崩溃栈里有Hidden API相关的提示九成是用了非SDK接口。解决办法也很直接优先替换成公开API或者用官方SDK扩展公共接口做不到的通过SystemApi配合系统签名来实现实在要反射加一层try/catch在非SDK接口异常时自动降级别让App直接崩。注意不要为了绕过hidden API限制去改源码、塞设备清单或者用其他手段篡改系统属性。这既不稳定也不安全App在用户设备上出问题你根本无从解释。5. 核心库升级Java 11能力和D8/R8的配合5.1 核心库版本演进Android 12的核心库libcore相比Android 11有了一次明显升级整个实现朝向OpenJDK 11看齐。这意味着你在Java层面可以用的标准库API更多了。我实测过可用的几个List.of、Map.of、Set.of这类集合工厂方法Android 10就部分支持Android 12覆盖更全String.repeat、String.isBlank、String.strip等Java 11新字符串方法java.time包下更多的时间API比如Duration.toSecondsPart这类更细的取整方法Optional.isEmpty、Predicate.not这类函数式小工具。这些API对App开发来说确实方便但有个兼容性问题必须注意**你写的代码最终是跑在老设备上的。**如果你没开desugar直接在老设备上用这些API大概率崩。所以别因为Android 12支持就顺手写一堆Java 11语法除非你把minSdk提到足够高或者开了核心库脱糖core library desugaring。5.2 对App开发者的实际影响D8/R8和ART更配了很多同学问Android S的ART更新了我们项目里的D8/R8构建链有没有什么变化D8/R8本身是构建工具链的一部分不是ART运行时但它们俩配合更紧密了。Android 12之后D8输出的dex格式和ART的兼容性更好尤其在处理大量lambda表达式时生成的字节码更符合ART的快速校验路径。用R8做代码缩减的App在Android 12上安装校验速度会略微提升。另一个实际影响是**如果项目已经开了多DEX或者用了R8升级到Android 12平台编译时尽量同步升级AGP到较新版本。**这不是玄学是为了让构建产物的dex字节码更贴近新ART的优化路径。老AGP产出的dex在新ART上可能还是能跑但优化空间会受限。5.3 兼容性自查清单我建议每个做Android开发的团队在适配Android 12时过一遍这个清单搜索代码里是否存在ActivityThread、H、Clipboard这类内部类反射检查targetSdk是否升级到31对应Android 12升级后立刻验证启动流程用R8开启代码混淆时加好-keep规则避免反射类名被改在真机上跑安装、启动、后台切换三个核心场景确认没有hidden API崩溃开启core library desugaring再使用Java 11相关API。6. 从系统体验回看ART变化流畅度、智能调度与后台优化6.1 系统流畅度的底层来源Android 12发布时大家都被Material You的动态取色和动效惊艳到。但很多人忽略了一点动效要流畅动画要跟手除了渲染引擎还依赖一整套运行时的调度支撑。ART这版在启动路径上的优化非常明显**基于Profile的引导编译策略加上artd的后台编译调度让很多应用的首次启动比Android 11快。**换句话说你看到的“打开App很顺”背后是dexopt没有挤在安装那一刻而是在合适的时间偷偷把热点方法编译好了。我用同一台Pixel 5做过粗测Android 11和Android 12下同一个社交App冷启动时间大概能差100到200ms。虽然样本不大但体感是真的。尤其是那些天天打开的App在Android 12上基本是越用越顺。6.2 “Smart”体验背后的思路Android S这一代系统到处是“smart”开头的特性比如Smart controls、Smart battery。网上甚至有人拿“smart medical art”这种词来描述Android S主打的智能感。虽然这个词组合得挺奇怪但方向是准的“智能”不是端到端的大模型而是系统知道什么时候该做什么。ART的artd调度就是一个例子它不追求所有应用全量AOT编译而是根据使用习惯和profile来决定编译哪些方法。**这种按需优化、后台调度的思路才算得上“智能”二字。**如果你观察过dumpsys artd的输出会发现一排排任务后跟着reason字段比如boot-after-ota、bg-dexopt、install非常直白地告诉你为什么编译器在干活。这就是系统在自行判断“该不该编译、什么时候编译、编译到什么程度”。6.3 对普通用户的建议普通用户不用纠结ART内部实现但可以留意几点系统升级后头一两天手机可能会发热这是在编译应用等它完成就行如果某天觉得某个应用特别卡可以先检查是否刚安装完后台编译可能还没跑完别在OTA升级后马上做性能测试给后台编译留几小时时间不然测出来的数据会非常误导人。7. 常见问题与排查技巧实录7.1 如何确认设备上的ART版本和artd状态想确认自己的设备是不是Android 12的ART最简单的方式adb shell getprop | grep -i art adb shell ps -A | grep artd adb shell dumpsys artdgetprop里比较关键的有dalvik.vm.usejit、dalvik.vm.dex2oat-filter等。dumpsys artd可以看任务队列和历史记录。7.2 应用在Android 12上崩溃怎么快速定位先抓logcat找崩溃栈。如果栈里关键方法是java.lang.reflect.Method.invoke且下面跟着一堆你自己代码没有的中间层大概率是反射被hidden API限制命中。建议先看这两个关键词adb logcat | grep -i hidden adb logcat | grep -i NoSuchMethodError确认命中之后回到代码里找那条反射路径能改公开API就改不能改就加降级逻辑。7.3 dexopt编译卡住或失败设备厂商调试时常见的一种情况dumpsys package dexopt里看到某应用一直是pending或者failed。排查步骤看artd日志adb logcat -d | grep artd看dumpsys package dexopt里对应包的last failure reason检查存储空间dexopt编译需要临时空间空间不够就会失败检查APK是否损坏尝试重新安装。如果是OTA后批量编译失败重点看是不是设置里禁止了后台编译或者厂商ROM改过pm.dexopt.*属性。7.4 安装变慢怎么办Android 12上安装慢一般是两个原因一是你正在用everything全量编译策略这种情况建议改成speed-profile二是设备存储吃紧dexopt根本没地方干活。改善方法adb shell cmd package compile -m speed-profile -f --all注意-f是强制没事别瞎跑。大批量应用强编译时设备会卡一会儿属于正常现象。7.5 学会看dumpsys package dexopt的关键字段这算是我的保留曲目。看一个应用到底优化成什么样重点抓这几个字段dexopt status成功、失败还是跳过compilation filter当前编译档位reason编译原因比如first-boot、boot-after-ota、bg-dexopt、installodex路径产物位置。如果看到一个包反复出现在bg-dexopt任务里说明它的profile在频繁变化可能有热点失效的问题。这时候需要回到应用侧做性能优化而不是指望系统无限重编译。我个人这几年的感受是ART每一次大版本更新都是在“编译成本”和“运行性能”之间找新的平衡。Android S这版把编译任务从system_server里拆出来让artd去调度又引入更灵活的dexopt策略本质上就是想让用户体验更顺滑。你不需要记住每个模块的源码路径但你应该知道装完应用别急着测试性能等后台编译完那个“卡”可能就消失了。这算是我在Android 12上踩过坑之后最想提醒大家的一句话。
返回列表