本文根据红橙Darren 的直播课程《Android Framework 学习启动篇》系列整理,三讲分别讲必备前提条件、Android Framework 简介与怎么入手学习。
ImageView 设置 android:adjustViewBounds="true" 后,宽度铺满屏幕、高度按图片比例自适应,看起来只是一个布局小问题。可一旦图片尺寸、scaleType、maxWidth/maxHeight 混在一起,很多人第一反应是写自定义 View。这个反应不算错,但它暴露了一个更大的问题:应用层经验够用时,我们习惯在 API 表面打补丁、找问题;但是问题进入边界条件、系统版本差异、Native 崩溃、启动链路、Binder 或窗口管理后,不读 Framework 源码基本就是猜。
为什么要读 Framework 源码
Framework 对 App 高级工程师来说,是必修课里的选修内容。日常业务不一定每天改系统源码,但源码能把很多“玄学问题”变成可验证的调用链。开发中遇到的布局异常、生命周期错乱、bad token、ANR、SO 加载失败,Bugly 上那些只给一段栈的崩溃,最终都落到同一件事:系统在什么状态下调了谁,谁持有什么对象,跨进程或跨语言边界时参数变成了什么。
它也是面试里的分水岭:Framework 基础不过关,其他方向再强,面试也大概率很难通过。

源码的价值不止排 bug。性能优化要看真实路径,不能只靠“少创建对象”这种泛泛建议;SDK 设计要看系统怎么分层,怎么把扩展点、反射创建、回调和线程模型拆开;一些看似做不到的能力,比如资源热修复、SO 热修复、高性能日志库,也都离不开对加载、缓存、IO 和进程模型的理解。Dlog 这类日志库的思路就是典型例子:日志先进内存缓冲,再批量写盘,和线程池定时 flush 的方案相比,关键差异在丢数据风险、写盘频率和调用侧开销。
例如看 ImageView 片段,重点在建立习惯:先确认系统已经提供了什么,再决定要不要自定义。
<!-- 根布局只用于承载示例 ImageView --><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 图片控件使用系统 ImageView --> <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:adjustViewBounds="true" android:scaleType="fitCenter" android:maxWidth="1080dp" android:maxHeight="1920dp" /> <!-- 根布局闭合 --></FrameLayout>这段布局的关键在于:ImageView 在测量阶段会把 wrap_content 和最大宽高一起纳入计算,尽量保留图片比例,而不是粗暴拉伸。
再看 AOSP 里 ImageView 对默认焦点高亮的判断,代码很短,但它体现了系统控件常见的写法:先看自身 drawable 的状态,再把结果交回父类合并。
// 标记该方法属于隐藏 API /** @hide */ // 重写父类的默认焦点高亮判断 @Override // 标记该 API 可用于测试场景 @TestApi // 根据背景和前景判断是否需要默认焦点高亮 public boolean isDefaultFocusHighlightNeeded(Drawable background, Drawable foreground) { // 当图片为空、无状态或没有指定 focus 状态时,认为缺少焦点状态 final boolean lackFocusState = mDrawable == null || !mDrawable.isStateful() // 继续判断 drawable 是否声明了 focus 状态 || !mDrawable.hasFocusStateSpecified(); // 父类允许高亮且当前 drawable 缺少焦点状态时,才使用默认焦点高亮 return super.isDefaultFocusHighlightNeeded(background, foreground) && lackFocusState; // 方法结束 }这个方法读背景、前景和当前 mDrawable 的状态,最后返回一个布尔值:系统要不要替 ImageView 补默认焦点高亮。
学习前的四个前提
第一,C/C++ 要能读懂。Android Java Framework 只是很薄的一层 API 封装,虚拟机、Binder、系统服务底层、媒体、图形、HAL 周边大量代码都在 C/C++。没有这层能力,看源码只能看见入口,看不见系统真正做事的地方。
第二,Linux 内核基础要补上。至少要理解 fork/exec 函数组、文件 IO、进程地址空间、共享内存、虚拟内存和物理内存、可执行文件怎么跑起来。Android 驱动层仍然遵守 Linux 的基本抽象:一切皆文件,Binder 驱动、输入输出设备、共享内存都可以沿着文件和系统调用去理解。
第三,最好有 1 到 3 年 Android 开发经验。没写过足够多业务,读 AMS、WMS、PMS 很容易变成背名词;有过页面重建错乱、ANR、适配问题、SO 崩溃,再去读系统源码,问题会自己带路。
第四,UML 和建模能力不能太差。Framework 不是几个类,是层、进程、服务、线程、IPC、状态机组合起来的系统。画不出结构图,脑子里就很难稳定保存全局关系。
Android 的前世今生
Android 不是 Google 从零原创出来的系统。Andy Rubin 创办 Android Inc. 之前曾在苹果工作,后来公司被 Google 收购,Android 才进入今天这条路线。Framework 里有很多核心代码来自长期系统工程积累,Dianne Hackborn 写过大量 Framework 核心代码,Binder 的思想也能追到 OpenBinder。知道这些背景不是为了讲历史八卦,而是提醒我们:Android 的很多设计不是“优雅范式优先”,而是工程稳定性、兼容性、厂商协作和硬件生态权衡后的结果。
系统分层:先有地图再深入

从上往下看,Android 大体分成 System Apps、Java API Framework、Native C/C++ Libraries 与 Android Runtime、HAL、Linux Kernel。应用工程师最熟的是前两层:四大组件、View System、资源、包管理、通知、窗口、位置、电话等 API。真正需要重点啃的是系统服务,尤其 AMS、PMS、WMS、IMS(输入管理服务 InputManagerService)。系统服务有 80 多个,但四个主干通了,很多服务的套路就能迁移。
Native C/C++ Libraries 包括 WebKit、OpenMAX AL、Media Framework、OpenGL ES、libc 等,Android Runtime 负责 Java/Kotlin 代码执行。Java 跨平台靠虚拟机,接触底层能力靠 JNI。可以把 Java API 理解成普通话,底层 C/C++ 和不同硬件能力是方言,虚拟机和 JNI 在中间做翻译和派发。
HAL 的价值是解耦和协议。Camera 从 500 万像素换到几千万像素,应用层仍然调相机 API;同一份 Android 代码能跑在不同硬件上,关键就在 HAL 把上层协议和下层驱动隔开。GPL 约束也在这里有现实意义:改内核通常要开源,不改内核、只在 HAL 或上层适配,厂商的空间就大很多。进一步理解这层,可以看老罗的《Android硬件抽象层(HAL)概要介绍和学习计划》。

到 Linux Kernel 层,就回到驱动:Audio、Binder、Display、Keypad、Bluetooth、Camera、Shared Memory、USB、WIFI、Power Management。Binder 也是驱动,打开 Binder 驱动也就是打开一个文件,再通过 ioctl 等系统调用完成 IPC。Binder 怎么工作,可以看老罗的《Android进程间通信(IPC)机制Binder简要介绍和学习计划》。Binder 是这个系列里最麻烦的部分,会用 7~10 次专门讲;把它搞懂后,其他代码就不难了。
一次 Camera.open 可以把分层串起来:Java API 经 JNI 进入 native 层后,拿到的是 CameraService 的 Binder 代理,需要先通过一次 Binder IPC 进入服务端进程(老版本是 mediaserver,Android 7.0 起是 cameraserver),再到 HAL、驱动和硬件。紧邻的 FileOutputStream.write 则是同进程路径:它进入 native 方法,通过 JNI 到本地 C/C++,再走系统调用进内核,不需要 Binder。

换成进程视角,图会更清楚:Launcher、Phone、Email、Browser 这些 App 跑在各自进程;System Server 里有 ActivityManager、WindowManager、PowerManager;Media Server 里有 AudioFlinger、MediaPlayerService;Zygote 负责孵化应用进程;Java Framework、Android Runtime 和 Native Framework 在中间把这些进程串成一个系统。

源码下载与编译
AOSP 顶层目录先认全,不要一上来扎进某个类。
# 顶层 Makefile 入口Makefile# Android C 库相关目录bionic# 启动引导相关目录bootable# 构建系统目录build# 兼容性测试目录cts# Dalvik 虚拟机历史目录dalvik# 开发工具和示例目录development# 第三方开源组件目录external# Framework 主目录frameworks# 硬件适配相关目录hardware# 编译输出目录out# 系统应用包目录packages# 预置组件目录prebuilt# SDK 相关目录sdk# 系统核心目录system这份清单给的是阅读坐标:先认顶层模块,再决定当前问题该进 frameworks/、system/ 还是 hardware/。
学习阶段只看一个版本,建议 Android 6.0。写资源热修复、SO 热修复、动态加载这种源码级能力,才需要跨版本兼容。全量源码几个 GB,frameworks/ 不到 1GB,先把主线读通比到处追版本有效。
编译可以参考老罗的《在Ubuntu上下载、编译和安装Android最新源代码》。下面命令的输入是 Ubuntu 环境和 AOSP 源码配置,输出是可刷机的系统产物,打包后就是刷机包;耗时通常按小时算。
# 安装 AOSP 编译所需依赖sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev libc6-dev lib32ncurses5-dev x11proto-core-dev libx11-dev lib32readline-glibc2-dev lib32z1-dev libgl1-mesa-dev g++-multilib mingw32 tofrodos python-markdown libxml2-utils xsltproc这条命令让系统包管理器从 Ubuntu 软件源装齐编译 AOSP 所需的基础工具链与开发库。
这份依赖列表来自老罗文章对应年代(2011 年前后)的 Ubuntu;新版系统里的 mingw32、tofrodos、python-markdown 等包名可能已经改名或下架,遇到包名找不到时,需要按当前 Ubuntu 版本替换为等价包。
# 创建本地 repo 工具目录mkdir ~/bin# 把 repo 工具目录加入当前 PATHPATH=~/bin:$PATH# 下载 repo 脚本到本地工具目录curl repo > ~/bin/repo# 给 repo 脚本增加可执行权限chmod a+x ~/bin/repo# 初始化 AOSP 仓库清单repo init -u ...# 同步 AOSP 源码repo sync# 开始编译系统镜像make其中 curl repo > ~/bin/repo 的下载地址要替换成 repo 工具的实际地址,老罗写文章那个年代给出的地址现在不一定可用。
这组命令把本地从“没有 repo 工具和源码”推到“完成源码同步、进入系统镜像编译”。repo init -u ... 里的清单地址要按实际 AOSP 源替换。
下一篇会进入开机启动,先把 init 相关路径放在这里:从系统启动后的第一个用户态进程入手,一路走到 zygote 和 SystemServer。
# init 进程 C++ 入口文件/system/core/init/Init.cpp# init 进程默认 rc 配置/system/core/rootdir/init.rc# init rc 解析逻辑/system/core/init/init_parser.cpp# init 内建命令实现/system/core/init/builtins.cpp这几个路径是阅读入口,顺着它们可以这样看:从 Init.cpp 进入进程主线,从 init.rc 看系统服务声明,再回到 parser 和 builtins 看配置如何变成动作。
怎么入手:四步路线

第一步,读 Java 和 C/C++ 层源码。别平均用力,先跟主调用链:Activity 启动、Service 管理、Window 添加、Binder 通信、资源加载、输入事件分发。配合罗升阳、Gityuan 的文章,能少走很多弯路;分层图这类结构性材料也可以参考刘望舒的博客,视频展示的“Android FrameWork 学习启动篇”系列文章就出自这里。C/C++ 基础够用时,很多内容能读到八九成,否则只能在 Java 外壳上转。
第二步,学设计思想。Activity 和 Application 为什么通过反射创建?Framework 为什么提供 Thread,而应用层只提交 Runnable?SDK 要不要自己创建上层对象,还是让调用方传进来?这些问题比背类名更有价值。MMKV 这类开源 SDK 就是现成的设计思想案例;同样的功能,大公司可能拆成三层、分别由不同的人维护,小公司则可能由一个人从头写到尾,对比着看更容易理解为什么要分层。

第三步,带着疑问分析常见问题。ANR 怎么来、7.0 的 bad token exception 为什么出现、8.0 scale canvas 为什么变了、Activity 重建后界面为什么错乱。问题越具体,源码路径越短。
第四步,动手写 SDK。资源热修复、SO 热修复、高性能日志打印库都可以做成练习。写完才知道哪些是 API 设计问题,哪些是系统限制,哪些是版本兼容成本。
版本策略要克制:学习 Framework 先固定 Android 6.0,不要今天看 8.0、明天看 12、后天又翻 master。给自己排周计划,比如一周补 Linux 内核基础,一周啃 Binder,一周读 SystemServer 启动。读源码要做笔记,否则看过和没看过差别不大。
延伸:渲染链路与跨平台
渲染这条线可以从 Surface 看起,再接 Skia、OpenGL、SurfaceFlinger。2D 里有 Skia 和 SKBitmap,3D 里绕不开 OpenGL ES,最终合成要看 SurfaceFlinger。Flutter 等跨平台方案性能好的一个原因,是它尽量绕开上层 View 体系,直接打到 native 渲染能力上。跨平台不是魔法,本质仍然是用一套上层语言和引擎去调本地能力。
结语与预告
Framework 学习的起点不是“把源码下载下来”,而是先知道分层地图、进程地图和主调用链。然后固定版本,读 frameworks/,抓 AMS/PMS/WMS/IMS,补 C/C++ 与 Linux,最后用 SDK 实战倒逼理解。这个系列会讲 C/C++ 与 Framework 源码、虚拟机、进程与系统启动、系统 App 源码;HAL 与普通驱动不在覆盖范围内,Binder 是例外,会单独细讲。下一篇从开机启动开始:init → zygote → SystemServer,这条线一通,Android 系统就不再只是一堆 API 名字。
本文由 AgentWorkFlow 生成