
系统架构全景五层楼的 Android## 1.1 一张五层楼的总平面图把 Android 想象成一栋五层楼。最上层住着**应用与运行时ART**你写的每个 App 都在这里由 Android Runtime 把字节码变成机器码执行第二层是 **Framework 框架层**核心是一个叫 system_server 的系统进程里面运行着 ActivityManagerService、WindowManagerService、PackageManagerService 等几十个大管家应用进程的创建、窗口的管理、包的安装都由它们裁决第三层是**原生服务层**一批用 C 写的守护进程——SurfaceFlinger 管合成显示、AudioFlinger 管音频、installd 管装包、apexd 管 APEX 模块更新——它们不依赖 Java 世界是系统的「体力劳动者」第四层是 **HAL 与厂商层**硬件抽象层用 AIDL/HIDL 接口把各家芯片的差异封装成统一契约VINTF 清单描述能力、SELinux 管住谁能访问谁最底层是 **Linux 内核**进程调度、内存管理、驱动、网络栈都在这里。| 楼层 | 名称 | 代表组件 | 负责回答的问题 ||---|---|---|---|| 5 | 应用与运行时 | App、ART、dalvik.vm 参数 | 代码怎么跑起来、跑多快 || 4 | Framework | system_serverAMS、WMS、PMS | 组件怎么调度、窗口怎么摆 || 3 | 原生服务 | SurfaceFlinger、AudioFlinger、installd、apexd | 图形、音频、装包谁来做 || 2 | HAL 与厂商 | AIDL/HIDL HAL、VINTF、SELinux、VNDK | 硬件差异怎么屏蔽、边界怎么守 || 1 | Linux 内核 | 调度器、内存管理、Binder 驱动、驱动 | 资源怎么分、硬件怎么管 |这张表的最大用处是**定位问题的楼层**。应用卡顿先想主线程在等谁等自己5 楼、等 Binder 返回4 楼、等 SurfaceFlinger 合成3 楼、等 GPU 驱动1 楼性能分析的第一步永远不是打开工具而是先猜楼层再用工具验证——这能省掉八成无效挖掘。## 1.2 system_serverFramework 的心脏system_server 是开机后由 Zygote 孵化的第一个「正式」Java 进程SystemUI、Launcher 之外的几乎所有系统服务都注册在这里。对性能排查最重要的几位管家**ActivityManagerServiceAMS**决定四大组件的生命周期与进程调度**WindowManagerServiceWMS**管理窗口层级、布局与动画和 SurfaceFlinger 密切配合**PackageManagerServicePMS**负责解析 APK、管理安装与组件查询首次开机慢的很大一部分账要记在它头上。这些服务运行在各自的 Binder 线程池里应用的一次 startActivity 背后就是一次跨进程调用你的主线程把请求发给 AMSAMS 再通知目标进程来回全靠 Binder。## 1.3 BinderAndroid 的「总机」Binder 是 Android 的进程间通信总线地位相当于整栋楼的电话交换机。一次同步 Binder 调用的旅程是调用方线程通过**代理对象proxy**把参数打包经 ioctl 进入 Binder 驱动驱动把数据**一次拷贝**送进目标进程的接收缓冲并从目标服务的**Binder 线程池**里唤醒一个线程执行真正的实现结果沿原路返回。理解 Binder 对性能有三点关键其一**它是序列化开销**传大对象、频繁调用都会累出毫秒级延迟日志里的 Binder transaction 时长值得盯其二**线程池有上限**服务端某个方法跑太久会拖垮整个服务的响应其三**oneway 调用会排队**异步不等于即时广播和事件分发里常见「发了很多、处理滞后」的背压现象。## 1.4 Zygote 与应用进程的出生每个 App 进程都不是从二进制冷启动的而是从 **Zygote** 这个「干细胞进程」fork 出来的。Zygote 在开机早期就预加载了大量类库与资源fork 时借助写时复制COW几乎零成本地共享给子进程之后新进程再加载自己的 APK 字节码。这套设计的性能含义很直接**进程本身创建很快慢的是预加载之后的那串初始化**——Application.onCreate、ContentProvider 安装、首个 Activity 的创建这正是启动优化篇第 14 篇要拆解的主线。Android 16 起 Zygote 的多进程模型还在演进如独立的服务进程池但「fork 快、初始化慢」的判断依然成立。## 1.5 ART从字节码到机器码ARTAndroid Runtime执行代码有三档**解释执行**最慢最省空间**JIT 即时编译**边跑边把热点编译成机器码**AOT 预先编译**在安装或空闲时通过 dex2oat 直接把 DEX 编成原生库。纯 AOT 浪费空间纯 JIT 首跑慢所以现代 Android 走混合路线先用 Profile运行画像标出热点编译器优先照顾它们——Baseline Profile、Cloud Profile、Startup Profile 全家桶都建立在这个思想上应用侧的最大可控杠杆就是提交一份高质量的 Profile让 ART 把启动关键路径提前编译好。另一个机制是**去优化deoptimization**当 JIT 的乐观假设被推翻例如某个虚调用突然换了实现已编译代码会退回解释执行表现为「越跑越慢」的诡异曲线抓 trace 时能看到 deoptimize 事件。## 1.6 容易被忽略的机制群这一层还散落着若干「平时看不见、出事才想起」的机制。**MessageQueue 与锁竞争**主线程的 Looper 靠消息队列驱动高并发下队列锁会成为瓶颈社区有过 DeliQueue 等无锁化探索Android 17 已为 target API 37 的应用启用无锁 MessageQueue——但依赖反射私有字段的测试工具可能因此失效。**cgroup**Android 用 cgroup v1/v2 把后台进程塞进受限的 CPU 与 I/O 带宽分组前台流畅的代价由后台任务承担。**BroadcastQueue**有序广播按接收者串行派发某个接收者的 onReceive 超时会拖累整条队列。**ContentProvider**它在 Application.onCreate 之前就可能被拉起是冷启动的隐形税自动初始化框架滥用它会显著拖慢启动。底层边界上**VNDK**把厂商库与系统库分开避免耦合腐烂**SELinux**用强制访问控制堵住越权访问**Bionic**是 Android 的 C 库体积与线程开销都比 glibc 小。[[FIG ai_layers.png|图 2 Android 五层架构与本专栏的覆盖范围]] 提示本篇是全书的地基。后续每一篇出现「AMS」「SurfaceFlinger」「ART」时都可以回到这张五层图确认它住在几楼、和谁做邻居——定位问题的第一步永远是先猜楼层。# 第 2 篇 渲染系统一帧的旅程## 2.1 主线从 VSync 到上屏你在屏幕上看到的每一帧都要走完一条流水线。**VSync**垂直同步信号由显示系统按刷新率节拍发出60 Hz 屏每 16.7 ms 一次120 Hz 每 8.3 ms 一次应用的 **Choreographer** 收到信号后回调 doFrame驱动 ViewRootImpl 遍历视图树并把绘制指令录成 **DisplayList**接着 **HWUI 的 RenderThread** 把这些指令翻译成 OpenGL/Vulkan 命令交给 **GPU** 真正光栅化画好的缓冲区通过 **BLAST** 缓冲队列提交给 **SurfaceFlinger**SurfaceFlinger 收集所有窗口的缓冲区决定哪些交给 **HWC硬件合成器**、哪些自己用 GPU 合成最终送到 **Display** 上屏。整条链是经典的生产者—消费者模型应用生产SurfaceFlinger 消费并合成HWC 与面板呈现。## 2.2 四个时间边界与 FrameTimeline原书给渲染链路标了**四个时间边界**堪称卡顿定位的坐标系vsync-app应用侧帧信号到达的时刻、vsync-sf合成器侧帧信号到达、present画面真正扫描出来的时刻与 actual实际呈现。Android 12 起系统把每个应用帧的**期望时间线Expected Timeline**与**实际时间线Actual Timeline**记录在 **FrameTimeline** 里期望是「这帧本该几点开始、几点上屏」实际是「它真的几点被提交、几点被呈现」。两线一对比帧被推迟在哪一段立刻现形——是应用主线程晚了Expected 起点就偏还是 GPU/合成器堵了Actual 终点偏。Perfetto 的 FrameTimeline 轨道就是把这组数据可视化第 11 篇会用它实操。## 2.3 BufferQueue、Gralloc 与 Sync Fence缓冲区在这条链上不是裸奔的它有三件配套行李。**BufferQueue** 是生产者与消费者之间的定长队列应用 dequeue 一块缓冲区来画、queue 回去待合成SurfaceFlinger 再 acquire 取用、release 归还四个状态流转全在 trace 里有迹可循队列满了应用会阻塞等待这是「 Surface 卡住」类问题的直接现场。**Gralloc**图形内存分配器HIDL 接口负责分配这些缓冲区大小、格式、是否可被硬件合成器直读都影响性能。**Sync Fence** 是跨硬件的「完工信号」GPU 还在画时 fence 不触发SurfaceFlinger 拿到带 fence 的缓冲区后原地等待而非空转——它让 CPU、GPU、显示控制器三台节奏不同的机器异步协作。排查「SurfaceView 黑帧」「视频抖动」类问题时fence 的等待时长往往是突破口。## 2.4 HWC谁来做合成一台屏幕上通常叠着多层内容状态栏、应用窗口、输入法、视频 Surface。把它们合成最终画面有两条路**CLIENT 合成**由 SurfaceFlinger 调 GPU 逐层混画耗 GPU 电但什么都能画**DEVICE 合成**由 HWC 芯片的 Overlay 硬件平面直接叠加几乎不耗 GPU。系统会优先把「规规矩矩」的层不旋转、不透明、格式受支持分给硬件平面剩下的交给 GPU。性能含义有二其一层数太多或某层带复杂变换圆角、透明度动画会导致**合成降级**GPU 负担陡增表现为刷微博时电量肉眼可见地掉其二dumpsys SurfaceFlinger 里能看到每层走了 DEVICE 还是 CLIENT是渲染优化的必查项。Android 的 HDR 显示、帧率切换90/120 Hz也都由 HWC 与面板能力共同决定。## 2.5 帧预算、帧率与 TaskSnapshot帧预算随刷新率收紧60 Hz 约 16.7 ms90 Hz 约 11.1 ms120 Hz 约 8.3 ms——**帧率翻倍不是「更流畅的奖励」而是「工期减半的考核」**。高刷屏普及后「主线程干同样的活」也会掉帧这是近几年流畅性话题变难的根本原因。另外两件事值得记一是**Frame Pacing**帧节奏游戏和视频场景不仅要看单帧多快还要看帧间隔是否均匀忽快忽慢比稳定地略慢更难受二是 **TaskSnapshot**系统给最近任务里的每个 Activity 存了一张启动快照冷启动时先展示快照再渐显真实内容——它让「秒开」的感觉成立但也解释了为什么冷启动测量必须区分「看到画面」和「可以交互」。## 2.6 渲染性能的三个观测口机制讲完落到「怎么看」。渲染性能有三个由近及远的观测口。**开发者选项的 GPU 呈现模式**俗称「柱状图」把最近几百帧按耗时画成条形红色超标一眼可见适合在真机上做最粗的定位**dumpsys gfxinfo 包名 framestats**输出最近 120 帧的逐帧分解绘制、准备、交换各阶段毫秒数能区分「应用画得慢」还是「合成等得久」**SurfaceFlinger 侧的 dumpsys SurfaceFlinger**则站在合成器视角列出每个 layer 的尺寸、变换与合成方式DEVICE 还是 CLIENT层数爆炸与合成降级在这里无处遁形。三个口各有盲区柱状图无归因、gfxinfo 只看应用、SurfaceFlinger 不见应用内部——所以第 11 篇的 Perfetto 与 FrameTimeline 才是终局答案这三个口是抓 trace 之前的热身。[[FIG ai_render.png|图 3 一帧的旅程VSync 到上屏的流水线与时间边界]] 注意动态刷新率ARR见第 3 篇普及后VSync 间隔会随内容变化分析时不要再默认每帧 16.67 ms——先确认实际刷新率再算帧预算否则 jank 判定会整体漂移。# 第 3 篇 输入系统从触摸到响应## 3.1 一条八站的传送带手指碰到屏幕之后事件要穿过一条清晰的传送带**触摸屏驱动与 evdev** 在内核里产生原始事件**EventHub** 从 /dev/input 节点批量读取并做设备管理**InputReader** 把原始采样转换成标准化的 MotionEvent/KeyEvent并处理校准、多点分离随后事件进入**监听器队列**做逐层加工下一节细讲**InputDispatcher** 为事件找到目标窗口、处理焦点与 ANR 计时经 **InputChannel/InputTransport** 这条进程间管道送达目标进程应用的 **InputEventReceiver**跑在主线程的 Looper 上接收并分发给 **ViewRootImpl**最终落到 DecorView 与你的 onTouch 回调。## 3.2 Android 17 的监听器顺序Android 17 上InputReader 与 InputDispatcher 之间的事件加工队列是固定的七站InputReader → UnwantedInteractionBlocker → InputFilter → PointerChoreographer → InputProcessor → Metrics 与 InteractionReporter → InputDispatcher。各自分工**UnwantedInteractionBlocker** 做手掌误触拒绝**InputFilter** 是策略过滤器无障碍、手势拦截都在这里**PointerChoreographer** 把原始输入升格成指针事件并做显示坐标侧的 choreography**InputProcessor** 做动作分类原书特别澄清旧资料里的 InputClassifier.cpp 在 android-17.0.0_r1 已不存在分类逻辑在 InputProcessor.cpp且**不是**「所有输入都经过 AI 分类」Metrics 与交互报告器收集遥测部分构建可能省略。无障碍服务、输入法、手势导航的怪异行为多数能在这个队列里找到对应的一站。## 3.3 六个时间边界别把「收到回调」当成「响应完成」端到端输入延迟要标出**六个时刻**①硬件或内核产生事件②EventHub 与 InputReader 读取转换③InputDispatcher 选好目标并发出④目标进程的 InputConsumer/InputEventReceiver 收到⑤应用处理完并提交包含视觉变化的缓冲区⑥SurfaceFlinger 与 HWC 完成呈现。两个最常见的测量误区都在这里MotionEvent.getEventTime() 接近第①时刻拿它到自己的回调结束做差只覆盖了「输入到应用」**不含渲染与上屏**反过来onTouch 里改完 UI 就宣布「响应完成」其实第⑥时刻还没发生。MOVE 事件还会被批量传输、在 VSYNC 前夕由 ViewRootImpl 统一消费——批处理省线程唤醒却改变每个采样点的消费时刻做重采样与预测坐标分析时要分清原始采样时间、预测目标时间与呈现时间。## 3.4 新面孔与背压近几个版本输入系统有几张新面孔值得记住。**Rust 编写的 InputFilter** 用内存安全语言重写了过滤管线内置防连击bounce keys、慢键slow keys、粘滞键sticky keys等无障碍能力是 Android 系统组件 Rust 化的代表案例。**Predictive Back**Android 13 引入、14 默认手势把返回手势做成「可预览、可取消」手指滑动过程中系统持续回调预览动画松手才提交——它要求动画能前进也能后退给应用动画设计立了新规矩。**ARRAdaptive Refresh Rate自适应刷新率**在显示时序侧调整 VSYNC 间隔输入链路的测量基准随之浮动。**InputDispatcher 的背压**机制则负责讨债应用主线程迟迟不消费输入dispatcher 会停止派发并可能触发输入类 ANR第 8 篇展开。## 3.5 输入设备家族与派生事件传送带上的「货物」不止手指一种。**触摸**是绝对主力多点触控由 InputReader 做多点分离与跟踪MOVE 事件携带批处理采样速度估算交给 VelocityTracker惯性滑动的物理来源**手写笔**走更讲究的协议支持压感、倾斜与悬浮hover事件低延迟笔迹还依赖显示侧的预测与相位对齐**键盘与鼠标**实体或蓝牙在 Android 上同样有完整支持按键有按下、重复、抬起三态焦点窗口决定谁接收**游戏手柄**与旋转方向盘把传感器语义翻译成标准按键。**输入法IME**是特殊一站它不产生原始输入而是作为输入系统的「座上宾」接管文本框的输入流——InputMethodManager 与 InputDispatcher 之间有专门的焦点与注入通道「输入法弹起把界面顶歪」「候选词卡顿」这类客诉责任边界要在这层划分。PointerChoreographer 负责的指针图标触摸点、光标形态也是这条链的末端产品。一句话输入系统不是一个「触摸屏驱动」那么简单它是多种设备、多个消费者的总调度室。[[FIG ai_input.png|图 4 输入传送带从内核到应用回调的八站与六个时间边界]] 提示排查「触控不灵」类客诉时先在六个时刻里定位慢在③④之间多为系统调度或跨进程延迟慢在⑤是应用主线程慢在⑥是渲染链路。分段计时永远比只测端到端总分有用。手势导航时代还带来一个看不见的输入消费者Predictive Back 返回预测。手指从屏幕边缘往回拖的一瞬系统就把「可能要返回」通知给应用应用提前渲染返回动画——输入管线的第一段感知直接决定了转场的跟手度。# 第 4 篇 内存管理七块田与回收队还有一个常被漏掉的输入源**输入法IME**。软键盘本身也是 InputReader 的客户端——按键先变成 KeyEvent 进输入管线再由 IME 翻译成 commitText 走 InputConnection 进应用是「输入系统」与「编辑系统」的接缝卡顿常发生在接缝处。游戏手柄与电视遥控则是多输入源并存的代表一套代码要同时容忍触摸、键、轴三类事件派生事件的统一坐标系就靠 PointerChoreographer 这层换算。## 4.1 先统一口径内存的七种「田」Android 谈内存第一件事是分清口径——同一块 RAM 在不同报表里名字不同。原书按内存域memory domain划分**Java Heap** 是 ART 管理的对象堆**Native Heap** 是 C/C 代码 malloc/new 出来的**Graphics** 是图形缓冲区Gralloc/dma-bufGPU 与应用共享**Code** 是代码与库映射**Stack** 是线程栈**System** 是系统分配剩下归 **Unknown**。统计口径上**VSS** 是进程虚拟地址空间含未驻留的虚胖**RSS** 是实际驻留物理内存共享库在每个进程重复计**PSS** 把共享页按使用人数均摊跨进程加总最合理**USS** 是进程独占部分**SwapPss** 是被换出到 zram 压缩区的部分。看内存请先问「哪个域、哪个口径」否则「降了 200 MB」可能是把共享库从 RSS 算成了 USS 的统计魔术。| 口径 | 一句话含义 | 什么时候用 ||---|---|---|| Java Heap | ART 管理的 Kotlin/Java 对象 | 泄漏、GC 抖动 || Native Heap | C/C 原生分配 | 图片解码、第三方 so || Graphics | 图形缓冲区可跨进程共享 | 渲染、相机、视频 || PSS | 均摊共享页后的物理占用 | 跨进程比较、线上上报 || SwapPss | zram 压缩交换部分 | 低内存设备的真实压力 |## 4.2 低内存杀手lmkd、PSI 与回收链路当内存吃紧Android 有一条完整的「减负流水线」。用户态的 **lmkd**低内存守护进程依据内核的 **PSIPressure Stall Information压力停顿信息**判断系统是否卡顿再按进程的 oom_score_adj越后台分越高从最不重要的开始杀先杀缓存进程再杀服务绝不动前台。内核侧则在杀进程之前先自救**kswapd** 后台线程异步回收页缓存回收不及时就由触发分配的线程自己**直接回收direct reclaim**——这就是卡顿现场里「主线程莫名卡在内存分配」的来源**内存整理compaction**收拾碎片好分配大块连续内存**zram** 把不活跃页压缩后进交换区小内存设备的救命稻草但吃 CPU。**LMK zram 后台限制**共同构成 Android「不死机但会杀后台」体验的政策根源也解释了为什么「内存够用」的旗舰机照样杀后台——那是策略不是 bug。## 4.3 缓存进程与冻结被杀之前后台应用还有两级缓冲。第一级是**缓存 LRU**退到后台的进程变成 cached 进程挂在 LRU 表上内存紧张时被 lmkd 收割所以你切回微信偶尔要「重新启动」。第二级是 **Cached App Freezer**应用冻结Android 11 引入把 cached 进程整个冻进 cgroup freezer不再消耗 CPU 调度——它省电但给「推送延迟」「后台任务不执行」的客诉提供了新来源任务没被杀只是被冻僵了解冻要靠下一次交互触发。Android 14 起对缓存进程数量也加了更严的限制后台保活的传统手艺双进程互拉、前台服务伪装在新版本上基本失效。## 4.4 新硬件规则16KB 页、MTE 与终结者线程两条硬件级规则正在改变内存问题形态。其一**16 KB 页**Android 15 起支持、新设备普及系统页大小从 4 KB 提到 16 KB同样的数据占用页数变少、TLB 命中率提高但**未按 16 KB 对齐编译的原生库会直接装不上**——纯 so 的应用要重建混编工程要逐库核对。其二**MTEMemory Tagging Extension内存标签扩展**Armv9 用硬件标签捕捉越界与释放后访问是 Native 内存安全的「测谎仪」级工具配合 GWP-ASan 的抽样保护让一类过去只能靠玄学复现的野指针错误有了确定性现场。最后补一个 Java 世界的坑**FinalizerDaemon** 是 ART 跑 finalize() 的守护线程依赖终结器释放资源文件句柄、Bitmap 时代的像素既慢又不可靠——终结器队列一积压内存看着占用高、对象却「死不透」现代代码应一律改用 try-with-resources 或 Cleaner 显式管理。## 4.5 内存的观测口分清了田与账还得知道田在哪看。系统侧**dumpsys meminfo 包名**按域Java/Native/Graphics……输出进程的 PSS 明细dumpsys meminfo 不带包名则给整机总览与 lmkd 阈值**ActivityManager.getMemoryInfo()**给应用一个「系统还剩多少」的粗信号onTrimMemory() 回调则让应用按级别UI 隐藏、RUNNING_LOW、RUNNING_CRITICAL主动释放缓存——用好它是免费的体面。**procfs 一族**/proc/self/status 的 VmRSS、smaps 的逐映射明细适合脚本化采样logcat 里 lmkd 的 Kill 记录直接告诉你「谁、何时、因为什么分被杀」。应用内**Android Studio Profiler** 实时曲线、heap dump 看 Java 域**heapprofd**第 11 篇采样 Native 分配。内存监控的黄金组合是「域报表 退出原因 压力信号」三件套知道自己占多少、上次怎么死的、系统紧不紧。[[FIG ai_memory.png|图 5 内存的七种「田」与低内存减负流水线]] 注意「内存大」不等于「内存有问题」。排查时先看 SwapPss 与 direct reclaim 事件SwapPss 高说明真实压力大direct reclaim 出现在主线程说明分配被卡——这两组信号比单纯的 PSS 曲线更有诊断价值。