
手头这台测试机上个月又双叒叕被我折腾成重启循环——不是硬件挂了是framework改崩了。老实说做安卓开发这些年不管你是写业务App、做系统定制、还是搞性能优化绕来绕去最后都会绕回同一个话题安卓系统框架也就是大家常说的Framework。网上聊Framework的文章不少但大多要么太理论要么直接甩源码下载链接。今天我打算换一种方式从我做系统定制和性能优化这几年踩过的坑出发把安卓系统框架的层级关系、核心组件、HAL层设计、SurfaceFlinger、以及Perfetto抓Trace这套东西串起来捋一遍。这篇内容适合三类人写App想深挖原理的应用开发者、刚转行做ROM或系统定制的工程师、以及面试前想快速搭起框架认知的学习者。看完全文你至少能回答三个问题框架层到底管什么、Binder为什么绕不掉、系统卡顿该从哪里一步步查起。1. 先理清楚安卓系统框架到底是什么1.1 从“盖楼”的角度看安卓的分层把整个安卓系统想象成一栋楼最底下是Linux内核这相当于地基管着CPU调度、内存分配、进程管理、设备驱动这些最基础的活。地基之上是HAL层也就是硬件抽象层它像水电管线的统一接口把各家芯片厂商的驱动塞进统一的插槽里上层不用管你用的是高通还是联发科。再往上是系统运行库和ART虚拟机相当于建材标准和施工规范让上面的代码能以统一的规则运行。再往上一层就是今天的主角——Framework框架层你可以把它理解成这栋楼里的承重墙和预埋管道承重墙决定了房间格局预埋管道决定了水电怎么走。楼里的人也就是各种App不直接接触地基和管线它们只通过Framework提供的电梯和门牌号来办事。为什么这么设计因为Android本身是一个多进程系统每个App默认跑在独立的沙箱进程里如果没有Framework统一管着App之间互相抢资源、乱插窗口、乱读数据整个系统早乱套了。Framework层最直观的存在就是位于/system/framework目录下的一堆jar包和oat文件。framework.jar是核心API和基础类的家Activity、Service、View、Binder这些都在这services.jar里跑着SystemServer启动的各种系统服务比如AMS、WMS、PMS这些“大管家”android.jar则是你写App时引用的SDK桩它只提供编译时需要的签名和公开方法真正运行时装在设备上的是前面那几个。我们平时说“改框架”其实改的就是两块一是frameworks/base/core里的Java API和核心类二是frameworks/base/services里的系统服务逻辑。而frameworks/base/packages/SystemUI里的状态栏、导航栏、通知面板本质上只是个特权App但因为跟框架耦合特别深也常常被算进框架开发的范围。1.2 为什么说 Framework 是决定“体验上限”的承重墙很多应用开发者会有个错觉性能问题都是自己代码写的烂跟系统没关系。但真相是你在应用层能做的事都被Framework牢牢框死了。举几个真实例子。热词里那个“安卓15动态隐藏/显示状态栏导航栏”看着是个很酷的小功能底层其实是改SystemUI和WindowManagerService里的窗口策略。具体说就是要在SystemUI的StatusBar里监听一个系统级Setting变化然后调用WindowManagerService.setViewVisibility或者重写DecorView的Insets逻辑把状态栏View对应的Layer从SurfaceFlinger的合成列表里移除或加回来。再比如为什么现在Android 12以后杀后台杀得这么狠因为在AMS和ActivityManagerService里有一套进程优先级和lmkd低内存杀进程守护程序的评判策略Ancestral、Visible、Perceptible、Cached这些进程状态等级都是Framework定义的。你写得再好的App如果被Framework放到Cached列表末尾一样会被第一个杀掉。这就是Framework的“承重墙”属性它决定了系统的能力边界、稳定性和体验上限。改错一个进程优先级可能让所有App集体闪退改对一处窗口策略就能实现发布会上吹的“灵动岛”。所以深入Framework不是炫技是真正能提升系统级体验的必经之路。2. Framework 核心命脉Binder、AMS 与四大组件2.1 Binder 是 Android 的“快递系统”绕不开的跨进程通信凡是在Android上做过跨进程通信一定听过Binder。那Binder到底是个啥简单说Binder是Android自己实现的一套跨进程通信IPC机制底层有一个内核驱动模块设备节点是/dev/binder。为什么不用Linux系统自带的那套管道、消息队列这些因为Android的进程间通信有四个硬要求性能要够快、要支持双向调用、要能传文件描述符、还要安全可控。传统的System V IPC共享内存能传大块数据但权限控制弱管道只能单向还得复制多次。Binder的特点是只需一次内存拷贝并且在传输过程中自动携带调用者的UID/PID接收方可以据此做权限校验。在Framework层面Binder干了一件特别重要的事让系统服务看起来像“直接在本地调用”。你去services.jar里看每个系统服务都通过ServiceManager注册成一个字符串名字比如activity对应ActivityTaskManagerServicewindow对应WindowManagerService。App端拿到的是一个BinderProxy代理对象调用方法时参数会被序列化到Parcel里通过transact发给服务端服务端的onTransact再做反序列化并执行真正的逻辑最后把结果回传。自己写一个跨进程接口标准做法是AIDL。比如定义一个ITestService.aidlinterface ITestService { String getMessage(); void setData(in Bundle data); }编译后系统会自动生成ITestService.Stub服务端和ITestService.Proxy客户端两个内部类你只要在服务端继承Stub实现方法再用ServiceManager.addService(test_service, binder)注册客户端就能用ServiceManager.getService(test_service)拿到代理像调用本地方法一样调用。这里有个新手特别容易踩的坑Binder事务缓冲区单次最大约1MB超过就会抛TransactionTooLargeException。我见过有人把一个2MB的Bitmap塞进Bundle里通过Binder传结果客户端那边直接崩。正确做法是小数据走Binder大文件走文件路径或ContentProvider的openFile接口。考虑框架开发时Binder还有一层意义很多性能问题的根源不在代码本身而在Binder线程池耗尽。/sys/module/binder/parameters/max_threads默认我见过15、16的配置如果同时有大量App跨进程调用系统服务Binder线程不够用所有调用都会排队系统表现为“全局卡死”或“ANR疯狂弹窗”。这也是为什么Perfetto里每次分析性能第一件事就是把binder_driver的track拉出来看。2.2 Activity 启动一趟完整流程从 startActivity 到 setContentViewActivity是每个安卓开发者的老朋友但它的完整启动链路很多人其实没捋过。我直接用一个实际流程来讲。你在App里调startActivity(intent)这个调用首先走进Instrumentation.execStartActivity通过Binder跑到系统端的ActivityTaskManagerServiceAndroid 10之后ATMS从AMS里拆出来了专门管Activity和Task。系统端收到请求后做一系列检查目标Activity有没有在Manifest里声明、调用者有没有权限、当前的应用进程存不存在。如果目标进程不存在ATMS会向zygote进程发一个fork请求。注意这个请求不是普通的JNI调用而是通过LocalSocket往zygote的socket发送命令。Zygote收到后fork出一个新进程新进程的入口是ActivityThread.main它会立刻调用Looper.prepareMainLooper建立主线程消息循环然后通过attach(true)把自己回传给系统服务。直到这一步新进程才算正式“接入”了系统。系统端接着就把LaunchActivityItem通过Binder发给新进程的ApplicationThread新进程侧收到后依次执行创建ActivityClientRecord、Instrumentation.newActivity反射创建Activity实例、WindowManagerGlobal里创建PhoneWindow和DecorView、最后进入onCreate。走到onCreate里的setContentView时其实只是把布局填充到DecorView这个容器里Window还没有真正显示。真正的显示是在onResume之后通过WindowManager.addView把DecorView注册到WindowManagerService里WMS会分配WindowState、计算Z序然后通知SurfaceFlinger把对应的Surface合成到屏幕上。这也是为什么有时你在onCreate里getWidth()拿不到正确宽高——因为Window的尺寸是WMS在布局阶段才最终确定的你onCreate里拿的只是一个预估值。这条链路对整个性能排查至关重要。比如热启动慢你要知道慢在哪个环节是Zygote来不及fork还是系统端WMS的relayoutWindow被卡住还是进程里主线程被其他消息堵了如果没有这个流程地图你就只能靠猜。有了它每一步都有对应的日志和Trace可以去验证。2.3 四大组件在 Framework 里谁在管四大组件不只是应用层的概念每一个在系统服务里都有一张独立的“台账”。Activity由ATMS和AMS管理ATMS管Task、ActivityRecord、窗口焦点AMS管进程生命周期和任务栈调度。打开dumpsys activity activities能看到当前所有的ActivityRecord和TaskRecorddumpsys activity processes能看到进程里跑了哪些组件、优先级是多少。Service由ACTIVITY_SERVICE其实还是AMS统一登记每个运行中的服务对应一个ServiceRecord里面记载了服务的包名、进程、绑定状态、启动次数。启动服务要经过ActiveServices.startServiceLocked里面有严格的进程状态检查和ANR超时判断。这也是为什么很多人做保活发现“明明加了START_STICKY过一会儿还是被杀”——系统层有stopServiceLocked和进程优化策略兜底不是应用层能轻易绕过的。BroadcastReceiver的派发则在BroadcastQueue里排队。Android 8.0之后隐式广播限制、Android 14之后又加了前台服务类型限制都是在Framework的BroadcastQueue和BackgroundActivityStartController里做手脚。动态注册的Receiver最后都收在AMS的mReceiverResolver这个数据结构里匹配时按Intent-Filter对批量扫描扫描耗时跟注册数量正相关。ContentProvider则通过ProviderMap按authority管理跨进程访问时最终走AMS的getContentProviderImpl查询并建立联系。Provider的onCreate其实优先于Application的onCreate执行这是很多人容易忽略的时序问题。记住一句话App层写的每一个组件都不是“代码自己跑起来”的而是由SystemServer里的几个大管家在调度和记录。所以框架层一出问题四大组件的启动、绑定、解析都会跟着遭殃这也就是为什么有些诡异Bug看起来是应用层问题最后定位在系统服务里。3. HAL 与 Treble系统框架底下的车间班组3.1 HAL 的演进从“直连 so”到“独立进程服务”前面讲了Framework负责定规则但真正干活的硬件驱动并不在Framework里而在HAL层。HAL的设计理念是“硬件无关性”Framework只面向接口不关心具体芯片实现。早期的HAL长什么样呢Framework层的JNI或者Native服务直接dlopen一个名为xxx.default.so的库找到hw_get_module导出的函数指针然后直接调用。这种模式下framework和vendor的so库是硬耦合的。系统升级一次vendor的so可能就要跟着适配一遍不然轻则功能失效重则直接重启。Google推出Treble架构之后把/system系统分区和/vendor厂商分区彻底隔离HAL接口不再是一堆直接dlopen的so而是用HIDL后来轻量场景用AIDL描述的跨进程接口。API变更频率和版本兼容性也有了严格约束。现在你看到的HAL服务比如android.hardware.audio2.0::IDevicesFactory其实是一个运行在独立进程里的binder服务。Framework里的AudioFlinger通过binder拿到IDevicesFactory代理再通过这个代理去打开具体的音频设备整个链路里不再直接碰vendor库。实操里感受最明显的是音频问题。做外放无声排查时adb shell dumpsys media.audio_flinger能看到完整的音频链路状态包括每个PlaybackThread、OutputStream是否打开、格式是否匹配。如果HAL层返回了DEAD_OBJECT或NO_INIT那问题基本锁死在vendor侧。定制系统时最烦的是selinux。HAL服务和framework服务之间的通信除了binder调用本身还受selinux策略约束。我自己就踩过坑新增了一个HAL服务代码写好了selinux的te规则忘记加结果服务反复被avc: denied拒绝日志里全是拒绝记录排查半天才反应过来是权限问题。这一点建议所有框架开发者都刻烟吸肺改HAL一定要同步改system/sepolicy、vendor/sepolicy里的对应te规则。3.2 系统启动链从开机动画到 Launcher 的完整接力理解了分区分层启动流程就顺理成章了。设备按下电源键Boot ROM加载BootloaderBootloader拉起Linux内核。内核起来后启动第一个用户态进程init它解析init.rc脚本挂载分区启动各种native守护进程其中最关键的是zygote。Zygote的意思是“受精卵”它是一个预加载了核心库和资源的进程之后所有App进程都是它的“孩子”。Zygote启动后立即fork出自己的第一个子进程SystemServer。SystemServer是Framework层真正的核心入口里面通过SystemServer.main一堆代码把几十个系统服务挨个启动AMS、WMS、PMS、NetworkManagementService、NotificationManagerService等等。每个服务启动顺序还有讲究比如PMS要先于AMS因为AMS需要知道装了哪些应用WMS又要在AMS之后因为窗口管理要配合Activity启动。服务全部启动完毕后SystemServer会通过AMS调用startHomeActivityLocked也就是启动Launcher桌面。同时SystemUI进程也会被拉起来负责状态栏、导航栏。一旦Launcher的第一帧绘制完成开机动画就退场sys.boot_completed这个系统属性被置为1。排查开机问题第一步永远是adb shell getprop | grep sys.boot_completed。这个属性为true说明Framework层服务全部启动完成是false说明卡在某个服务上。接下来看logcat -b all里SystemServer挂了哪个服务或者看/data/anr/下有没有system_server的anr trace。如果反复自动重启大概率是zygote被反复fork之后又检测到system_server崩溃触发了重启逻辑。3.3 SurfaceFlinger每一帧是怎么被画到屏幕上的手机上你看到的所有画面不管来自哪个App最终都要送到SurfaceFlinger手里做合成。SurfaceFlinger是Android显示系统的中枢它做的事情可以简化成两件一是管理Layer图层每个Window和Surface都对应一个Layer二是在每个VSYNC信号到来时把所有可见的Layer按Z序合成再提交给显示硬件。VSYNC是关键。屏幕是按固定频率刷新比如60Hz或120Hz。如果App乱画一帧画到一半屏幕就开始扫描输出了画面就会撕裂。为了解决这个问题Android引入VSYNC作为全局心跳Choreographer在VSYNC到来时安排App的UI绘制SurfaceFlinger也在VSYNC到来时进行合成。如果App的帧率跟不上屏幕刷新率系统会让SurfaceFlinger继续用上一帧的内容。但这会带来一个副作用latency变高。所以现代Android普遍用三重缓冲让App最多能提前准备好两帧缓解掉帧和延迟的矛盾。排查卡顿adb shell dumpsys SurfaceFlinger --latency是经典命令。它会打印每个Layer最近一帧的提交时间对比一下App vsync时间和submit时间能看出到底是App侧渲染慢SurfaceFlinger等待App提交还是合成阶段慢Submit本身耗时。不过这个命令输出的原始数据比较难看现在大家基本都用Perfetto图形化来看后面我专门讲。还有个很常见的场景状态栏或导航栏隐藏后底下的App画面没有全屏铺满而是留了一条黑边。这本质是WMS计算窗口Insets的逻辑和SurfaceFlinger实际合成的图层尺寸不一致导致的。改这种问题要同时改SystemUI的窗口策略和WMS的windowState计算只改一头没用。4. Framework 实战怎么改、怎么查、怎么调4.1 改 Framework 代码的常规路径改框架代码大多数开发者接触最多的是两个场景一个是调试定位问题在关键函数里加Log另一个是需求开发比如修改默认桌面、调整最近任务列表、屏蔽某些按键事件。两种场景我分开说。如果是调试定位最直接的办法是下载对应机型的系统源码Google官方AOSP或者厂商开源代码都行在frameworks/base下改动后用系统源码环境的构建工具编出新的system镜像再刷入测试机验证。这个改动流程比较重但却是每次框架问题排查必须走的路。我自己习惯先把想看的函数名在源码里搜一遍加到关键路径比如AMS的startActivityLocked、WMS的setViewVisibility编译出framework.jar或services.jar替换到设备上再触发复现。如果是需求开发比如想把系统默认的Launcher换成自己定制的桌面核心操作是在frameworks/base/core/res/res/values/config.xml里改config_defaultComponentName这个配置或者在PMS里调整setDefaultLauncher的逻辑。这种改动不用动C代码风险相对可控但编译和验证流程还是一样的需要完整的源码环境和测试设备。正规做法是刷机验证而不是运行时hook运行时hook只能作为本地调试的临时手段不适合上生产设备。这里特别说一下编译链路。现在的Android源码用的是Soong构建系统编译系统镜像前最好先source build/envsetup.sh然后lunch选择目标设备配置。只改Java代码时可以单独编make framework或make services编出来的是编译产物里的优化后的dex/oat文件再打包进system.img。改SystemUI的话单独编SystemUI.apk就行。但是千万注意改了接口签名比如AIDL接口加了方法后所有依赖方都要重新编否则跨进程调用直接NoSuchMethodException。我见过的翻车操作里最典型的是改了frameworks/base/core里的核心类但只编了framework.jar没重新编依赖它的Settings、SystemUI这些系统App导致各种类加载异常。记住一个原则凡涉及核心API改动必须全部重编系统App否则就别动core目录。4.2 Perfetto把系统卡顿拉到显微镜下以前分析系统性能大家用systrace。现在Google主推Perfetto它比systrace强大太多能同时看CPU调度、binder调用、SurfaceFlinger合成、内存、I/O等一大堆信息而且Web端可视化特别友好。抓trace的命令很简单adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto-trace -t 10s sched freq idle binder_driver-t 10s指定抓10秒后面的sched freq idle binder_driver是要使能的trace类别。抓完后adb pull拉回来拖到ui.perfetto.dev网站打开就能看。拿到Trace先别急着一帧一帧盯我有一套固定分析顺序。先看全局的CPU调度情况——各CPU核的利用率高不高有没有大段空转或者频繁迁移上下文。然后看binder_driver track系统服务之间跨进程调用是否有长时间排队。如果有大量调用集中在同一个服务再双击展开看调用的方法名找到具体瓶颈函数。最后看App侧的UI Thread和RenderThread排查是主线程被阻断还是渲染慢再回到SurfaceFlinger看合成阶段有没有丢帧。举个例子。有一次线上反馈桌面滑动掉帧我抓完Trace发现在android.anim的Choreographer VSYNC回调里有一个View的performMeasure耗时爆炸。顺着方法名找到具体View发现是一个自定义控件在Measure里写了一个O(n²)的layout算法。根源在App代码但如果没有Perfetto把帧链路展开你根本想不到一个自定义View能拖垮整个桌面。另外切记抓Perfetto时设备状态要干净。开着大量后台任务抓Trace里的噪声会大到你根本找不到主因。最好固定测试场景同一操作重复三次取有共性的问题段来分析。顶级遥测的思维跟做菜差不多食材Trace的质量决定菜品结论的上限。4.3 这些坑我替你先踩过了框架开发做了几年翻过的车比见过的人还多整理几个典型的给后来人当路标。第一个坑是改了hide API导致SDK编译不过。Android的framework.jar里有很多被hide标注的方法和类这些是自己的系统进程用的App正常编译时看不到。做框架定制时你尽可以在源码里调用但要保证编译环境引用的android.jar是构建产物里的新版而不是Android Studio自带的旧版。否则编译器会报错找不到方法。第二个坑是SurfaceFlinger相关。改了窗口策略后要重点看Layer的计数是否异常。adb shell dumpsys SurfaceFlinger | grep -c Layer如果异常增长说明有窗口没有被正常销毁时间久了会拖垮整个显示系统。这问题排查起来特别像内存泄漏但本质是资源没释放。第三个坑是Binder调用返回DeadObjectException但服务明明还在。这种情况十有八九是binder线程池被占满而不是服务死了。你可以先adb shell cat /proc/pidof system_server/task/*/comm | grep binder看看binder线程都在干嘛再用Perfetto看binder_driver的queue深度。第四个坑是和热词里那个“安卓11 root”相关的搞框架调试很多朋友喜欢先root再直接拿su权限去替换系统文件。这个作为临时调试手段没问题但两只脚全踩进去就麻烦了。root后的环境selinux经常被放宽很多问题在root状态下根本复现不了。我自己更倾向直接用userdebug版本的系统镜像调试保留一部分adb root能力同时selinux策略还是正常加载这样抓出来的问题才更接近真实。5. 常见问题速查Framework 异常定位思路这里整理一个我在实际工作中反复用到的速查表格遇到问题可以先对照这个表格走一圈。现象优先排查方向常用命令或思路开机动画结束后一直黑屏/桌面不出现SystemServer某些服务启动失败或Launcher崩溃检查sys.boot_completed属性logcat -b all里搜E/ActivityTaskManagerdumpsys activity activities看home是否启动App集体ANR系统反应迟钝Binder线程池耗尽或某个核心服务持锁dumpsys activity看binder线程数Perfetto看binder_driver队列检查近期framework是否有死锁日志状态栏反复重启/消失SystemUI进程崩溃或WMS窗口策略异常logcat过滤AndroidRuntimedumpsys window windows看SystemUI窗口状态杀掉应用后系统变卡进程没有被真正清理或LMKD策略没生效dumpsys activity processes看缓存进程数量dumpsys lmkd看杀进程记录音频无声或断续HAL层连接断开或AudioFlinger线程异常dumpsys media.audio_flinger检查播放线程和混音状态logcat搜AudioFlinger自定义HAL服务一直被拒selinux策略未覆盖或用错domainadb shell dmesg搜avc: denied确认vendorsepolicy的te规则屏幕显示内容切边/黑边WMS的Insets与SurfaceFlinger图层尺寸不一致dumpsys window displays看displayInsetsdumpsys SurfaceFlinger --list看图层尺寸这张表没有覆盖所有情况但可以作为第一阶段的排查框架。Framework的Bug千奇百怪但归根结底跑不出四类服务起不来、跨进程通信失败、渲染链路异常、资源泄漏。只要能快速判断出是这四类里的哪一种就已经成功了一大半。最后分享一个我坚持多年的习惯。改框架代码之前一定先把对应的源码文从头到尾读一遍理解清楚这段逻辑是给谁用的、什么时候会被调用、有没有其他服务也依赖它。很多时候你以为的“小改动”影响面远超想象。比如你以为只是改了一段状态栏显示逻辑结果连带着让WMS每次layout都重算Insets直接把整个系统的UI性能拖垮。这行没有捷径唯一的捷径就是“想清楚再动”。