ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android系统架构深度解析:从Linux内核到Framework的分层设计与通信机制

Android系统架构深度解析:从Linux内核到Framework的分层设计与通信机制 1. 从一部手机说起Android 系统架构到底在解决什么问题很多人第一次接触 Android 系统架构是从刷机、改 Framework、或者移植系统开始的。我也不例外。当年拿着一台卡顿的旧机器想搞明白为什么同样一颗芯片不同厂商的系统流畅度差这么多才一步步从 App 层往下挖挖到 Framework再挖到 HAL最后挖到 Linux 内核。挖完之后最大的感受是Android 系统架构不是一张画出来好看的框图而是一套为了解决硬件千差万别、应用却要统一运行这个核心矛盾而设计的分层协作机制。先把结论摆在前面Android 系统架构的本质是用一套分层 抽象的结构把硬件差异和应用逻辑彻底隔离开。上层应用只认统一的 API下层硬件各自实现自己的驱动中间靠 HAL 和 Framework 做翻译和调度。理解了这条主线后面所有的模块、进程、通信机制都是围绕它展开的。这套架构大致分成五层从下往上依次是Linux 内核层负责进程调度、内存管理、设备驱动、网络协议栈、电源管理等最底层能力。HAL 层硬件抽象层把具体硬件的操作封装成标准接口让上层不用关心用的是哪家芯片。Native 层与运行时包括 C/C 库、ART 虚拟机、核心系统库。Framework 层Java API 框架开发者日常打交道的 ActivityManager、WindowManager、PackageManager 都在这一层。应用层系统应用和第三方 App。这五层不是简单的堆叠而是通过Binder IPC、JNI、HIDL/AIDL等机制互相通信。很多人学架构只记住了分层图却忽略了层与层之间怎么说话结果一到实际改代码就懵。所以这篇我会把重点放在层与层之间的接口和通信上而不是复述那张人人都见过的框图。适合谁看如果你正在做 Android 系统开发、Framework 定制、HAL 驱动适配、系统移植或者准备系统架构设计师这类考试这篇内容能帮你把零散的知识点串成一条线。如果你只是 App 开发者看完也能明白为什么有些 API 在不同机型上行为不一致。2. Linux 内核层Android 不是一套全新的系统2.1 为什么 Android 要基于 Linux 内核而不是从零写这是很多人没想清楚的问题。Android 完全可以像某些嵌入式系统一样自己写一套内核但它选择了 Linux。原因很实际Linux 内核经过几十年发展在进程调度、内存管理、文件系统、网络协议栈、电源管理这些基础能力上已经非常成熟而且有庞大的驱动生态。Android 团队要做的不是重造轮子而是在 Linux 之上做针对移动场景的裁剪和增强。具体来说Android 在标准 Linux 内核基础上加了几个关键的东西Binder 驱动这是 Android 独有的标准 Linux 没有。它是整个 Android IPC 的基石后面会详细讲。Ashmem匿名共享内存用于进程间高效共享大块内存比如图形缓冲区。Low Memory KillerLMK移动设备内存有限需要在内存紧张时主动杀掉后台进程这套机制是 Android 特有的。Wakelock电源管理相关控制设备何时可以进入休眠。Logger日志系统。所以严格来说Android 用的不是原版 Linux而是打了 Android 补丁的 Linux。你在做系统移植时第一步往往就是把 Android 的这些补丁合入到目标芯片的内核源码里。2.2 内核层到底管哪些事从架构角度看内核层主要承担四类职责职责类别具体内容对上层的影响进程与线程调度CFS 调度器、进程优先级决定 App 响应速度内存管理虚拟内存、页缓存、LMK决定后台能留多少 App设备驱动显示、触摸、摄像头、音频、传感器决定硬件能否被识别通信与同步Binder、Socket、信号量、互斥锁决定进程间能否协作这里有个容易被忽略的点内核层的驱动质量直接决定了整机的稳定性。我见过太多机器Framework 层代码写得没问题但一跑压力测试就重启最后定位到是某个传感器驱动在高频采样时死锁。所以做系统架构不能只盯着上层内核这层的地基必须打牢。2.3 内核与用户空间的通信方式内核和上层应用怎么交换数据常见的有这么几种系统调用syscall最基础的方式应用通过 open、read、write、ioctl 等进入内核。procfs / sysfs内核把一些状态和配置暴露成文件用户空间读写文件就等于和内核通信。netlink用于内核主动向用户空间推送事件比如网络状态变化。BinderAndroid 特有的高频 IPC 通道。实际做系统开发时如果你要新增一个内核功能并让上层能控制它最省事的做法往往是走 sysfs 或 ioctl而不是去改 Binder。因为 Binder 的接口改动会牵动 Framework成本高得多。这是我在实际项目里踩过坑之后总结的经验能用文件接口解决的就别动 IPC 协议。3. HAL 层让 Android 摆脱一机一系统的泥潭3.1 HAL 存在的根本理由假设没有 HAL。那么 Framework 层要调用摄像头就得直接写针对某颗摄像头芯片的代码。换一颗芯片Framework 就得改。这在手机行业是不可接受的——一个 Android 版本要适配几十上百款机型每款硬件都不同。HAL 就是来解决这个问题的。它在 Framework 和内核驱动之间插了一层定义了一套标准接口。Framework 只调用接口具体实现由各芯片厂商提供。厂商只要按接口实现自己的 HALFramework 就不用改。用生活化的类比HAL 就像电源插座标准。你的电器Framework只认插头形状不管电是从火电、水电还是核电来的。发电厂硬件厂商只要按标准做插座HAL电器就能用。3.2 HAL 的三种形态与演进HAL 不是一成不变的它经历了几个阶段传统 HALLegacy HAL以.so动态库形式存在通过hw_get_module加载。接口用 C 语言定义结构体里塞满函数指针。HIDLHAL Interface Definition LanguageAndroid 8.0 引入用于 Treble 项目。把 HAL 拆成独立进程通过 Binder 化通信实现 Framework 和 HAL 的解耦。AIDL HALAndroid 11 之后主推用 AIDL 统一描述 HAL 接口进一步简化。为什么要从传统 HAL 演进到 HIDL/AIDL核心目的是让系统可以独立升级。传统 HAL 是库和 Framework 编译在一起升级 Framework 就得重新编译整个系统。Treble 之后HAL 变成独立进程Framework 升级时 HAL 不用动厂商适配成本大幅降低。3.3 一个 HAL 模块的典型结构以传统 HAL 为例一个模块通常包含// 定义模块结构 struct hw_module_t { uint32_t tag; uint16_t module_api_version; uint16_t hal_api_version; const char *id; const char *name; const char *author; struct hw_module_methods_t* methods; void* dso; // ... }; // 定义设备结构 struct hw_device_t { uint32_t tag; uint32_t version; struct hw_module_t* module; uint32_t reserved[12]; int (*close)(struct hw_device_t* device); };厂商实现时就是填充这些结构体把具体的 open、read、write 逻辑挂上去。Framework 通过hw_get_module(camera)拿到模块再open拿到设备然后调用设备的方法。这里有个实操细节HAL 模块的命名和路径是有约定的。模块 id 要和.so文件名对应放在/vendor/lib/hw/或/system/lib/hw/下文件名格式是id.board.so。如果放错位置或命名不对hw_get_module会直接返回失败而且日志里不一定有明显提示这是新手最容易卡住的地方。3.4 HAL 开发中的常见坑我在适配 HAL 时踩过的坑挑几个典型的说版本号不匹配module_api_version和 Framework 期望的不一致会导致加载失败。改 HAL 时一定要确认版本号。线程安全HAL 可能被多个进程同时调用实现时必须考虑加锁。我见过因为没加锁导致摄像头偶发花屏的案例。资源释放close函数里没释放干净反复打开关闭会内存泄漏跑久了系统就崩。日志缺失HAL 层日志默认很少出问题时建议临时加ALOGD定位完再删。提示调试 HAL 时logcat里过滤HAL、hw_get_module、dlopen这些关键字能快速定位是加载失败还是调用失败。4. Framework 层开发者最熟悉也最容易误解的一层4.1 Framework 到底包含什么Framework 层是 Java 世界和 Native 世界的交汇处。它向上给 App 提供 API向下通过 JNI 调用 Native 库再通过 Binder 和系统服务通信。核心组件包括ActivityManagerServiceAMS管理 Activity 生命周期、任务栈、进程。WindowManagerServiceWMS管理窗口、层级、输入分发。PackageManagerServicePMS管理应用安装、权限、组件信息。ContentProvider、BroadcastReceiver、Service的底层支撑。View 系统从 Measure、Layout 到 Draw 的完整流程。很多人以为 Framework 就是系统 API 的集合其实它更重要的角色是系统服务的调度中心。App 调用的每一个 API背后往往是一次跨进程调用最终落到某个 SystemService 上执行。4.2 BinderFramework 的血管如果只能记住一个 Framework 相关的机制那一定是 Binder。它是 Android 最高频的 IPC 方式AMS、WMS、PMS 全都靠它通信。Binder 的核心设计是一次拷贝。传统 IPC如管道、Socket需要两次数据拷贝用户空间到内核内核再到目标用户空间。Binder 通过内存映射让内核和目标进程共享一块内存只需要一次拷贝。这在移动设备上意义重大因为 IPC 极其频繁。Binder 通信的基本角色Client发起调用的进程。Server提供服务的进程如 system_server。ServiceManager服务注册与查询的中心。Binder 驱动内核里的中转站。一个典型的调用链是App 通过getSystemService拿到 AMS 的代理对象BpBinder调用方法时数据打包成 Parcel通过 Binder 驱动传到 system_server由 BnBinder 解包并调用真正的 AMS 实现。这里有个关键理解App 拿到的 AMS 不是真正的 AMS而是一个代理。真正的 AMS 跑在 system_server 进程里。这个代理-实体模式是理解 Framework 的钥匙。4.3 系统启动流程从按下电源到桌面出现理解 Framework 最好的方式之一是跟一遍启动流程。简化版如下Bootloader加载内核。内核启动挂载根文件系统启动第一个用户进程init。init 进程解析init.rc启动各种 native 服务其中最关键的是zygote。Zygote预加载常用类和资源然后 fork 出system_server。system_server启动 AMS、WMS、PMS 等核心服务。AMS启动 Launcher桌面出现。这条链路里Zygote 的设计非常巧妙。它预加载了大量类和资源fork 出来的进程直接共享这些内存避免了每个 App 启动都重新加载大幅提升启动速度。这也是为什么 Android 应用启动比某些系统快的原因之一。4.4 改 Framework 的正确姿势做系统定制时改 Framework 是家常便饭。但直接改 AOSP 源码风险很高我的经验是优先用 overlay 机制AOSP 支持 RRORuntime Resource Overlay可以在不改源码的情况下替换资源。新增 API 要谨慎一旦新增公开 API就要考虑兼容性和 CTS 测试。改系统服务要评估影响面AMS、WMS 的改动可能影响所有 App必须做充分回归。善用dumpsysdumpsys activity、dumpsys window能直接看到系统服务内部状态调试神器。注意改 Framework 后一定要跑 CTS。很多定制 ROM 过不了 CTS就是因为 Framework 改动引入了不兼容行为。5. 应用层与运行时ART、JNI 与跨语言协作5.1 ART 虚拟机做了什么Android 的应用代码跑在 ARTAndroid Runtime上。ART 的核心职责是执行 DEX 字节码。和早期的 Dalvik 相比ART 最大的变化是提前编译AOT加即时编译JIT的混合模式。AOT安装时把 DEX 编译成机器码启动快但安装慢、占空间。JIT运行时编译热点代码安装快但启动初期慢。混合模式结合两者安装时只做部分编译运行时根据热度再编译。理解 ART 对性能优化很关键。比如你知道某个方法被频繁调用就可以通过减少反射、避免频繁创建对象来降低 JIT 压力。5.2 JNIJava 和 Native 的桥Framework 层大量使用 JNI 调用 Native 代码。JNI 的规则不复杂但坑不少局部引用和全局引用JNI 里的局部引用在方法返回后失效跨线程使用必须转成全局引用。线程附着Native 线程要调用 Java 方法必须先AttachCurrentThread。异常处理JNI 调用 Java 方法后要检查异常否则会带着异常继续执行行为不可预期。我见过最典型的 JNI 崩溃是Native 线程里直接用了 Java 传过来的局部引用结果引用失效导致野指针。这类问题日志往往指向不明排查起来很痛苦。所以 JNI 代码一定要严格管理引用生命周期。5.3 应用层的沙箱机制每个 App 跑在独立的进程里有独立的 UID独立的文件目录。这套沙箱机制保证了应用之间互不干扰。但沙箱也带来限制比如访问其他应用数据需要通过 ContentProvider。热词里出现的content://com.xxx.fileprovider/...就是这套机制的体现。FileProvider 是应用间共享文件的官方方式通过 URI 授权避免直接暴露文件路径。理解沙箱才能理解为什么 Android 的文件访问这么绕——绕是为了安全。6. 层与层之间真正决定架构质量的是接口设计6.1 纵向通信从 App 到内核的完整链路把前面几层串起来一次典型的点击屏幕打开相机操作链路是这样的触摸事件由内核输入子系统捕获通过/dev/input上报。Framework 的 InputManagerService 读取事件分发给对应窗口。App 收到点击调用 Camera API。Camera API 通过 Binder 调用 CameraService。CameraService 通过 HIDL/AIDL 调用 Camera HAL。HAL 调用内核 V4L2 驱动操作摄像头硬件。这条链路里任何一环出问题表现都是相机打不开。所以排查系统问题时从下往上或从上往下逐层验证比盲目改代码高效得多。6.2 横向通信系统服务之间的协作系统服务之间也大量通信。比如 AMS 启动 Activity 时要和 WMS 协调窗口和 PMS 确认组件信息和 PowerManager 确认屏幕状态。这些协作都通过 Binder 完成。理解横向通信才能理解为什么改一个服务会影响另一个服务。我做过一个需求只是改 AMS 的进程优先级策略结果影响了 WMS 的窗口动画因为两者共享了进程状态。这种耦合是架构设计时必须警惕的。6.3 接口稳定性Treble 带来的改变Treble 项目是 Android 架构演进的一个分水岭。它把 HAL 从 Framework 中彻底剥离定义了稳定的接口。带来的好处是系统升级更快Framework 升级不用等厂商适配 HAL。厂商适配更省力只要 HAL 接口不变底层不用动。测试更聚焦VTS 测试专门验证 HAL 接口。但 Treble 也带来新问题HAL 变成独立进程后IPC 开销增加某些高频调用场景性能下降。所以架构演进永远是权衡没有银弹。7. 系统架构的实操排查思路与经验总结7.1 分层排查法定位问题的通用套路遇到系统问题我习惯按这个顺序排查层级排查手段典型问题应用层logcat、StrictMode崩溃、ANRFrameworkdumpsys、systrace服务异常、卡顿Nativetombstone、gdb段错误、内存泄漏HALHAL 日志、VTS硬件不工作内核dmesg、ftrace驱动报错、死锁关键是先确定问题在哪一层再深入。很多人一上来就改 Framework结果问题在内核白忙一场。7.2 几个我踩过的真实坑Binder 事务过大导致崩溃Binder 单次传输有大小限制约 1MB传大图或大列表时会抛TransactionTooLargeException。解决办法是分片或改用共享内存。HAL 加载顺序问题某些 HAL 依赖其他 HAL 先启动init.rc里的启动顺序写错会导致服务起不来。用on property触发可以解决。SELinux 权限拦截Android 5.0 之后 SELinux 强制模式HAL 访问某些资源会被拒。日志里搜avc: denied能看到需要补te规则。Framework 改动导致 CTS 失败新增 API 没加SystemApi或TestApi注解CTS 直接挂。这些坑的共同点是日志里不一定有直接答案需要结合架构知识推断。这也是为什么我一直强调要理解层与层的关系而不是死记 API。7.3 学习路径建议如果你想系统掌握 Android 系统架构我的建议是先跑通 AOSP 编译理解构建系统。跟一遍启动流程从 init 到 Launcher。挑一个系统服务比如 AMS深入读源码。自己写一个 HAL 模块跑通加载和调用。做一次系统定制比如改开机动画或加系统服务。这个过程很慢但走完一遍你对架构的理解会从知道有这几层变成知道每层怎么协作。这种理解是看多少篇架构图都换不来的。最后分享一个我个人的习惯每次遇到系统问题我都会在笔记本上画一遍涉及到的调用链路标出每一层的接口和通信方式。画着画着很多想不通的问题就通了。架构这东西看是看不会的得动手画、动手改、动手调。
返回列表