经常有做了两三年应用开发的同事跑过来问我:“我想转系统组,framework到底怎么学?”这个问题一问出来,十有八九他还没搞清楚framework这个词到底指哪一块代码。有人以为是Android Studio里那一堆gradle插件,有人以为是那些带hide注解的类,还有人直接在手机上开个“显示布局边界”就觉得自己在研究framework了。
这事不能怪大家。Android官方的层级图画得很漂亮,但真正落到代码上,“framework”这个词的边界非常模糊。它可以小到只指framework.jar里那几千个Java类,大到包含AOSP里从内核驱动到应用框架的全部内容。而网上各种资料又特别喜欢把“Binder”“Handler”“AMS”“WMS”“SurfaceFlinger”塞进一个标题里,搞得像背完八股就能通一样。实际上,framework学习最大的门槛不是智商,而是不知道从哪里下手,以及不知道学到什么程度算“会了”。
这篇文章我想站在一个真正做过系统定制和framework开发的工程师角度,把整条学习路径拆开讲清楚。不讲那种“第一天看Java、第二天看C++、第三天看内核”的鸡汤路线,而是告诉你哪些是必须先啃下来的硬骨头,哪些是可以先用工具绕开的坑,以及你每天在应用层写的那些代码,在framework里到底经历了什么。内容会比较长,建议收藏之后分段看。
1. 先搞懂:Android framework到底是什么
1.1 应用开发者和系统工程师嘴里的“framework”不是同一个东西
如果你一直在写应用层,你对framework的认知大概率是“系统提供给我调用的那些API”——getSystemService()、startActivity()、onCreate()这些。站在这个角度,framework就是你Android Studio里自动补全的那些类的总和。这个理解没有错,但它只讲清楚了“使用者”视角。
真正的framework,在AOSP源码里指的是从system_server进程往下到app进程之间那一大坨运行时代码。它包含两个大块:一块是运行在你应用进程里的framework.jar,比如ActivityThread、Looper、Handler、ContextImpl;另一块是运行在独立系统进程里的各种XXXService,比如ActivityTaskManagerService(简称ATMS)、WindowManagerService(WMS)、PackageManagerService(PMS)。这两块之间靠Binder通信,靠Handler做线程切换,靠各种dispatcher派发事件。
用餐厅打比方:你的App是客人,framework.jar里的ActivityThread是服务员,系统和各种服务是后厨。客人只需要跟服务员点菜,服务员负责把菜单传给后厨,后厨做完了再通过传菜窗口把菜送出来。中间传菜窗口就是Binder,负责跨进程搬运数据;而后厨的厨师之间也有各自的规矩——谁先炒、谁后炒,这就是各种Service之间的调度逻辑。
很多人学framework只盯着Activity生命周期、Context机制,这是只看到了传菜窗口的“菜”,没看到后厨怎么运作。我见过不少应用开发转岗面试,问startActivity启动流程能背出ActivityThread和AMS,但一问他system_server里还跑着哪些线程、PMS负责干什么、WMS和ATMS之间怎么配合,就答不上来了。这不是“知道”,这是“背脚本”。
1.2 哪些能力才算真正意义上的framework核心
如果把framework比作一棵树,真正的主干其实就那么几块:
- Binder:整个Android的跨进程通信骨架。没有Binder,Activity调Service、App调系统服务全部瘫痪。
- Handler/Looper/MessageQueue:单线程模型下的消息循环机制,是
system_server和应用主线程得以运转的基础。 - SystemServer:系统服务的启动器,所有核心服务都在这一个进程里创建。
- 四大组件调度:AMS/ATMS如何管理Activity任务栈,Service怎么启动绑定,BroadcastReceiver如何广播分发,ContentProvider如何跨进程访问。
- View体系与WMS:一个
View如何被测量、布局、绘制,最后通过WindowManagerService和SurfaceFlinger显示到屏幕上。 - 输入系统:从触摸屏到
InputDispatcher再到目标窗口的整条链路。 - HAL与内核接口:framework和硬件打交道的那层抽象,包含Audio、Camera、Sensor、Graphics等。
这些里面,Binder和Handler属于“内功”,必须精读源码;SystemServer和AMS属于“骨架”,要能画出调用链;View、WMS、SurfaceFlinger属于“表现层”,重点理解流程,不需要逐行背诵;HAL则偏向驱动适配,适合走系统定制方向的人深挖。
我个人的建议是:不要一上来就捧着《Android内部机制》之类的书从头翻到尾。framework源码是一棵巨大的树,不是一本需要顺序阅读的书,你要做的是顺着某一条具体行为的“调用链”去探索。比如从一次点击屏幕到界面响应的完整链路,或者从调用startActivity()到新界面显示出来的完整链路。一条链走通之后,你会发现很多知识点都是围绕这条链展开的。
2. 从应用层到系统层:一条可执行的学习路线
2.1 第一阶段:先确认应用层基本功没短板
这是一个看上去很废话但最容易翻车的建议。framework学习最怕的不是你不会底层,而是你对上层理解有偏差。比如你连Activity的四种启动模式都说不清楚,那就别指望能理解任务栈;你连Handler的同步屏障和IdleHandler都没用过,那就别指望能看懂Choreographer的帧刷新机制。
这个阶段你不需要看任何系统源码,只需要拿Android Studio做几件事:
- 写一个小Demo,用
ActivityManager的getRunningTasks()(注意高版本需要权限)或dumpsys activity activities观察Activity堆栈变化,体会不同launchMode的实际效果。 - 用
android:process=":remote"开一个子进程,在Application和Activity里各加一句日志,你会直观感受到多进程下的Application初始化,以及Binder跨进程调用时的耗时。 - 手动模拟主线程耗时阻塞,然后观察ANR弹窗的出现和
/data/anr/目录下的日志,这能帮你建立对“主线程消息循环”的敏感度。
这些实验都不需要读源码,但它们能帮你建立正确的“直觉”。有了直觉,后面读源码时你才知道这段代码在真实运行时解决什么问题。
另外提醒一句:网上那些“两小时搞定Android framework面试”的文章,你当索引目录看就行,千万别当学习材料。真正搞懂framework,至少需要你在一段时间内每天花两三个小时持续投入,没有捷径。
2.2 第二阶段:把源码环境准备好,并学会用正确方式“读”源码
读framework源码,第一件事不是下载源码,而是想清楚用什么方式读。我见过最惨痛的案例:有人辛辛苦苦下完整套AOSP,几百个G,最后因为编译环境问题卡了两个星期,连framework.jar的影子都没看到,信心直接没了。
如果只是想理解逻辑,我的建议是分两步走。第一步,先直接用网页版源码查看器,比如Android官方源码搜索网站cs.android.com,配合Google搜索定位类名和方法名,非常快。第二次再考虑拉AOSP代码到本地,用aidegen生成索引,然后导入Android Studio进行代码跳转和调试。
本地源码我目前的推荐分支是android-13.0.0_r*或android-14.0.0_r*,这两个版本代码结构清晰,网上现成的调试方案也多。下载源码前先配好repo工具,然后用镜像站同步。磁盘空间至少准备200G,编译环境推荐Ubuntu 20.04以上,内存16G起步、32G比较舒服。
源码下载完,别急着编译,先做一件性价比极高的事:打开frameworks/base/core/java/android/app/ActivityThread.java,搜main方法,然后从这个入口开始追。ActivityThread是每个应用进程的入口,从main()到attach()再到handleBindApplication(),这条线上基本覆盖了“一个App进程是怎么活起来”的全部答案。
读的时候记住一个原则:不要逐行读,要“带问题读”。比如你问“Application和Activity的onCreate谁先执行?”然后带着这个问题去追调用链,从ActivityThread.performLaunchActivity()里你会看到appContext先创建、Instrumentation.callApplicationOnCreate()后调用的顺序。这个过程远比背文字更有用。
2.3 第三阶段:Binder和Handler是绕不过去的两座山
如果让我只选两个topic作为framework的“生死线”,那就是Binder和Handler。这两个东西不理解透,后面看AMS、WMS、SurfaceFlinger全是糊涂账。
先说Binder。你要理解的不是“Binder是Android的IPC机制”这种一句话答案,而是四个层次:
第一层,进程隔离与地址空间。每个进程有自己的虚拟地址空间,用户空间不能直接访问别的进程的数据,这是Binder存在的底层原因。
第二层,Binder的通信模型。Binder把一次跨进程调用抽象成Client、Server、ServiceManager(也叫ContextManager)三方的协作:Server启动后向ServiceManager注册自己;Client通过ServiceManager查到Server的代理,然后通过这个代理发起调用。Android里面各种getSystemService()查的其实就是服务列表,代理对象是BinderProxy,真实对象叫Binder(或其内部类BBinder的Java层代表)。
第三层,一次拷贝原理。Binder在底层用mmap把内核缓冲区映射到用户空间,所以一次数据传输只需要从发送方拷贝到内核缓冲区,接收方直接通过映射读取,相比传统的Socket/管道两次拷贝少了一次。这是Binder性能优于其他IPC的关键原因,面试必问。
第四层,线程池机制。Binder是支持并发调用的,每个进程在初始化时会创建Binder线程池(默认是15个线程,部分版本可配置),远程调用请求到达后会从池中取一个线程来执行。理解这点,你才能回答“system_server为什么有那么多binder线程”这种问题。
再说Handler。Handler的本质不是“异步”,而是“把一段逻辑放到指定线程的消息队列里等待执行”。它由Looper(循环器)、MessageQueue(消息队列)、Handler(处理器)组成。MessageQueue.next()在无消息时会通过epoll阻塞,让线程休眠,有消息时再唤醒。这就解释了为什么主线程平时不占CPU,但又能及时响应事件。
读Handler源码时,我建议重点看MessageQueue.java的next()方法,里面涉及nativePollOnce的调用,这是理解Block和Wakeup的关键。然后看Handler.enqueueMessage()和dispatchMessage(),理解消息从入队到分发的全流程。最后再看Choreographer和ViewRootImpl,了解一帧的绘制消息是怎么被安排到主线程队列里的。
当你把Binder和Handler理解成“两条腿”后,再去看AMS调度Activity、WMS调度Window、Input系统调度事件,会发现所有逻辑都是在这两条腿上跑的。
2.4 第四阶段:从SystemServer到HAL、SurfaceFlinger、perfetto
基础内功扎实之后,就该往系统层面扩展了。这个阶段我建议按三个方向深入,但没必要三个都学,根据你想走的方向选就行:
方向A:系统服务与AMS/ATMS调度。以startActivity()为线索,从Instrumentation.execStartActivity()一路追到ATMS.startActivity(),再追到ActivityStarter、Task、ActivityRecord这些数据结构。顺便把ActivityTaskManagerService和WindowManagerService的互相调用也看了,理解Activity和Window的对应关系。
方向B:图形显示链路。这个方向会硬核很多。你要理解一张图片是怎么从App的Canvas绘制指令,变成RenderThread的DisplayList,再通过SurfaceFlinger进行合成,最终通过HAL送到屏幕。重点关注ViewRootImpl.performTraversals()、ThreadedRenderer、SurfaceFlinger的合成流程,以及Choreographer里面的vsync机制。学完这个方向,性能优化和掉帧问题对你来说就是透明的。
方向C:硬件抽象与驱动适配。HAL是连接framework和内核的桥梁。比如音频,上层Java通过AudioFlinger和AudioPolicyService管理音频策略,底层通过HAL中的audio_hw实现具体设备的读写;再比如Camera,HAL定义了CameraDevice、CaptureRequest的数据流模型。做手机厂商或车载系统定制,这个方向需求量很大。
另外,性能分析工具perfetto强烈建议在这个阶段系统性学一遍。它是目前Android上最强大的系统级trace工具,可以同时记录CPU调度、进程/线程状态、Binder调用、SurfaceFlinger合成、内存分配等信息,导出后能直接在网页上可视化。调试framework问题时,它比logcat高效十倍。
抓trace的基础命令很简单:
# 抓取10秒系统全局trace adb shell perfetto -o /data/misc/perfetto-traces/trace.perfetto -t 10s sched freq idle binder_driver gfx view wm am # 将trace文件拉到本地 adb pull /data/misc/perfetto-traces/trace.perfetto # 打开ui.perfetto.dev加载文件即可查看看到这里,你应该明白了:framework学习不是“看一本书”的事,它是“建立一棵知识树”的事。Binder和Handler是根,SystemServer是主杆,AMS/WMS/View是枝干,HAL和图形链路是叶。根不牢,枝干全是空中楼阁。
3. 实战拆解:一次点击背后,framework到底干了多少活
3.1 SystemServer启动流程:系统服务的“开国大典”
要理解framework,绕不开SystemServer。它是所有核心系统服务的老家,位列Android启动顺序中最关键的一环。整体启动链路是:init进程 →zygote进程 →SystemServer进程。
zygote是Android的“进程孵化器”,App进程和SystemServer都由它fork而来。SystemServer会在main()方法里调用SystemServer.run(),然后在其中分三批启动所有服务:
- 引导服务(Boot Services):包括
ActivityTaskManagerService、PowerManagerService、PackageManagerService等,它们是最底层的依赖。 - 核心服务(Core Services):包括
BatteryService、UsageStatsService、WebViewUpdateService等。 - 其他服务(Other Services):包括
AudioService、CameraService、AlarmManagerService、NotificationManagerService、WallpaperManagerService等。
现在Android版本中,ATMS和WMS是前后脚创建的,它们俩共享同一个WindowManagerGlobalLock,这解决了很多历史性的死锁问题。如果你想在系统启动时观察服务创建顺序,可以直接抓logcat看SystemServiceManager.startService()的日志,系统会打印类似Starting service XxxService的信息。
这一段学习的具体目标是能自己画出这张表:
| 服务名 | 职责 | 启动阶段 |
|---|---|---|
| ActivityTaskManagerService | 管理Activity、任务栈 | 引导服务 |
| WindowManagerService | 管理窗口、布局和输入窗口焦点 | 引导服务 |
| PackageManagerService | 包管理、权限管理 | 引导服务 |
| PowerManagerService | 电源和唤醒锁管理 | 引导服务 |
| AudioService | 音频策略、音量、设备路由 | 其他服务 |
| CameraService | 相机设备和采集流程 | 其他服务 |
3.2 startActivity的完整旅程:跨进程调用的样板案例
从你点击桌面图标到Activity界面显示,这个过程中framework的参与堪称“接力赛”。我用一个简化的链路拆解它:
第一步,你的App进程发起调用。应用内调用startActivity(),实际会走Instrumentation.execStartActivity(),通过ActivityManager.getService()获取ATMS的Binder代理,然后发起跨进程调用。这里注意,底层是ActivityManagerService在Android 10以后把Activity相关的逻辑拆分到了ActivityTaskManagerService,所以现在真正干活的是ATMS。
第二步,system_server进程处理。ATMS收到调用请求后,会通过ActivityStarter判断调用者的Intent是否合法、目标Activity是否存在、是否有权限,然后计算目标Activity应该放入哪个Task、复用还是新建、使用什么启动模式。接下来会通过ActivityRecord表示目标Activity,并通过TaskSnapshotController保存当前屏幕上旧Activity的快照,方便做过渡动画。
第三步,通知App进程创建Activity。ATMS告诉目标App进程(如果目标App还没启动,则先通过ZygoteProcess请求Zygote fork出一个新进程),让它执行ActivityThread的scheduleLaunchActivity()。注意这里的“告诉”也是Binder通信。
第四步,App进程内执行。ActivityThread收到消息后,把LaunchActivity的消息发送到主线程Handler的MessageQueue中。主线程在消息循环里取出消息,调用handleLaunchActivity(),然后通过Instrumentation.newActivity()反射创建Activity对象,接着performLaunchActivity()里做attach()、onCreate()、onStart()、onResume()等回调。最终ViewRootImpl会向WMS申请添加窗口,完成界面显示。
所以你发现没有:Application层一个startActivity(),背后经历了两次跨进程Binder调用和至少一次线程切换,中间穿插着系统服务的各种调度。这还只是最简链路,没算上ANR监控、GC干预、WindowToken校验这些细节。
我在带新人的时候,要求他们必须把这条链路的每一步对应到源码类名,并且在源码里跳到真正的代码行,能用文字描述出每一步发生了什么。能完整做到这件事,framework才算是“入门了”。
3.3 触摸事件变成界面反馈:Input体系的工作现场
触摸事件链路和Activity启动链路看起来完全不一样,但骨架还是Binder + Handler。
物理触摸屏产生中断后,内核驱动把原始事件上报给EventHub,然后InputReader读取事件,InputDispatcher负责分发。InputDispatcher会按窗口焦点选择目标窗口,通过Binder把事件发送到目标App进程。App进程的InputEventReceiver收到事件后,把它放入主线程队列,最终传递给ViewRootImpl和DecorView,由View数从根节点开始dispatchEvent分发到具体子View。
这里有个很方便的观察手段:在真机上开启“显示触摸位置”和“指针位置”,同时打开开发者选项里的“系统跟踪”,你就能在perfetto里看到从InputDispatcher到ViewRootImpl的完整调度。对于研究输入焦点和手势冲突,这套方法非常有效。
3.4 SurfaceFlinger和HAL:最后一块拼图
事件处理完,UI要更新,真正负责把UI画到屏幕上的是SurfaceFlinger。App进程通过Canvas绘制视图后,会把渲染指令交给RenderThread,通过OpenGL或Vulkan生成GPU缓冲区。SurfaceFlinger的作用是把多个App的缓冲区(你看到的其实是多个Surface)合成一帧画面,再通过HAL里的DisplayDevice输出到屏幕。
Android的刷新机制是垂直同步(VSync)驱动的。当屏幕刷新率达到120Hz时,SurfaceFlinger每8.3ms消费一个新帧;App的Choreographer也会根据VSync信号开始绘制新帧。如果App绘制耗时太长,错过VSync,就会出现掉帧,也就是我们常说的卡顿。
这里有一个面试常问的问题:“为什么Android建议使用12ms/16ms作为帧耗时预算?”其实这取决于屏幕刷新率。60Hz对应16.6ms,90Hz对应11.1ms,120Hz对应8.3ms。你平时用Choreographer打帧间隔,或者用dumpsys gfxinfo查看帧耗时,判断的就是这个预算有没有超支。
SurfaceFlinger这层代码涉及大量C++和GPU概念,新手容易劝退。我的建议是先把握黑白灰框架:先看BufferQueue的生产-消费模型,再看SurfaceFlinger的onMessageInvalidate触发合成,最后通过dumpsys SurfaceFlinger --latency这类指令感受帧率数据。别一上来啃RenderEngine的GLSL代码,那不是新手的地盘。
4. framework日常开发和调试的“保命工具”
学framework是为了改framework,改framework就离不开调试。这里分享几个我几乎每天都在用的工具和思路。
4.1 日志三板斧:logcat、dumpsys、perfetto让你不再当“盲人”
framework系统服务里的日志输出非常丰富,关键要会看。普通App开发通常只关心自己应用的tag,但系统开发必须习惯从海量系统日志里捞关键信息。
logcat:最基础的。看system_server相关日志,需要过滤
SystemServer、ActivityTaskManager、WindowManager、ActivityManager这些tag。也可以配合adb logcat -b all查看所有缓冲区,常用于分析ANR、系统服务崩溃。dumpsys:这是framework的“体检报告”,几乎每个系统服务都支持
dumpsys命令,输出该服务的内部状态。常用例子:
# 查看Activity任务栈详情 adb shell dumpsys activity activities # 查看窗口层级和焦点 adb shell dumpsys window windows # 查看进程和内存状态 adb shell dumpsys meminfo # 查看后台进程和LRU状态 adb shell dumpsys activity processes我经常用dumpsys activity activities判断一个Activity是否真的在预期任务栈里,用dumpsys window windows判断弹窗窗口焦点问题。这两个命令能帮你快速定位很多崩溃和UI异常。
- perfetto:前面已经提过,这里再补充一个实操场景。如果你怀疑某个操作卡顿,通常抓一次10秒trace就够了。重点看四个指标:
- 主线程和UI线程有没有被阻塞。
- 是否有明显的高耗时函数(perfetto会显示函数调用栈)。
- Binder调用频率是不是过高,导致系统服务线程池被打满。
- SurfaceFlinger有没有出现
FrameMissed或Jank事件。
perfetto的可视化页面支持自己添加Track,比如CPU调度、锁竞争,用顺手之后你会理解为什么做framework的人天天离不开它。
4.2 动态调试system_server:断点打在“系统心脏”上
在改AMS/WMS这类系统服务逻辑时,日志往往不够用,你需要断点。
如果你能编译AOSP,并且有可root的设备或模拟器,推荐用Android Studio的Attach Debugger to Android Process功能,选择system_server进程。断点可以打在Java层的ActivityTaskManagerService.java里,单步追踪Activity调度逻辑,效果和普通App调试完全一样。
如果设备没有root,也可以用adb forward配合jdwp方式,但限制比较多。还有一条路是用run-as针对可调试的App进程,但system_server的调试通常还是需要root或eng版本系统。
如果是C++层的问题,比如SurfaceFlinger、Binder驱动,那就得上gallium工具或者lldb。但这个学习曲线比较陡,没有Java层那么好上手,新手阶段可以先绕开。
4.3 系统定制常用操作:改framework.jar后如何让它生效
改完framework代码,总得跑起来验证。常见做法是编译整个系统然后刷机,但这样太慢。更高效的做法:
# 1. 关闭selinux(开发机常用,如果用eng版系统则默认关闭) adb root adb shell setenforce 0 # 2. remount系统分区 adb remount # 3. 把编译出来的framework.jar推到系统目录 adb push out/target/product/xxx/system/framework/framework.jar /system/framework/ # 4. 重启 adb reboot注意,现在分区格式普遍是动态分区或super分区,adb remount不一定有效,需要先adb disable-verity再重启。另外,修改framework.jar后,有可能还需要更新boot.art、boot.oat等优化文件,否则运行时可能走旧缓存。稳妥的办法是直接adb shell cmd package compile -m speed -f android重新编译一下,或者干脆刷system.img。
如果你只是改系统属性、权限配置这类轻量改动,完全没必要动framework.jar,可以用adb shell setprop临时设置,或者pushsystem/etc/permissions下的xml来扩展权限。学会区分“改框架代码”和“改配置”哪个成本更低,是系统开发的基本素养。
5. 面试和实战中反复出现的framework高频考点
5.1 经典问题速查
这部分整理一份我面试候选人和自己被面试时都被问过的高频列表,每一条背后都能展开成一篇长文,这里只给关键思路,方便你自查。
| 问题 | 核心答题要点 |
|---|---|
| Binder为什么比Socket快 | 一次拷贝 + mmap内核态映射;Socket需要两次拷贝 |
| Handler的IdleHandler有什么用 | 空闲时执行,常用于延迟加载、埋点、GC时机优化 |
| AMS和ATMS有什么区别 | Android 10后拆分,ATMS负责Activity/任务栈,AMS负责权限、进程、生命周期管理 |
| ViewRootImpl和DecorView什么关系 | DecorView是顶级View,ViewRootImpl是View树与WMS沟通的桥梁,负责performTraversals调度 |
| SurfaceView为什么能独立线程绘制 | 独立Surface和BufferQueue,在WMS上拥有独立窗口,不参与宿主View的绘制链表 |
| WMS和Window的关系 | Window是抽象概念,WMS管理Window的添加、层级、焦点、布局;每个Window对应一个ViewRootImpl |
| 系统启动时为什么zygote要先加载framework | 共享框架类,fork出的App进程能快速复用类加载结果,节省内存和时间 |
| SystemServer为什么不开多个进程 | 降低进程间通信开销,用binder线程池和高性能设备承载并发;代价是单点风险,所以有watchdog |
这些问题的回答都需要你能“讲出过程”而不是“报出名词”。比如Binder为什么快,你要能把copy_from_user和mmap的链路讲清楚;Handler的IdleHandler,要能说出MessageQueue.next()在取消息阻塞前会执行mIdleHandlers里的任务。
5.2 容易踩坑的细节
framework面试除了问原理,还喜欢埋一些“看似简单但你不动手真不知道”的细节。比如:
- Binder线程池默认大小:Android 8.0以后默认
BINDER_MAX_THREADS是15,但某些系统服务创建时会手动调大,比如系统UI。用dumpsys activity能看到binder线程状态。 - system_server里Handler的线程与优先级:system_server的
ui线程负责主消息循环,android.io线程负责文件/网络IO,binder线程处理远程调用。如果某个服务死锁,多数是binder线程互相等待。 - Apex模块和普通jar包的区别:Android 10以后引入APEX机制,像
com.android.runtime、com.android.art这些都是APE模块,可以独立升级。framework.jar还在system/framework,但被拆出的模块越来越多了。 - 多应用同时录音的限制:Android 9之前音频录制策略是排他式的,之后在高通/MTK平台实现了多应用混音,但应用层仍需要处理
AudioRecord的并发策略。
这些细节单看八股文背不下来,都是动手改过framework或修过系统bug才能积累出来的。所以面试时如果被问到,能结合一个真实案例讲出来,会比背答案强十倍。
6. 学习framework最容易踩的坑与我个人的一点经验
最后聊几个几乎所有自学者都会遇到的问题。
第一个坑:试图“读完全部源码”。AOSP光frameworks/base就有几百万行代码,你不可能逐行读完。正确的方式是围着一条条“调用链”打转。今天追一遍startActivity(),明天追一遍requestLayout(),后天追一遍InputDispatcher,链与链之间自然交织出知识网。
第二个坑:环境搭好了却没坚持下来。我见过有人源码下载了一个月,天天问“编不过怎么办”,最后代码看了不到十行。人的意志力非常有限,与其花精力折腾编译环境,不如先用cs.android.com在网上读代码。等你真的把Java层核心链路读完后,再决定要不要下源码本地编译。把技术难度嚼碎了再啃,效率高得多。
第三个坑:只啃Java层,对C++和底层避之不及。理解Binder离不开C++的ProcessState、IPCThreadState;理解SurfaceFlinger离不开Layer和BufferQueue的C++实现。如果你只盯着Java层,很多“性能问题”“卡顿问题”永远解释不了。当然,不要求你达到C++专家的水平,但至少能读懂关键函数和结构体。
第四个坑:不看日志瞎猜。framework问题定位绝大多数是靠日志和trace,不是靠逻辑推理。遇到系统服务崩溃,先adb logcat -b crash看栈;遇到卡顿,先抓perfetto看关键线程;遇到显示问题,先dumpsys window看窗口状态。有了数据再推测原因,基本一两轮就能锁定。
我自己的习惯是书房白板上永远写着两条主线:一条是Binder,一条是Handler,其他所有知识点都被我挂在它们下面。每次被一个新问题卡住,我就先问自己:这个问题涉及的是哪一个Binder接口?它跑在哪个线程的哪个Handler消息里?想清楚这两件事,八成问题已经有了方向。
如果你也准备走这条路,我的建议很朴素:给自己定一个为期三个月的计划,每周只学一个主题,每个主题都要求自己能动手写一个小Demo或画出一张调用链图。三个月下来,你再看Android系统,绝对不会是当初那个只看得见API的黑盒了。