Android Framework 的问题往往顺着 init、system_server、AMS/WMS、SurfaceFlinger 这条链路追下去才见根因,出问题时该 dumpsys 哪个服务是排查的第一抓手。全文分三条主线:先走通开机链路(启动流程 → 开机动画 → 系统预置 app),再逐个拆核心服务(AMS / PMS·PKMS / WMS / IMS)与图形合成(SurfaceFlinger),下沉到 Native 层(Thread 实现与 CallStack 调试),最后落到窗口形态(多屏 / 自由窗口 / 分屏)与构建系统(Android.mk)。文末附一张「系统服务职责速查表」。
一、开机链路:从 init 拉起系统到 Launcher 与开机动画
Android 启动从 Bootloader 到 kernel,再由 init 解析 init.rc 拉起 servicemanager、zygote、surfaceflinger。Zygote 孵化 system_server,后者启动 AMS/PMS/WMS 等核心服务,然后进入 SystemServer 的 systemReady。之后 AMS 启动 Launcher 与锁屏。
这条链路上,开机动画是 init 拉起的 native 进程 BootAnimation,它读 /system/media/bootanimation.zip:desc.txt 描述宽高、fps 与 part 播放次数,然后是 png 序列。SurfaceFlinger 把内容合成到开机动画 layer,system_server 就绪后发退出信号。定制时需要注意 zip 必须未压缩存储、留意 RGB565/PNG 体积与循环次数;车机可能要多屏各放一套。
与开机链路强相关的是系统预置 app:它们放进 system/priv-app 或 product/app,由 PMS 扫描并授予 privileged 权限。privapp-permissions.xml 必须白名单,否则 Android 8 后会拒绝启动;签名可用 platform key 获得 systemuid。车机常预置 CarService 客户端与地图,要注意开机广播过多会拖慢启动。
两个容易踩的坑
- 开机动画卡住不消失,多半是 boot complete 没到、SurfaceFlinger 或动画进程崩溃:先确认
BootAnimation进程是否还在,调试用logcat -s BootAnimation、stop bootanim;别在 zip 里塞超大图,会导致低配设备掉帧。 - 改 zygote 参数要谨慎,它影响所有 App 的孵化;OTA 后第一次启动更慢,是因为
dex2oat。
什么时候用:做系统定制、车机/电视 ROM、预置应用与开机体验优化时这套链路每天都要碰。纯应用开发通常不需要深入 init.rc,但理解「我的 App 为什么这么晚才起来」会很有帮助。
二、包管理服务 PMS/PKMS:扫描、权限与安装
PMS 即 PackageManagerService,是安装、卸载、查询组件与权限的核心。在不少文档里它也被简写成 PKMS——两者指的就是同一个服务,只是称呼不同。它开机扫描各分区 APK,维护 Settings.xml;安装会话走 PackageInstaller,由 installd 执行 dexopt 与目录创建。开机扫描 system/priv-app 与 data/app,生成 PackageSetting。
签名校验、split apk、instant app 都在这层,查询组件用 resolveIntent。现代代码里权限子系统已拆到 PermissionManagerService,但包状态仍在 PMS。instant 与 apex 增加了扫描复杂度;开机慢常因扫描与 dexopt。
三个容易踩的坑
- 签名 scheme v2/v3 失败会静默,排查用
dumpsys package与logcat PackageManager,别只盯着安装界面。 - 不要在主线程同步 install,也不要在主线程做大量
queryIntentActivities——会直接卡 UI。 - 预置 app 升级失败多半是版本码或签名不一致;OTA 后
ag/pm与包可见性(Android 11)是应用查不到组件的常见原因。
什么时候用:做预置应用、插件化/动态下发、包可见性适配、安装流程定制时必看。普通业务开发遇到「装不上/查不到」也应先来这里定位。
三、进程与 Activity 管理:AMS
AMS 管理进程、Activity 栈、Service 与广播。startActivity 会校验 Intent、权限、任务亲和,再通知 WMS 做窗口切换。进程优先级与 oom_adj 由它与 LMK 协同。四大组件生命周期最终在 ActivityThread 被调度,前台服务、后台限制与 Standby Bucket 都在策略层。
多用户与多显示让 Task 模型更复杂,读代码时先抓住 RootWindowContainer 与 Task。
三个容易踩的坑
- 卡顿可能是 AMS 锁被持太久,Binder 调用进 system_server 时要防锁顺序。
- 广播队列爆满会拖死系统,不要无脑发全局广播。
- 导出组件务必收紧权限,否则就是攻击面。
什么时候用:做进程保活、前后台调度、多用户/多窗口、启动性能与卡顿分析时。纯 UI 开发了解生命周期即可,但要排查「为什么被杀了/为什么启动慢」就得进 AMS。
四、窗口管理:WMS 与层级体系
WMS(WindowManagerService)管理窗口层级、动画、输入焦点与屏幕配置。应用通过 WindowManager 加 View,最终变成 WindowState;它与 SurfaceFlinger 用 SurfaceControl 通信,与 AMS 协同 Activity 可见性。
层级从低到高大致是:壁纸、应用、系统栏、屏上键盘、Toast。Insets 与 cutout 在这里计算。多显示、分屏、自由窗口都是 WMS 的 WindowContainer 树演进——理解这棵树,多窗口相关的问题才有抓手。
三个容易踩的坑
- 卡顿常出在
relayoutWindow与动画,dumpsys window是第一手资料。 - 改系统栏行为要用
WindowInsetsController,不要直接改属性,否则升级必冲突。 - 输入分发失败先看焦点窗口是否被挡——这往往不是输入的问题,而是 WMS 层级的问题。
什么时候用:做悬浮窗、系统栏定制、多窗口、输入焦点问题时。纯应用布局一般不需要,但「点不动/焦点乱跳」基本都从 WMS 查起。
五、输入系统:IMS
IMS 在 Android 里常指 InputManagerService,负责按键、触摸与传感器输入。事件从 EventHub 读 /dev/input,经 InputReader 映射成 Android 事件,再由 InputDispatcher 发给焦点窗口;WMS 提供焦点与窗口可点区域。
定制按键布局用 .kl/.kcm 文件;多屏要指定 displayId。手势导航与指针捕获也走 IMS。
三个容易踩的坑
- ANR 的 "Input dispatching timed out" 就出在这层,别只去查应用主线程——焦点窗口被挡或 InputDispatcher 卡住都会触发。
- 改键值不要只改应用层,要看
.kl/.kcm与 IMS。 - 部分事件注入需要
INJECT_EVENTS权限;车机的旋钮、旋编码器、触控板要单独InputDevice。
什么时候用:做遥控器/旋钮/触控板等定制输入、按键映射、手势导航、排查 ANR 与输入丢事件时。普通触摸开发用不上,但车机/电视/大屏设备绕不开。
六、图形合成:SurfaceFlinger 启动与工作原理
SurfaceFlinger 是 Android 的合成器进程,负责把各层 Surface 按 Z-order 合成到显示设备。系统启动时由 init 拉起,创建 DisplayDevice、注册到 Binder 服务 SurfaceFlingerAIDL,并等待 HWComposer 的 VSYNC。
工作路径大致是:应用通过 BufferQueue 交付 graphic buffer,SurfaceFlinger 在 VSYNC 时采样、计算可见区域,能 HWC overlay 的层交硬件,否则走 GLES 合成。掉帧往往出在应用交付过晚、GPU 合成过重或主线程阻塞。
三个容易踩的坑
- 减少无效半透明层——多层半透明叠加会被迫走 GLES 合成,吃掉性能。
- 避免频繁
lockCanvas,把动画留在应用进程内完成,别把合成压力甩给 SF。 - 调试看
dumpsys SurfaceFlinger与 systrace 的 SF 轨道,比猜强。
什么时候用:做图形性能、掉帧/卡顿、多屏合成、Surface 相关问题时。应用层只关心自己的 Surface,但想搞懂「为什么整屏掉帧」必须到这一层。
七、Native 层:Thread 实现与 CallStack 堆栈调试
Android Native 线程通常封装 pthread:libutils 的 Thread 类提供 run/readyToRun/requestExit;Java 层 Thread 最终落到 ART 的 nativeCreate。系统服务里还用 ThreadPool 与 Binder 线程池。要注意线程名(pthread_setname_np)方便 systrace,优先级用 setpriority 或 cgroup。Looper/MessageQueue 的 nativePollOnce 是典型阻塞点,不要在回调里做重活。
定位 Native 问题靠 CallStack——它在 libutils 里用 _Unwind 或 libunwindstack 抓 native 回溯。构造 CallStack 对象再 log 或 dump,常用于 Binder 超时、死锁与「谁在持锁」,比 Java 异常栈更能看到 JNI 下面。
三个容易踩的坑
- 在信号处理器里 unwinding 不安全,优先在线程上下文抓;打印前确保已链接
libunwindstack并在调试版打开符号,release 可 strip 但保留.gnu_debugdata。 - 脱离
Thread自己pthread_create时记得处理join/detach,以及与 JVMAttachCurrentThread的配合,否则 JNI 会崩。 CallStack是主动埋点,debuggerd的 tombstone 是崩溃后——两者互补,结合 ATRACE 与 systrace 才能定位卡顿调用链。车机上常把前者封装成LOG_CALLSTACK宏。
什么时候用:做 Native 崩溃、死锁、Binder 超时、性能卡顿定位时必须用。纯 Java 业务基本碰不到,但凡下沉到 C++ 服务层就绕不开。调试工具链:debuggerd、tombstone、CallStack。
八、窗口形态实战:多屏、自由窗口与分屏
这三种形态本质都是 WMS 的 WindowContainer 树在不同配置下的表现,但应用侧适配点各不相同。
多屏显示由 DisplayManager 与 WMS 协同:主屏之外可挂 HDMI/虚拟屏。Activity 用 launchDisplayId 指定目标屏,Presentation 适合副屏独立 DecorView;系统层 DisplayContent 各自有一套窗口栈与密度。车机/电视常把仪表与中控拆成不同 displayId。虚拟屏 VirtualDisplay 把内容投到 Surface,常配合投屏与录屏。调试用 dumpsys display 与 adb shell am start --display。注意输入焦点一次只在一个 display 上,应用要适配不同 dpi 与方向,避免把主屏 Activity 直接搬过去导致资源错乱。
自由窗口(Freeform)让 Task 以浮动矩形存在,标题栏可拖动缩放。桌面模式/平板与部分车机开启 persist.sys.freeform;WMS 用 WindowConfiguration.windowingMode=FREEFORM,bounds 存在 Task。应用要声明 resizeable 并正确处理最小尺寸;焦点、输入法与多窗口裁剪容易出问题。与分屏不同,自由窗口数量不限但受内存与合成器限制。调试 dumpsys activity activities 看 windowingMode,OEM 常改 DecorCaption,升级 AOSP 时注意冲突。
分屏(Split Screen)把屏幕划成两个 Task 区域,WMS 用 Task 与 ActivityRecord 维护左右/上下栈。应用需 resizeableActivity,否则会被强制全屏或拒绝进入分屏;多窗口下 onConfigurationChanged 会频繁触发。N 以后系统用 Divider 拖动分隔条,Android 12L 起大屏默认更激进。系统签名应用可用 ActivityOptions 指定相邻任务。
三个容易踩的坑
- 多窗口都要
resizeable:分屏下onConfigurationChanged频繁触发,要改用自适应布局而不是写死 dp,并保存状态避免分屏时重建丢失。 - 分屏与画中画、自由窗口互斥策略因 OEM 而异,测试要覆盖拖入、交换、主屏旋转。
- 副屏是否允许触摸、焦点只在一个 display 上——多屏适配最容易忽略的是输入与权限隔离。
什么时候用:做车机/电视/大屏/桌面模式、多屏互动、自由窗口与分屏适配时。手机单屏应用基本不需要,但横屏大屏设备几乎必做。
九、构建系统:Android.mk 文件解析
Android.mk 是旧版 NDK/AOSP 的 Make 描述文件,用来声明模块名、源文件、头文件路径和依赖库。LOCAL_PATH、CLEAR_VARS、BUILD_SHARED_LIBRARY 这类宏决定最终产出 so、静态库还是可执行文件。
阅读时先看 LOCAL_MODULE 和 LOCAL_SRC_FILES,再顺着 LOCAL_SHARED_LIBRARIES 找链接关系。条件编译常用 TARGET_ARCH、BOARD 变量,同一份 mk 可能在不同产品上走出完全不同的模块集合。
三个容易踩的坑
- 改依赖前先整编验证,避免只改一处导致隐式包含的模块丢失。
- 新项目更推荐
Android.bp,但存量平台代码仍大量保留 mk,迁移要量力而行。 - 同一份 mk 在不同产品上模块集合可能完全不同,别用一台设备的编译结果去推断另一台。
什么时候用:维护 AOSP/平台 Native 模块、移植 so、改编译依赖时。纯应用开发用 Gradle 即可,但碰到底层模块或系统编译就躲不开 mk 与 bp。
附:系统服务职责速查表
| 服务/组件 | 全称 | 负责什么 | 出问题时先看什么 |
|---|---|---|---|
| init | init 进程 | 解析 init.rc,拉起 servicemanager、zygote、surfaceflinger |
init.rc service 依赖、boot 卡住 |
| Zygote | — | 孵化 system_server 与所有 App 进程 | 改 zygote 参数影响全部 App 孵化 |
| AMS | ActivityManagerService | 进程、Activity 栈、Service、广播、oom_adj | dumpsys activity、AMS 锁持有时长、广播队列 |
| PMS / PKMS | PackageManagerService | 安装/卸载、组件与权限查询、分区扫描 | dumpsys package、logcat PackageManager、版本码/签名 |
| WMS | WindowManagerService | 窗口层级、动画、输入焦点、屏幕配置 | dumpsys window、relayoutWindow、焦点窗口 |
| IMS | InputManagerService | 按键/触摸/传感器输入与分发 | dumpsys input、getevent、ANR 输入超时 |
| SurfaceFlinger | — | 多 Surface 按 Z-order 合成到显示设备 | dumpsys SurfaceFlinger、systrace SF 轨道、VSYNC |
| BootAnimation | — | 开机动画 native 进程,读 bootanimation.zip | logcat -s BootAnimation、stop bootanim、boot complete |
| DisplayManager / WMS | — | 多屏、虚拟屏(VirtualDisplay)、DisplayContent |
dumpsys display、am start --display、displayId |
| libutils Thread / CallStack | — | Native 线程封装与 native 回溯 | debuggerd、tombstone、CallStack、systrace |
Android Framework 实战全景:从开机链路、核心服务到窗口形态
https://lautung.com/archives/android-framework-in-practice
评论