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 拉起 servicemanagerzygotesurfaceflinger。Zygote 孵化 system_server,后者启动 AMS/PMS/WMS 等核心服务,然后进入 SystemServersystemReady。之后 AMS 启动 Launcher 与锁屏。

这条链路上,开机动画是 init 拉起的 native 进程 BootAnimation,它读 /system/media/bootanimation.zipdesc.txt 描述宽高、fps 与 part 播放次数,然后是 png 序列。SurfaceFlinger 把内容合成到开机动画 layer,system_server 就绪后发退出信号。定制时需要注意 zip 必须未压缩存储、留意 RGB565/PNG 体积与循环次数;车机可能要多屏各放一套。

与开机链路强相关的是系统预置 app:它们放进 system/priv-appproduct/app,由 PMS 扫描并授予 privileged 权限。privapp-permissions.xml 必须白名单,否则 Android 8 后会拒绝启动;签名可用 platform key 获得 systemuid。车机常预置 CarService 客户端与地图,要注意开机广播过多会拖慢启动

两个容易踩的坑

  • 开机动画卡住不消失,多半是 boot complete 没到、SurfaceFlinger 或动画进程崩溃:先确认 BootAnimation 进程是否还在,调试用 logcat -s BootAnimationstop bootanim;别在 zip 里塞超大图,会导致低配设备掉帧。
  • 改 zygote 参数要谨慎,它影响所有 App 的孵化;OTA 后第一次启动更慢,是因为 dex2oat

什么时候用:做系统定制、车机/电视 ROM、预置应用与开机体验优化时这套链路每天都要碰。纯应用开发通常不需要深入 init.rc,但理解「我的 App 为什么这么晚才起来」会很有帮助。


二、包管理服务 PMS/PKMS:扫描、权限与安装

PMS 即 PackageManagerService,是安装、卸载、查询组件与权限的核心。在不少文档里它也被简写成 PKMS——两者指的就是同一个服务,只是称呼不同。它开机扫描各分区 APK,维护 Settings.xml;安装会话走 PackageInstaller,由 installd 执行 dexopt 与目录创建。开机扫描 system/priv-appdata/app,生成 PackageSetting

签名校验、split apkinstant app 都在这层,查询组件用 resolveIntent。现代代码里权限子系统已拆到 PermissionManagerService,但包状态仍在 PMSinstantapex 增加了扫描复杂度;开机慢常因扫描与 dexopt

三个容易踩的坑

  • 签名 scheme v2/v3 失败会静默,排查用 dumpsys packagelogcat 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 模型更复杂,读代码时先抓住 RootWindowContainerTask

三个容易踩的坑

  • 卡顿可能是 AMS 锁被持太久,Binder 调用进 system_server 时要防锁顺序。
  • 广播队列爆满会拖死系统,不要无脑发全局广播。
  • 导出组件务必收紧权限,否则就是攻击面。

什么时候用:做进程保活、前后台调度、多用户/多窗口、启动性能与卡顿分析时。纯 UI 开发了解生命周期即可,但要排查「为什么被杀了/为什么启动慢」就得进 AMS。


四、窗口管理:WMS 与层级体系

WMS(WindowManagerService)管理窗口层级、动画、输入焦点与屏幕配置。应用通过 WindowManager 加 View,最终变成 WindowState;它与 SurfaceFlinger 用 SurfaceControl 通信,与 AMS 协同 Activity 可见性。

层级从低到高大致是:壁纸、应用、系统栏、屏上键盘、Toast。Insetscutout 在这里计算。多显示、分屏、自由窗口都是 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:libutilsThread 类提供 run/readyToRun/requestExit;Java 层 Thread 最终落到 ART 的 nativeCreate。系统服务里还用 ThreadPool 与 Binder 线程池。要注意线程名(pthread_setname_np)方便 systrace,优先级用 setpriority 或 cgroup。Looper/MessageQueuenativePollOnce 是典型阻塞点,不要在回调里做重活。

定位 Native 问题靠 CallStack——它在 libutils 里用 _Unwindlibunwindstack 抓 native 回溯。构造 CallStack 对象再 logdump,常用于 Binder 超时、死锁与「谁在持锁」,比 Java 异常栈更能看到 JNI 下面。

三个容易踩的坑

  • 在信号处理器里 unwinding 不安全,优先在线程上下文抓;打印前确保已链接 libunwindstack 并在调试版打开符号,release 可 strip 但保留 .gnu_debugdata
  • 脱离 Thread 自己 pthread_create 时记得处理 join/detach,以及与 JVM AttachCurrentThread 的配合,否则 JNI 会崩。
  • CallStack 是主动埋点,debuggerd 的 tombstone 是崩溃后——两者互补,结合 ATRACE 与 systrace 才能定位卡顿调用链。车机上常把前者封装成 LOG_CALLSTACK 宏。

什么时候用:做 Native 崩溃、死锁、Binder 超时、性能卡顿定位时必须用。纯 Java 业务基本碰不到,但凡下沉到 C++ 服务层就绕不开。调试工具链:debuggerdtombstoneCallStack


八、窗口形态实战:多屏、自由窗口与分屏

这三种形态本质都是 WMS 的 WindowContainer 树在不同配置下的表现,但应用侧适配点各不相同。

多屏显示DisplayManager 与 WMS 协同:主屏之外可挂 HDMI/虚拟屏。ActivitylaunchDisplayId 指定目标屏,Presentation 适合副屏独立 DecorView;系统层 DisplayContent 各自有一套窗口栈与密度。车机/电视常把仪表与中控拆成不同 displayId。虚拟屏 VirtualDisplay 把内容投到 Surface,常配合投屏与录屏。调试用 dumpsys displayadb shell am start --display。注意输入焦点一次只在一个 display 上,应用要适配不同 dpi 与方向,避免把主屏 Activity 直接搬过去导致资源错乱。

自由窗口(Freeform)让 Task 以浮动矩形存在,标题栏可拖动缩放。桌面模式/平板与部分车机开启 persist.sys.freeform;WMS 用 WindowConfiguration.windowingMode=FREEFORMbounds 存在 Task。应用要声明 resizeable 并正确处理最小尺寸;焦点、输入法与多窗口裁剪容易出问题。与分屏不同,自由窗口数量不限但受内存与合成器限制。调试 dumpsys activity activitieswindowingMode,OEM 常改 DecorCaption,升级 AOSP 时注意冲突。

分屏(Split Screen)把屏幕划成两个 Task 区域,WMS 用 TaskActivityRecord 维护左右/上下栈。应用需 resizeableActivity,否则会被强制全屏或拒绝进入分屏;多窗口下 onConfigurationChanged 会频繁触发。N 以后系统用 Divider 拖动分隔条,Android 12L 起大屏默认更激进。系统签名应用可用 ActivityOptions 指定相邻任务。

三个容易踩的坑

  • 多窗口都要 resizeable:分屏下 onConfigurationChanged 频繁触发,要改用自适应布局而不是写死 dp,并保存状态避免分屏时重建丢失。
  • 分屏与画中画、自由窗口互斥策略因 OEM 而异,测试要覆盖拖入、交换、主屏旋转。
  • 副屏是否允许触摸、焦点只在一个 display 上——多屏适配最容易忽略的是输入与权限隔离。

什么时候用:做车机/电视/大屏/桌面模式、多屏互动、自由窗口与分屏适配时。手机单屏应用基本不需要,但横屏大屏设备几乎必做。


九、构建系统:Android.mk 文件解析

Android.mk 是旧版 NDK/AOSP 的 Make 描述文件,用来声明模块名、源文件、头文件路径和依赖库。LOCAL_PATHCLEAR_VARSBUILD_SHARED_LIBRARY 这类宏决定最终产出 so、静态库还是可执行文件。

阅读时先看 LOCAL_MODULELOCAL_SRC_FILES,再顺着 LOCAL_SHARED_LIBRARIES 找链接关系。条件编译常用 TARGET_ARCHBOARD 变量,同一份 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 packagelogcat PackageManager、版本码/签名
WMS WindowManagerService 窗口层级、动画、输入焦点、屏幕配置 dumpsys windowrelayoutWindow、焦点窗口
IMS InputManagerService 按键/触摸/传感器输入与分发 dumpsys inputgetevent、ANR 输入超时
SurfaceFlinger 多 Surface 按 Z-order 合成到显示设备 dumpsys SurfaceFlinger、systrace SF 轨道、VSYNC
BootAnimation 开机动画 native 进程,读 bootanimation.zip logcat -s BootAnimationstop bootanim、boot complete
DisplayManager / WMS 多屏、虚拟屏(VirtualDisplay)、DisplayContent dumpsys displayam start --display、displayId
libutils Thread / CallStack Native 线程封装与 native 回溯 debuggerd、tombstone、CallStack、systrace