1. 从一部手机说起:Android 系统架构到底在解决什么问题
很多人第一次接触 Android 系统架构,是在面试或者看源码的时候。打开 AOSP 的目录,看到frameworks、hardware、system、kernel一大堆文件夹,瞬间就懵了。但如果你换一个角度——从你手里这部手机开机的那一瞬间开始想——事情会清晰很多。
按下电源键,屏幕亮起,锁屏界面出现,你滑动解锁,点开微信,发了一条消息。这短短几秒钟里,至少有五六个不同层级的软件模块在协同工作:引导程序加载内核,内核初始化硬件驱动,Native 层启动各种系统服务,Framework 层拉起应用进程,最后你的微信才出现在屏幕上。Android 系统架构,本质上就是回答一个问题:从硬件到应用,这几层东西是怎么分工、怎么通信、怎么协作的。
我做了十多年 Android 系统层开发,从早期的 Android 4.4 一直跟到现在的 Android 15、16,踩过的坑可以说遍布每一层。这篇文章不是教科书式的架构概述,而是把我这些年对 Android 系统架构的理解、实操中遇到的真实问题、以及那些文档里不会写的经验,系统地梳理一遍。无论你是刚入行的应用开发想往底层走,还是做嵌入式想理解 Android 的全貌,或者只是面试前想快速建立知识框架,这篇内容都能给你一个可以直接参考的完整视角。
核心关键词会贯穿全文:Android、系统架构、Linux 内核、HAL、Framework。这五个词基本就是 Android 架构的骨架,理解了它们之间的关系,整个系统在你脑子里就是活的。
2. Android 系统架构的整体分层设计
2.1 五层架构的经典划分与背后的设计逻辑
Google 官方给出的 Android 架构图是五层结构,从下往上依次是:Linux 内核层、硬件抽象层(HAL)、Native 库与 Android 运行时层、Java API Framework 层、系统应用层。这个划分不是随便定的,每一层的存在都有明确的工程理由。
先说最底层的Linux 内核。Android 选择 Linux 内核不是偶然,2007 年 Android 立项的时候,Linux 已经是一个成熟、稳定、驱动生态极其丰富的操作系统内核。用它做底座,意味着 Android 不需要从零写驱动、写内存管理、写进程调度。内核层负责的东西很硬核:进程调度、内存管理、网络协议栈、电源管理、以及各种硬件驱动(显示、摄像头、蓝牙、音频等)。你在应用层调用一个Camera.open(),最终落到内核里就是摄像头驱动的寄存器操作。
往上一层是HAL(Hardware Abstraction Layer)。这一层是 Android 架构里最容易被忽视、但工程价值极高的一层。它的核心作用是解耦:Framework 层不需要知道具体硬件的实现细节,只需要调用 HAL 提供的标准接口。比如摄像头 HAL 定义了camera_device_ops结构体,里面有一组函数指针,高通、联发科、三星各自实现自己的版本,但上层调用方式完全一样。这就是为什么同一套 Android Framework 能跑在几百种不同硬件上。
再往上是Native 层和 ART 运行时。Native 层用 C/C++ 写,包含了一堆核心库:libc、OpenGL ES、SQLite、WebKit、媒体框架等。ART(Android Runtime)负责执行 Java/Kotlin 字节码,早期是 Dalvik,Android 5.0 之后换成 ART,引入了 AOT 预编译,性能提升非常明显。每个应用进程都跑在自己的 ART 虚拟机实例里,这是 Android 沙箱安全模型的基础。
Java API Framework 层是应用开发者最熟悉的部分。ActivityManager、WindowManager、PackageManager、ContentProvider、NotificationManager 这些系统服务都在这一层。它们通过 Binder IPC 与底层服务通信,向上提供 Java 接口。你写的startActivity(),最终会通过 Binder 跨进程调用到 system_server 里的 ActivityManagerService。
最上面是系统应用层,包括桌面、设置、电话、相机这些预装应用。它们和第三方应用一样跑在 ART 上,只是拥有更高的权限。
2.2 为什么是分层而不是单体:一次真实的架构权衡
有人会问,为什么要搞这么复杂的分层?直接写一个大程序不行吗?我举个实际例子你就明白了。
假设你是一家手机厂商的工程师,要支持一款新的指纹传感器。如果 Android 是单体架构,你需要改动 Framework 里所有跟指纹相关的代码,还要保证不影响其他功能,风险极高。但在分层架构下,你只需要做两件事:写一个符合指纹 HAL 接口规范的.so库,然后在 Framework 层配置一下加载路径。上层代码一行不用改,其他厂商的指纹模块也完全不受影响。
这就是分层的价值:变更隔离。每一层只依赖下一层的接口,不依赖实现。接口稳定,实现随便换。Android 能形成今天这么庞大的设备生态,分层设计功不可没。
但分层也有代价。最直接的就是性能开销:一次跨层调用可能涉及多次 IPC、序列化、反序列化。比如你从应用层读一个传感器数据,路径是 App → Framework → SensorService → HAL → 内核驱动,中间要过好几次 Binder。所以 Android 在性能敏感的场景(如音频、图形渲染)会尽量让数据走共享内存或者直接 Native 调用,绕开 Java 层。
另一个代价是调试复杂度。一个 bug 可能出现在任何一层,定位起来需要你对整个调用链有清晰的认识。这也是为什么系统层开发的门槛比应用层高——你得同时懂 Java、C++、C、甚至汇编和内核。
2.3 各层之间的通信机制:Binder 是绝对主角
Android 各层之间怎么通信?答案是Binder。这是 Android 架构里最重要的一个机制,没有之一。
Binder 是一个基于内核的 IPC(进程间通信)框架。它的核心是一个内核驱动/dev/binder,所有跨进程调用都通过它中转。相比传统的管道、Socket、共享内存,Binder 的优势是:一次拷贝(数据从发送方用户空间拷贝到内核,接收方直接映射内核缓冲区)、支持远程对象引用、自带线程池管理。
在架构层面,Binder 贯穿了 Framework 层和 Native 层。你在 Java 层拿到的ActivityManager对象,其实是一个 Binder Proxy,真正的实现在 system_server 进程里。你调用它的方法,参数会被 Parcel 序列化,通过 Binder 驱动传到 system_server,那边反序列化后调用真实方法,返回值再原路返回。
理解 Binder 是理解 Android 架构的关键。我见过太多应用开发者,写了几年代码,却不知道getSystemService()背后发生了什么。一旦你理解了 Binder,很多"玄学"问题就迎刃而解了——比如为什么跨进程传大 Bitmap 会崩(Binder 事务缓冲区只有 1MB),为什么某些系统服务调用会阻塞主线程。
3. Linux 内核层:Android 的地基
3.1 Android 对标准 Linux 内核做了哪些改造
Android 用的 Linux 内核不是原版,Google 做了不少定制。这些定制主要集中在几个方面:
Binder 驱动。前面说了,这是 Android IPC 的核心,标准 Linux 内核里没有,是 Android 特有的。它注册为/dev/binder字符设备,提供ioctl接口给用户空间调用。
Ashmem(匿名共享内存)。标准 Linux 有mmap和shm,但 Android 需要一种更灵活、可回收的共享内存机制,于是有了 Ashmem。它允许内核在内存紧张时回收某些共享内存页,这对内存受限的移动设备很重要。不过从 Android 10 开始,Ashmem 逐渐被标准的memfd替代。
Low Memory Killer(LMK)。移动设备内存有限,当内存不足时需要杀掉一些后台进程。标准 Linux 有 OOM Killer,但它的策略不适合 Android 的场景。LMK 根据进程的oom_adj值(后来改成oom_score_adj)来决定杀谁,前台进程优先级最高,后台进程最先被杀。Android 10 之后 LMK 被移到用户空间,叫 LMKD。
Wakelock。这是 Android 电源管理的核心机制。标准 Linux 的休眠策略比较简单,Android 需要更精细的控制:应用可以申请 Wakelock 阻止系统休眠,比如你正在听音乐或者下载文件。这个机制在内核里实现为/sys/power/wake_lock接口。
日志系统(Logger)。Android 有自己的日志驱动,提供logcat功能。不过新版本也逐渐迁移到pstore和debugfs。
这些改造让 Linux 内核更适合移动场景,但也带来一个问题:Android 内核和上游 Linux 的同步变得困难。每次 Linux 发布新版本,Google 都要把 Android 的补丁 rebase 上去,工作量巨大。这也是为什么 Android 设备的内核版本往往落后于最新 Linux 好几年。
3.2 内核驱动与硬件交互的实操细节
做系统移植的时候,最常打交道的就是内核驱动。我拿一个真实场景举例:调试一块新的触摸屏。
首先,你要在设备树(Device Tree)里配置 I2C 总线地址和中断引脚。设备树是 ARM 架构下描述硬件的方式,编译成.dtb文件由 bootloader 传给内核。配置大概长这样:
&i2c1 { touchscreen@38 { compatible = "vendor,touch-ft5x06"; reg = <0x38>; interrupt-parent = <&gpio1>; interrupts = <4 IRQ_TYPE_EDGE_FALLING>; reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>; }; };然后写驱动,注册 input 设备,在中断处理函数里读取触摸坐标,通过input_report_abs()上报给 input 子系统。内核的 input 子系统会把这些事件通过/dev/input/eventX暴露给用户空间。
用户空间这边,Android 的EventHub(在frameworks/native/services/inputflinger里)会监听这些设备节点,把原始事件转换成 Android 的MotionEvent,再通过 InputDispatcher 分发给应用。
这个链路很长,任何一环出问题触摸都会失效。我踩过的坑包括:设备树中断类型配错(应该下降沿配成了高电平)、I2C 地址冲突、驱动里坐标轴方向搞反、HAL 层没有正确上报ABS_MT_POSITION_X等。调试的时候,getevent命令是你的好朋友,它能直接打印/dev/input的原始事件,帮你判断问题出在内核还是上层。
3.3 内核版本选择与厂商定制的现实考量
选内核版本是个技术活。理论上越新越好,新版本有更好的性能、更多的驱动支持、更少的安全漏洞。但现实中,你往往被 SoC 厂商绑死。
高通、联发科这些 SoC 厂商会基于某个 Linux LTS 版本做自己的 BSP(Board Support Package),然后手机厂商再基于 BSP 做定制。比如某款芯片用的是 Linux 5.10,那你就只能用 5.10,想升到 6.1 得等 SoC 厂商更新 BSP,而他们通常只在旗舰芯片上做这种更新。
这就导致一个尴尬的局面:中低端设备的内核可能停留在三四年前的版本,安全补丁靠 backport 打。作为系统工程师,你能做的是:定期同步上游的 CVE 修复、裁剪不需要的驱动减小攻击面、开启内核加固选项(如CONFIG_STACKPROTECTOR、CONFIG_FORTIFY_SOURCE)。
还有一个现实问题是GKI(Generic Kernel Image)。从 Android 12 开始,Google 推行 GKI,把内核分成通用的核心镜像和厂商的模块。这样 Google 可以独立更新内核核心,不用等 SoC 厂商。这个方向是对的,但过渡期很痛苦,很多厂商的驱动还没完全模块化,兼容性问题不少。
4. HAL 层:硬件与框架的翻译官
4.1 HAL 的演进:从传统 HAL 到 HIDL 再到 AIDL
HAL 这一层这些年变化最大,值得单独讲讲。
传统 HAL(Legacy HAL)是 Android 8.0 之前的做法。每个 HAL 就是一个.so库,Framework 通过dlopen加载,然后dlsym拿到hw_module_t结构体,调用里面的函数指针。这种方式简单直接,但问题很多:HAL 和 Framework 跑在同一个进程里,HAL 崩溃会拖垮整个系统服务;HAL 接口没有版本管理,升级困难;不同厂商的 HAL 实现五花八门,兼容性差。
HIDL(HAL Interface Definition Language)是 Android 8.0 引入的。它把 HAL 定义成接口文件(.hal),用hidl-gen工具生成 C++ 或 Java 的桩代码。HAL 实现跑在独立的进程里(叫hwservicemanager管理的 binderized HAL),通过 Binder 通信。这样 HAL 崩溃不会影响 Framework,接口也有了版本管理(比如android.hardware.camera@2.4)。
AIDL for HAL是 Android 11 之后推的方向。Google 发现 HIDL 和 AIDL 两套 IPC 机制维护成本太高,干脆统一用 AIDL。新的 HAL 都用 AIDL 定义,HIDL 逐步废弃。AIDL HAL 的好处是复用了成熟的 AIDL 工具链,支持稳定版本(stable AIDL),而且 Java 和 C++ 都能用。
这个演进过程反映了一个趋势:Android 越来越重视模块化和可维护性。从单体到进程隔离,从私有接口到标准化 IDL,每一步都是为了让这个庞大的系统更容易演进。
4.2 一个摄像头 HAL 的完整实现思路
我拿摄像头 HAL 举例,讲讲一个 HAL 模块从零到能用的完整过程。这是我在一个定制项目里真实做过的。
首先定义接口。如果用 AIDL,你会写一个ICameraProvider.aidl,里面定义getCameraIdList()、getCameraDeviceInterface()等方法。用aidl工具生成桩代码后,你实现BnCameraProvider这个类。
核心工作是实现ICameraDevice和ICameraDeviceSession。前者负责打开摄像头、配置流,后者负责实际的帧捕获。在configureStreams()里,你要根据上层请求的分辨率、格式,配置 ISP(图像信号处理器)的输出通道。在processCaptureRequest()里,你要把请求下发给驱动,等待帧数据回来,再通过ICameraDeviceCallback回调给上层。
这里最麻烦的是缓冲区管理。摄像头数据量很大,1080p 一帧就是几 MB,不能每次都拷贝。Android 用gralloc分配图形缓冲区,HAL 直接把数据写进这个缓冲区,上层通过ANativeWindow拿到。gralloc 的实现又依赖具体的 GPU 和显示驱动,所以这块经常出问题——比如缓冲区格式不匹配、stride 对齐错误导致图像花屏。
调试摄像头 HAL,我常用的手段是:先用v4l2-ctl直接测试驱动能不能出图,排除内核问题;然后在 HAL 里加日志,打印每一帧的时间戳和缓冲区地址;最后用dumpsys media.camera看 Framework 层的状态。这三步基本能定位 90% 的问题。
4.3 HAL 开发中的常见陷阱与调试手段
做 HAL 开发,有几个坑几乎人人都会踩。
第一个是接口版本不匹配。Android 的 HAL 接口是带版本的,Framework 期望@2.4版本,你实现了@2.3,编译能过但运行时找不到接口。解决办法是仔细看manifest.xml里的配置,确保 HAL 声明的版本和 Framework 请求的一致。
第二个是 SELinux 权限。Android 从 5.0 开始强制 SELinux,HAL 进程有自己的域(domain),访问设备节点、文件、socket 都需要相应的策略。新手经常遇到 HAL 启动失败,看 logcat 只有一句avc: denied。这时候要用audit2allow工具根据 avc 日志生成策略规则,加到sepolicy里。
第三个是进程生命周期。Binderized HAL 是懒加载的,第一次被调用时才启动,空闲一段时间后会被杀掉。如果你的 HAL 有初始化开销大的问题(比如要加载固件),每次重启都会卡一下。解决办法是在manifest.xml里配置oneshot="false"并设置合理的priority,或者用init.rc里的class让它常驻。
第四个是并发问题。HAL 会被多个客户端同时调用,比如相机和录像可能同时请求。你的实现必须线程安全,该加锁的地方加锁。我见过一个 bug,两个客户端同时配置流,导致 ISP 寄存器状态错乱,图像直接黑屏。后来加了互斥锁才解决。
调试 HAL 的工具链:logcat看日志、dumpsys看状态、strace跟踪系统调用、perfetto做性能分析。如果怀疑是 Binder 通信问题,可以打开 Binder 的 debug 开关,看事务的详细过程。
5. Framework 层:应用开发者的主战场
5.1 四大组件背后的系统服务支撑
应用开发者天天用 Activity、Service、BroadcastReceiver、ContentProvider,但很少有人深究它们背后是谁在支撑。答案是system_server 进程里的一堆系统服务。
ActivityManagerService(AMS)是四大组件的中枢。你调用startActivity(),请求通过 Binder 到 AMS,AMS 负责决定要不要启动、启动在哪个进程、怎么调度。它还管理着所有进程的生命周期和优先级。AMS 是 system_server 里最复杂的服务之一,代码量巨大,逻辑盘根错节。
WindowManagerService(WMS)管窗口。每个 Activity 对应一个窗口,WMS 负责窗口的层级、位置、大小、动画。你看到的转场动画、状态栏、导航栏,都是 WMS 在调度。WMS 和 SurfaceFlinger(Native 层的合成服务)配合,把各个窗口的内容合成到屏幕上。
PackageManagerService(PMS)管应用安装和包信息。你getPackageManager().getPackageInfo()拿到的数据,就是 PMS 在开机时扫描/data/app和/system/app建立起来的。PMS 还负责权限管理、组件解析。
ContentProvider的底层是ActivityManagerService和ContentService。跨进程访问 ContentProvider 时,AMS 负责找到目标进程并建立连接,实际的数据传输走 Binder。
理解这些服务的存在,能帮你解释很多现象。比如为什么应用启动慢——因为要经过 AMS 的一系列检查、进程创建、Application 初始化。为什么后台 Service 会被杀——因为 AMS 根据内存压力调整进程优先级。
5.2 Binder 机制在 Framework 中的实际运用
Binder 在 Framework 层无处不在,但它的使用有一套固定模式,理解了这套模式,看源码就轻松了。
以ActivityManager为例。客户端调用ActivityManager.getService().startActivity(...)。getService()返回的是一个IActivityManager接口的 Proxy 对象。这个 Proxy 是ActivityManagerService的远程代理,它的startActivity()方法会把参数写进 Parcel,然后调用transact()通过 Binder 驱动发送。
服务端这边,ActivityManagerService继承自IActivityManager.Stub,onTransact()方法会根据 transaction code 分发到具体的实现方法。参数从 Parcel 里读出来,调用真正的startActivity(),返回值再写回 Parcel。
这套机制的关键是AIDL 自动生成的代码。你定义IActivityManager.aidl,编译时会生成IActivityManager.java,里面包含Stub(服务端基类)和Proxy(客户端代理)。手写这套代码很容易出错,所以 Android 大量使用 AIDL。
有一个细节值得注意:Binder 事务是同步的。客户端调用transact()后会阻塞,直到服务端返回。这意味着如果你在主线程调用一个耗时的系统服务方法,就会 ANR。所以 Android 提供了oneway关键字,标记为 oneway 的方法调用后立即返回,不等结果。比如IActivityManager里很多通知类的方法都是 oneway 的。
还有一个坑是Binder 事务缓冲区大小。每个进程的 Binder 缓冲区默认是 1MB,所有并发事务共享。如果你传一个 2MB 的 Bitmap 跨进程,直接抛TransactionTooLargeException。解决办法是用 Ashmem 或者文件描述符传递大数据,只传一个引用。
5.3 系统服务的启动流程与定制修改
system_server 是 Android 启动过程中最关键的进程,它承载了几乎所有 Java 系统服务。理解它的启动流程,对做系统定制非常重要。
启动从Zygote开始。Zygote 是 Android 的进程孵化器,开机时由 init 进程启动,预加载了 Framework 的类和资源。当需要启动 system_server 时,Zygote fork 出一个新进程,执行SystemServer.main()。
SystemServer.main()里会依次启动三类服务:引导服务(ActivityManagerService、PackageManagerService、PowerManagerService 等)、核心服务(WindowManagerService、DisplayManagerService 等)、其他服务(CameraService、AudioService 等)。每个服务启动时都会调用ServiceManager.addService()注册到 ServiceManager,这样其他进程才能通过名字找到它。
启动顺序是有讲究的。比如 AMS 必须在 PMS 之后启动,因为 AMS 需要 PMS 提供包信息。WMS 必须在 AMS 之后,因为 WMS 需要 AMS 提供 Activity 信息。这个依赖关系在SystemServer.java里通过代码顺序保证。
做系统定制时,经常需要在这里加东西。比如你要加一个自己的系统服务,就在startOtherServices()里加一行ServiceManager.addService("myService", new MyService())。但要注意,加服务会增加开机时间,而且如果服务初始化失败会拖垮整个 system_server,导致设备起不来。所以我的经验是:能不加就不加,能延迟初始化就延迟。
还有一个常见的定制需求是修改系统服务的默认行为。比如改默认亮度、改默认语言、改默认动画速度。这些通常在frameworks/base/core/res/res/values/config.xml里配置,或者通过SettingsProvider的默认值设置。改完要重新编译 framework-res.apk,刷机验证。
6. 从架构视角排查真实问题
6.1 应用崩溃如何一路追溯到内核
我讲一个真实的排查案例,展示如何用架构思维定位问题。
现象:某款设备上,某个应用打开相机后偶尔崩溃,logcat 显示FATAL EXCEPTION在Camera.open()附近。
第一步,看应用层日志。异常是CameraAccessException,说明相机服务拒绝了请求。但为什么拒绝?应用层看不出来。
第二步,看 system_server 日志。dumpsys media.camera显示相机设备被占用。但谁占用的?继续查。
第三步,看 camera service 日志。发现有一个之前的会话没有正确关闭,导致设备一直处于 busy 状态。为什么没关闭?因为那个进程被杀了,但 HAL 没有收到通知。
第四步,看 HAL 日志。发现 HAL 在等待一个永远不会到来的回调,卡住了。为什么卡住?因为内核驱动在某个 ioctl 上阻塞了。
第五步,看内核日志。dmesg显示摄像头驱动的 I2C 通信超时。原来是硬件接触不良,导致驱动重试多次后卡死。
这个案例说明:一个应用层的崩溃,根因可能在内核。如果你只懂应用层,永远找不到真正的问题。架构知识在这里的价值,就是让你知道每一层该看什么日志、该用什么工具、该怀疑什么。
6.2 性能问题的分层定位方法
性能问题同样需要分层定位。我总结了一套方法:
先看现象。是启动慢、滑动卡、还是耗电快?不同现象指向不同的层。
再看指标。用perfetto抓 trace,看 CPU 占用、线程状态、Binder 调用。如果是 UI 卡顿,看 SurfaceFlinger 的合成时间、RenderThread 的绘制时间。如果是启动慢,看 AMS 的启动流程、应用的 Application 初始化。
然后分层排查。应用层看有没有主线程 IO、有没有过度绘制;Framework 层看系统服务有没有锁竞争、有没有频繁 GC;Native 层看有没有内存拷贝、有没有锁等待;内核层看有没有 CPU 调频问题、有没有 IO 阻塞。
我遇到过一个典型案例:某应用滑动列表卡顿。应用层优化了半天没用。后来用 perfetto 一看,发现是 WMS 在处理输入事件时持有了一个大锁,导致 InputDispatcher 阻塞。再往下查,是某个系统服务的 Binder 调用超时。最后定位到是 HAL 层的一个传感器读取操作太慢,拖累了整个输入链路。这种问题,没有架构视角根本无从下手。
6.3 系统定制中的架构决策经验
做系统定制,经常面临架构层面的决策。我分享几个经验。
要不要改 Framework。改 Framework 影响面大,升级困难,能不改就不改。优先考虑用 HAL 或者应用层解决。比如你要加一个自定义的硬件功能,优先做成 HAL,通过标准接口暴露给应用。
要不要加系统服务。加服务会增加开机时间和内存占用,而且一旦出问题影响全局。如果功能简单,考虑做成一个 Native 守护进程,或者用现有的服务扩展。
怎么保证兼容性。Android 的 CTS(Compatibility Test Suite)会检查系统是否符合规范。你改的东西如果影响了 CTS 测试项,设备就过不了认证。所以改之前一定要跑 CTS,确认没有破坏标准行为。
怎么处理版本升级。Android 每年一个大版本,API 和行为都在变。定制代码要尽量用稳定的接口,避免依赖内部实现。我见过太多项目,因为用了私有 API,升级时全部推倒重来。
7. 常见问题速查与避坑清单
7.1 各层典型问题对照表
| 层级 | 典型问题 | 排查工具 | 常见原因 |
|---|---|---|---|
| 应用层 | 崩溃、ANR | logcat、StrictMode | 主线程 IO、内存泄漏 |
| Framework | 服务无响应、死锁 | dumpsys、perfetto | 锁竞争、Binder 超时 |
| Native | 内存泄漏、崩溃 | valgrind、asan | 野指针、缓冲区溢出 |
| HAL | 设备打不开、数据异常 | logcat、strace | 权限、版本、并发 |
| 内核 | 驱动失败、系统卡死 | dmesg、ftrace | 硬件、中断、内存 |
7.2 我踩过的那些坑
坑一:Binder 缓冲区溢出。跨进程传大数组,没注意 1MB 限制,直接崩。后来改成传文件描述符,用共享内存。
坑二:SELinux 权限。HAL 进程访问设备节点被拒,logcat 只有一行 avc denied。用audit2allow生成策略,但要注意不要过度授权,否则过不了安全审查。
坑三:内核版本不匹配。拿了一个别人编译的驱动,加载时报version magic错误。内核模块和内核版本必须严格对应,包括编译配置。
坑四:HAL 接口版本。Framework 请求@2.4,HAL 只实现了@2.3,运行时找不到。看manifest.xml和dumpsys的版本信息。
坑五:system_server 崩溃。加了一个系统服务,初始化时抛异常,导致整个 system_server 挂掉,设备无限重启。加服务一定要做异常保护。
7.3 给不同阶段开发者的建议
如果你是应用开发者,想往系统层走,建议从 Binder 和 AMS 入手,先理解应用启动流程,再逐步深入 WMS、PMS。不要一上来就看内核,容易劝退。
如果你是嵌入式工程师,想理解 Android,建议从 HAL 入手,因为这是你已有的驱动知识和 Android Framework 的桥梁。理解了 HAL,再往上往下都顺。
如果你是面试准备,重点掌握五层架构的划分、Binder 机制、四大组件的系统服务支撑、HAL 的演进。这几个是高频考点。
如果你是系统定制工程师,重点掌握 system_server 启动流程、SELinux 策略、HAL 开发、内核驱动调试。这些是日常工作的核心。
8. 架构之外:一些个人体会
做了这么多年 Android 系统,我最大的体会是:架构知识不是用来背的,是用来定位问题的。你不需要记住每个类的每个方法,但你需要知道一个问题可能出在哪一层,该用什么工具去看。
另一个体会是:Android 的架构一直在变,但核心思想没变。从 Dalvik 到 ART,从 HIDL 到 AIDL,从 LMK 到 LMKD,具体实现换了一茬又一茬,但分层解耦、进程隔离、接口标准化这些原则始终如一。抓住这些不变的东西,你就能以不变应万变。
最后分享一个实用技巧:遇到不懂的问题,先画调用链。从应用层的 API 开始,一层层往下画,标出每一层的输入输出和通信方式。画着画着,问题往往就自己浮现出来了。这个方法我用了十年,屡试不爽。
Android 系统架构是个深不见底的话题,一篇文章不可能讲完所有细节。但只要你能建立起这个分层框架,知道每一层大概在干什么、怎么交互,剩下的就是在这个框架里不断填充细节。这个过程很漫长,但也很有意思。