Android Framework 到底是什么
第 0 课:Android Framework 到底是什么
在 Activity 里拿窗口服务,只需要一行:
WindowManager windowManager = getSystemService(WindowManager.class);平时写 App,拿到 WindowManager 之后直接调用就行,很少有人追问这段代码最终由谁执行。可窗口位置不对、焦点没切过去,或者 setContentView() 之后迟迟不出首帧的时候,只盯着 Activity 很快就查不下去了。
这类问题常见的走向是:反复改 View 层,最后才发现全局窗口状态并不归应用管。问题出在手里那个对象看起来像普通的本地对象,实际上它只是入口。应用拿到的是 Framework 暴露的 API,请求会不会留在当前进程、会不会经 Binder 进入系统服务、要不要继续走到 Native 服务或内核,只能沿着具体的调用链去看。
所以学 Framework 最先要改的习惯是:看到一行 API,不要只问“下一步调哪个方法”,还要问它属于哪一层、运行在哪个进程、此刻由哪个线程执行。
本章目标:得到一张能用于源码阅读和问题定位的地图,分清系统层、源码目录、进程和调用边界。
本章边界:这里只搭全景,不展开 Binder 驱动、Handler 循环、JNI、Context 或 WMS 的内部实现。
先把 Framework 放回 Android 系统里
很多入门资料会把 Android 画成几层,Framework 夹在中间。这张图没有错,容易出错的是读图的方式。一层并不等于一个进程,一套 API 也不等于一段只在本地执行的代码。

应用层的 Activity、Service、View 和业务代码,通常运行在应用自己的 Linux 进程里。Context、WindowManager、ActivityManager 这类对象也有一部分代码在应用进程中执行,它们负责提供稳定的 API,做参数整理、状态缓存或请求封装。
设备级状态则不能让每个 App 各管一份。应用生命周期、任务与进程、全局窗口状态都需要统一协调,于是 Android 把大量主要 Java 系统服务放进 system_server,AMS、ATMS、WMS 就属于这一类。Framework API 是应用看到的入口,真正掌握全局状态的往往是系统服务。两者之间可能经 Binder 连接,也可能是本地调用,这个判断不能只靠类名。
再往下是 Native 服务和运行库。SurfaceFlinger 是独立进程中的 Native 服务,应用进程和 system_server 也都会加载 Native 库。看到 C/C++ 就认为发生了跨进程,是很常见的误判。Native 描述的是代码形态和运行环境,有没有跨进程,要看后面是否还有 IPC。
Linux Kernel 提供进程调度、虚拟内存、文件系统和设备驱动。用户空间进程通过系统调用进入内核,Binder 驱动也在这一层。“进入内核”和“进入另一个服务进程”是两种不同的边界,写调用链时要分开标。
综合来看,Android Framework 并不是某一个类,也不只是一个 Java API 包。它是一组把应用和系统能力连接起来的 API、系统服务与协作机制,代码分布在 Java、Native 和内核相关模块中,运行时则落在不同进程和线程上。
从 AOSP 根目录找到 Framework
知道了系统分层,打开 AOSP 时第二个坑就来了:frameworks/base 很重要,却不等于整个 Android Framework。
AOSP 根目录按源码模块和构建边界组织,Java API、Java 系统服务、Native 基础库、媒体、运行时、HAL 和内核并不放在同一个目录。下面这张表是“第一次该去哪里找”的索引,不是目录百科:
| AOSP 目录 | 主要负责什么 | 第一次可以看的入口 | 典型运行位置 |
|---|---|---|---|
frameworks/base/ | Java Framework API、主要 Java 系统服务、系统资源与部分命令 | core/java/、services/、packages/ | 应用进程、system_server 等 |
frameworks/native/ | Native Framework 基础库、Native Binder、Surface 与图形合成服务 | libs/binder/、libs/gui/、services/surfaceflinger/ | 应用或系统进程加载的 Native 库、SurfaceFlinger 进程 |
frameworks/av/ | 音视频采集、播放、编解码、媒体服务及相关客户端库 | media/、services/ | 应用进程、媒体相关服务进程 |
packages/modules/ | 通过 Mainline 等机制模块化交付的系统能力 | Connectivity/、Permission/ | 取决于模块,可能进入应用、系统服务或独立进程 |
system/core/ | Android 用户空间的基础程序和库,例如 init、adb、日志 | init/、adb/、liblog/ | init、adbd 及加载相关库的进程 |
art/ 与 libcore/ | ART 运行时、DEX 执行、垃圾回收,以及 Java 核心类库实现 | art/runtime/、libcore/ojluni/ | 每个使用 ART 的 Java 进程 |
hardware/interfaces/ | Framework 与硬件实现之间的标准 HAL 接口定义 | 各能力目录下的 AIDL/HIDL 接口 | Framework 客户端与 HAL 服务进程之间 |
| 内核源码 | 进程、内存、文件系统、设备驱动和 Binder 驱动 | drivers/android/ | Linux Kernel 内核空间 |
实际查代码时,比较稳的做法是先按问题类型选入口。问题来自 App 调用的 Java API,就先看 frameworks/base/core/java/;涉及 AMS、WMS 这类主要 Java 系统服务,再转到 frameworks/base/services/;图形、Native Binder 或 Surface 相关的问题通常进入 frameworks/native/;媒体看 frameworks/av/;运行时和核心类库则从 art/、libcore/ 缩小范围。
这个顺序的作用是减少无效搜索,它并不是目录与进程的固定对应。Mainline 还在不断调整代码边界,一部分能力已经搬进 packages/modules/。内核也可能来自单独的 Android Common Kernel 或设备厂商仓库,未必和平台源码在同一个 checkout 里。
源码目录不等于运行进程
这一节是全文最值得记住的地方。

frameworks/base/core/java/ 构建出的类,可能被加载进应用进程,也可能加载进 system_server;frameworks/native/libs/ 里的库同样会被多个进程使用。目录回答的是“代码在哪里维护”,进程回答的是“构建之后的代码此刻在哪里执行”。两者有关联,却不是一一对应。
反过来也成立。知道异常发生在 system_server,并不意味着只要搜一个源码目录就够了。这个进程里同时有 Framework Java 类、ART、Native 库和模块化组件,只凭进程名去猜源码位置,通常会漏掉真正的调用节点。
排查时按三种起点分别处理:
- 已知类名或接口:先从源码路径确认所属模块,再沿调用方和实现方判断进程。
- 已知崩溃进程或 ANR 进程:先看线程栈和调用上下文,再回到对应的源码模块。
- 只有用户现象:先找离现象最近、又足够稳定的入口,不要一上来全局搜索一个很宽的词。
按目录硬读,看不见运行边界;只盯进程和日志,又找不到代码从哪里构建出来。上面的顺序是在两者之间取一个折中。
用一次窗口请求练习定位
回到开头的 getSystemService(WindowManager.class)。这一篇不追具体方法内部,只用它演示怎样缩小范围:
应用侧 WindowManager / Context API
→ frameworks/base/core/java/(应用进程中的 Java Framework 入口)
→ frameworks/base/services/(system_server 中的 WMS 等服务)
→ frameworks/native/libs/gui/ 与 services/surfaceflinger/
(进程内 Native 客户端能力或 SurfaceFlinger 独立进程)
→ 内核显示驱动、Binder 驱动等能力这条路径不代表每个窗口 API 都会完整走一遍,也不代表 WMS 直接负责图形合成。它表达的是排查顺序:先确认应用侧入口,再找管理全局状态的系统服务;问题如果已经进入图形提交或合成阶段,再继续追 Native 服务和内核能力。
窗口焦点不对的时候,只在 View 或 Activity 里改代码通常不管用,因为全局窗口状态由系统侧掌握。反过来,如果应用根本没有正确提交窗口请求,一上来就去改 WMS,风险大,也很难长期维护。先判断链路停在哪一层,再决定改 App 还是改 Framework,这一步在读源码和真正动手改系统时都要守住。
后面的专题会分别展开 Binder 跨进程机制、WindowManagerService 的窗口职责,以及 SurfaceFlinger 的图形合成职责。
一次请求到底跨了什么边界
Framework 代码里经常同时出现 Handler、Binder、JNI 和系统调用。它们都能改变执行的位置,但改变的维度不一样。统统写成“交给系统处理”,调用链就失去了定位价值。

普通本地调用不会自动切线程,也不会自动跨进程。方法名叫 start、send 或 request,都不能拿来当边界证据。
Handler解决的是同一进程内的线程协作。消息进入目标线程关联的队列,之后由目标线程取出执行,它跨的是线程,不会把请求送到另一个进程。
Binder用于进程间通信。调用方看起来仍像在调用一个方法,服务端代码却运行在另一个进程里,并且有自己的线程上下文。读 Binder 链路时,至少要把调用进程、服务进程以及两边各自的线程分开记录。
JNI跨越的是 Java/Kotlin 与 C/C++ 之间的语言边界。Native 库若加载在当前进程中,调用仍然留在同一个进程和线程里,之后是否跨进程,要看有没有继续使用 Binder 等 IPC。
系统调用把执行从用户空间带进内核。应用进程、system_server 和独立的 Native 服务都属于用户空间,只有需要文件、内存、调度或驱动能力时才会进入内核。进入内核并不等于进入了另一个用户空间服务。
调用链最容易出问题的地方,往往不是概念不清,而是画图时只写类名,边界没有落在线上。等到排查 ANR、死锁或状态不同步时再回头猜线程和进程,成本会高很多。
画调用链时,每个节点写清四件事
遇到一条长链,先别急着抄满屏的方法名。给每个关键节点写一行:
[系统层] [进程] [线程] 类.方法()跨过边界时,在连接线上标出 Handler、Binder、JNI、回调或“系统调用”。当前源码只能证明到某个节点时,就停在那里,把后面标成待确认,不要用经验补齐。
这样记录会慢一点,但之后排查能省下不少时间。它能直接告诉你下一步该看应用日志、system_server 的线程栈、Native 服务的状态,还是内核侧的证据。笔记里至少要能回答下面几个问题:请求从哪个 App API、系统事件或日志现象开始?当前节点真正运行在哪个进程、哪个线程?这一跳传递的是方法调用、消息、Binder 事务,还是语言或内核边界?哪个对象持有状态,状态在什么条件下变化?如果结果不对,链路上最后一个有证据的正常节点在哪里?
本篇要带走的三件事
frameworks/base是重要入口,但不代表整个 Android Framework。- Framework API、系统服务、源码目录和运行进程是四个不同的维度,不能互相替代。
- Handler、Binder、JNI 和系统调用跨越的边界各不相同,调用链上必须把它们标出来。
如果这三点已经能用自己的话讲清楚,就可以进入下一篇,沿开机时间线看第一条系统链路。如果还混在一起,就拿一个熟悉的 API,把它的系统层、源码目录和运行位置各自标出来,标不出来的地方正是要补的地方。
本章涉及的源码指针
本章只保留能承接后续专题的代表入口,内部实现留到后面展开:
| 路径 | 后续阅读的问题 |
|---|---|
frameworks/base/core/java/android/content/Context.java | 应用看到的 Framework 能力入口如何定义 |
frameworks/base/services/java/com/android/server/SystemServer.java | 主要 Java 系统服务如何被组织和启动 |
frameworks/native/libs/binder/ | Java 服务之外的 Native Binder 基础能力如何组织 |
frameworks/native/services/surfaceflinger/ | 独立 Native 图形服务如何组织 |
frameworks/av/media/ | 媒体能力的客户端与公共实现从哪里进入 |
system/core/init/ | Android 用户空间的第一个进程如何建立运行环境 |
art/runtime/ 与 libcore/ojluni/ | Java 代码依赖的运行时和核心类库如何实现 |
hardware/interfaces/ | Framework 与硬件服务之间的标准接口在哪里定义 |
drivers/android/binder.c | Binder 驱动如何承接用户空间事务 |
源码基准为 Android 14 android-14.0.0_r75。系统分层与进程模型可以对照 Android 官方的 AOSP 架构概览、Binder 概览 和 进程与线程概览。