厂商定制的Android系统为什么也要解耦?
Hi,我是阿昌
,今天学习记录的是关于厂商定制的Android系统为什么也要解耦?
的内容。
一、Android 系统架构
AOSP,全称是 Android Open Source Project,中文译为“Android 开放源代码项目”。
厂商每年会基于 Google 开放的最新代码进行适配定制,开发属于自己的 OS 版本。
首先,根据 Android 的架构图来看看 Android 系统架构的设计。
对照架构图,从上到下来看。
在应用框架层上面应该还有一层,就是诸多的应用。
这些应用可以分为 2 类:
- 一类是
系统应用
,拥有高的系统权限,可以调用系统提供的高权限接口,例如打电话、短信、设置等应用; - 另外一类就是
非系统应用
,与第三方应用一样,例如定制一些便签、运动健康、视频播放等应用。
-
第一层就是
应用框架层
,应用框架最常被应用开发者使用,对应用提供标准的 API 来调用系统的能力,从而实现相关的业务功能。我们在代码编译时,通常会依赖 Android SDK 的 android.jar 空包,保证能通过编译。但需要注意的是 android.jar 具体的实现都在框架层中,实际运行时调用的都是系统中的类。 -
第二层是
Binder IPC
。有了 Binder 进程间通信 (IPC) 机制,应用框架就能跨越进程边界并调用 Android 系统服务代码。由于系统的很多服务都是运行在 System Server 进程,但是集成到应用的 SDK 代码是运行在应用的进程,所以需要通过 Binder 的方式来实现跨进程间的通信。 -
第三层是
系统服务层
。系统服务专注于特定功能的模块化组件,例如窗口管理器、搜索服务或通知管理器。例如熟悉的 AMS、WMS、PMS 等,都运行在系统服务层。 -
第四层是
硬件抽象层 (HAL)
。Google 在 Android 8.0 里一个名为“Treble”的项目中设计了 HAL 层,目的是让制造商能够以更低成本、更轻松快速地将设备更新到新版 Android 系统。在这种新架构中,HAL 接口定义语言指定了 HAL 和其用户之间的接口,让用户无需重新构建 HAL,就能替换 Android 框架。 -
最后一层是
Linux 内核层
。Google 在官网介绍的开发 Android 设备驱动程序与开发典型的 Linux 设备驱动程序类似。但 Android 使用的 Linux 内核版本包含一些特殊的补充功能,例如低内存终止守护进程、唤醒锁定、Binder IPC 驱动程序等,对于移动嵌入式平台,这些是非常重要的功能。
二、对 Anroid 系统的“魔改”
既然 Android 系统已经有了规范的架构设计,为什么定制 Android 系统还会产生耦合的问题呢?
由于手机产品涉及软硬结合,所以一般会采用 IPD 产品开发流程,研发一款手机的时间通常需要 3 - 12 个月的时间,并按
1、应用之间的耦合
理论上来说,应用之间
都是相对独立的。但是在定制系统中,有一些应用在运行时存在相互依赖,例如桌面与负一屏(基于桌面向右滑动后的快捷入口)。
这里应用 A 在运行时可能需要调用应用 B 提供的某些方法,才能保证功能正常运行,如下图所示。
这里看起来似乎合理,编译上没有依赖,运行时也是通过标准的 API 调用。但关键的问题是不同项目上的功能有差异,依赖的 API 会有变化,并且应用之间并没有做好兼容性的处理,这样导致应用 B 不存在时,应用 A 无法正常运行。
结合下图来理解:
2、应用与框架之间的耦合
应用与框架之间的耦合
。做过应用开发的同学应该知道,我们需要依赖 Android 的 SDK 来开发。因为 Google 会保持 SDK 接口的稳定及兼容,所以基于标准 SDK 开发的应用,才能运行在各个大版本的 Android 系统中。但是在框架里面还有一些类被标识了 @hide
,或者有些类属于 com.android.internal 中的类,这些都是标准的 SDK 不会提供的。但是,厂商可以编译生成完整的 android.jar 包,这样应用就可以调用到这些非公开的接口,以便实现更加丰富的功能。
当然还有一些应用采用另外一种方法,就是使用反射的形式
。
可以结合后面的示意图来理解。
由于这部分 API,Google 在大版本的迭代中并不一定保证兼容,所以这也意味着一旦使用这个特殊的 Jar 包的应用,就与特定的大版本绑定了。应用需要针对每一个大版本都维护一个特定的 APK。
遇到过一种更“反直觉”的操作——框架依赖应用。比如在框架层增加代码,会去调用 APP 中相关的资源及代码。
这种反向的耦合更
容易出现问题,因为一旦底层框架代码的兼容性没有处理好,就很容易导致无法开机、黑屏等严重后果。### 场景 3:框架之间的耦合第三种典型的耦合场景是框架之间的耦合,这里的框架耦合指的是厂商扩展的代码与框架之间的耦合。
为了扩展系统的功能,定制 Android 系统可以在框架中添加一些代码,例如可以在 AMS 里面的 Activity 生命周期回调增加一些统计代码,就能统计到应用界面的一些执行情况。
这些能力是三方应用无法实现的功能,是厂商定制应用的优势。
可以参考后面的示意图来理解。
但是缺少规范化的管理及灵活的插桩设计,也会产生耦合问题。我
Google 每年都会更新 AOSP 基线代码,框架之间的耦合会导致扩展的代码与框架代码强关联。
-
一方面这些代码只能跟
随着框架代码一起维护
,无法做到独立维护; -
另外一方面当代码有更新时,
维护成本也非常高
。
三、耦合带来的问题
除了前面提到的定制 Android 系统的耦合问题,耦合也会影响到团队效率以及产品质量,接下来就重点探讨三个常见问题。
1、大量重复的代码合并工作
前面提到 Google 每年都会升级一个 Android 的大版本,对于厂商来说,他们其实拿到的是第三手代码。
前面还有一个上游——芯片平台
。
为了帮助理解,这张示意图。
因为是第三手代码
,为了保证本地代码能及时同步上游的最新代码,厂商需要定期去同步上游的代码,大版本可能是每年一次,补丁 Patch 可能是 2 周一次。
由于侵入性的修改,容易导致代码冲突的出现,特别是每次的大版本更新。
另外,由于各种耦合的问题,通常最后量产版本时需要拉去独立的 MP 分支。
这样,并行的项目越多时,合并代码的工作量就会呈指数级爆发。
2、并行维护多个版本
由于应用于架构和耦合问题,会让不同项目集成难度升高。
因为应用无法做到一个 apk 适配多个项目,这样对于应用来说往往需要同时维护 3-5 个版本,并且通常也是采用拉取分支的形式,一个分支出一个项目版本的 APK。
同时,维护多个版本带来了大量重复性的工作。
例如当修复一个 Bug 时,需要同步到若干个分支中,并且带给测试同学的压力也非常大。
由于每个分支的代码都不是完全一致的,需要做回归测试时,工作量也会翻倍。
3、“未知”的产品质量
由于代码的耦合问题,非常容易导致修改代码出现连带问题。所以开发同学会选择尽可能少修改代码,更别谈去做一些中大型的代码重构。
在机型数量越来越多的状态下,技术复杂度越来越高。
两种压力的共同作用下,代码修改越多,代码重构就变得越来越难,代码质量完全无法把控。
另外,对于产品的质量也带来了非常大的挑战。
前面提到的多项目、多版本的问题,导致在最后集成阶段需要大量的回归测试,然而在缺乏高质量的自动化测试覆盖下,仅靠人工很难进行全面的验证,这样就非常容易导致问题流到线上用户手中。
四、总结
各个分层职责清晰,从上到下依次为应用框架层、Binder IPC、系统服务层、硬件抽象层以及 Linux 内核层。但是当厂商开始定制 Android 系统时,由于项目管理、交付压力等等情况,非常容易出现不按规矩出牌的情况,总结了 3 种最常见的耦合场景,后面的表格回顾。
由于耦合的问题,团队需要完成大量重复、机械性的代码合并工作,也需要同时维护多个并行的版本。
开发同学淹没在数不尽的分支合并任务里,测试同学淹没在数不尽的黑盒测试中,团队无法把精力投入到代码优化和更多产品质量优化工作上,时间一久,就会给系统埋下诸多隐患。