嵌入式安卓编程指南-全-
嵌入式安卓编程指南(全)
原文:
zh.annas-archive.org/md5/9bc936bbccde26c205e7f8b16cd5450f译者:飞龙
前言
Android 引发了我们这个时代最伟大的革命之一。在智能手机、电视、平板电脑、手表、嵌入式板上都有其存在,可以说是无处不在。其开源特性为公司、专家用户和黑客提供了学习、改进和定制系统、创建最流行移动操作系统的定制版本的机会。
这本书是一次从 Android 项目的起源到未来的旅程,沿途经过构建自定义 Android 系统所需的所有阶段,无论是从源代码还是从二进制镜像开始。
本书涵盖的内容
第一章,“理解架构*”,解释了 Android 硬件和软件架构、Android 兼容性定义文档、Android 兼容性测试套件和 Android 运行时。
第二章,“获取源代码 – 结构和哲学*”,解释了 Android 开源项目。
第三章,“设置和构建 – 模拟器方式*”,教你如何设置构建环境并构建 Android 模拟器的系统镜像。
第四章,“转向现实世界硬件*”,告诉你如何构建一个真实设备以及如何刷系统镜像。
第五章,“定制内核和引导序列*”,深入内核和引导序列的定制,以便打造完美的系统。
第六章,“制作”您的第一个 ROM*,讨论了自定义恢复镜像、root 权限和 Android Kitchen。
第七章,“定制您的个人 Android 系统*”,讨论了黑客攻击 Android 框架、添加应用程序和优化系统。
第八章,“超越智能手机*”,讨论了下一步是什么,一旦你离开智能手机世界,Android 的可能性是什么。
更多关于 Android N 编程:在本章中,你将找到有关 Android N 编程的更多信息,请参阅www.packtpub.com/sites/default/files/downloads/MoreaboutAndroidNProgramming.pdf。
你需要这本书
旅行所需的一切就是一台个人电脑,Ubuntu Linux 或 OS X 都可以,一个互联网连接,以及你的热情!
这本书面向的对象
如果你是一名程序员或嵌入式系统黑客,想要定制、构建和部署自己的 Android 版本,那么这本书绝对是为你准备的。
术语约定
在这本书中,你会发现许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称如下所示:“我们可以通过使用include指令来包含其他上下文。”
代码块设置如下:
LOCAL_SRC_FILES:=\
netcat.c \
atomicio.c
任何命令行输入或输出都写作如下:
$ git add art_new_feature
$ git commit -m "Add new awesome feature to ART"
新术语和重要词汇以粗体显示。屏幕上显示的单词,例如在菜单或对话框中,在文本中显示如下:“点击下一个按钮将您带到下一屏幕。”
注意
警告或重要注意事项以这样的框显示。
小贴士
小贴士和技巧如下显示。
读者反馈
我们始终欢迎读者的反馈。告诉我们您对这本书的看法——您喜欢或不喜欢的地方。读者反馈对我们很重要,因为它帮助我们开发出您真正能从中获得最大收益的标题。
要发送一般反馈,请简单地发送电子邮件至<feedback@packtpub.com>,并在邮件主题中提及书名。
如果您在某个主题上具有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南,网址为www.packtpub.com/authors。
客户支持
现在您已经是 Packt 图书的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大收益。
下载示例代码
您可以从您的账户下载此书的示例代码文件。www.packtpub.com。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
使用您的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的支持标签上。
-
点击代码下载与勘误。
-
在搜索框中输入书名。
-
选择您想要下载代码文件的书。
-
从下拉菜单中选择您购买此书的来源。
-
点击代码下载。
您还可以通过点击 Packt Publishing 网站上的书页上的代码文件按钮下载代码文件。您可以通过在搜索框中输入书名来访问此页面。请注意,您需要登录到您的 Packt 账户。
下载文件后,请确保您使用最新版本解压缩或提取文件夹。
-
Windows 上的 WinRAR / 7-Zip
-
Mac 上的 Zipeg / iZip / UnRarX
-
Linux 上的 7-Zip / PeaZip
书籍的代码包也托管在 GitHub 上,网址为github.com/PacktPublishing/Learning-Embedded-Android-N-Programming。我们还有其他来自我们丰富图书和视频目录的代码包可供在github.com/PacktPublishing/找到。查看它们吧!
勘误
尽管我们已经尽一切努力确保内容的准确性,但错误仍然会发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误表,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分。
侵权
在互联网上侵犯版权材料是一个跨所有媒体的持续问题。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现任何形式的我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过<copyright@packtpub.com>与我们联系,并提供涉嫌侵权材料的链接。
我们感谢您在保护我们的作者和提供有价值内容的能力方面的帮助。
问题
如果您对本书的任何方面有问题,您可以通过<questions@packtpub.com>与我们联系,我们将尽力解决问题。
第一章:理解架构
在本章中,用户将了解 Android 的硬件和软件架构。我们将概述Android 兼容性定义文档,以便正确理解我们为了创建一个完全合规和认证的设备需要什么。
用户将了解Android 应用框架(AAF)、两个不同的 Android 运行时系统——Dalvik 和 ART,以及 Google 提供的系统库集合。
用户将有一个第一次上手的机会,设置和运行 Android 兼容性测试套件。我们将一起测试一个现有的认证设备,并迈出创建认证设备道路的第一步。
Android 系统概述
就像每个其他操作系统一样,Android 有一个基于层的结构。下一张图展示了整个系统架构的正确抽象概述:

我们可以将系统分为以下主要层:
-
Linux 内核
-
硬件抽象层
-
核心库和运行环境
-
应用框架
-
Binder IPC
-
应用程序
软件层最接近硬件架构的是Linux 内核。这一层负责与硬件组件通信,并为上层提供了一个易于使用的接口。
在架构路径上继续前进,我们有 Android 运行时和核心库。这一层为应用框架提供了基本工具。应用框架是一组现成可用的组件,系统通过 Android SDK 将这些组件提供给应用层。顶层包含我们每天使用的所有应用——游戏、生产力应用、多媒体等。
Linux 内核
Android 基于 Linux 内核,但它不是一个经典的基于 Linux 的桌面系统:它不是 Ubuntu。然而,Android 架构设计师和开发者依赖于 Linux 内核,因为它开源,全球范围内经过广泛测试,并且可以轻松定制以适应 Android 特定的硬件需求,适用于任何类型的设备。
从一个非常实际的角度来看,选择基于开源核心来构建系统强化了 Android 的开放系统哲学,由其社区支持,并得到企业公司的信任,这得益于其透明度。此外,这种方法节省了大量开发时间——他们不必从头开始,可以专注于架构的其他部分,利用一个流行且文档齐全的核心。
原味 Linux 内核需要一些调整才能正确满足所有 Android 的要求。谷歌的大部分贡献集中在以下方面:
-
修复错误
-
启用新硬件
-
提高电源管理
-
改进错误报告
-
提高性能
-
提高安全性
从硬件的角度来看,Android 团队在 Linux 内核中添加了许多新功能。发布了大量修复和黑客工具来改进蓝牙支持和管理,添加了许多通用输入/输出(GPIO)驱动程序,增强了 ARM 兼容性,因为 ARM 是 Android 支持的主要架构,同时 MMC 管理也收到了许多贡献。新增加了 ADB 设备驱动程序,帮助开发者通过 USB 与外部设备通信。
从内存的角度来看,Android 团队引入了 PMEM,即进程内存分配器。这使得能够在用户空间和内核空间之间管理大型的物理连续内存区域。在特定的低资源硬件领域工作,Android 团队发布了 Ashmem,Android 共享内存,它针对低内存设备,并提供了一个易于使用的基于文件的 API 来管理共享内存,尤其是在内存压力下。
从电源管理的角度来看,Android 团队引入了一个改进的挂起系统、唤醒锁和 Android 闹钟定时器,这是内核实现以支持 Android 闹钟管理器。
其他有趣的贡献包括对 Android logcat 命令的内核支持,该命令提供了系统消息、应用调试消息和异常的日志,以及 Android Binder,这是一个 Android 特定的进程间通信系统,也用于远程方法调用。
硬件抽象层 – HAL
为了克服日益增长的硬件碎片化,Android 工程师创建了一个抽象层,允许系统仅通过了解特定的互通信接口与硬件交互。系统完全忽略了硬件和驱动程序的低级实现。这种方法强制执行了“针对接口”而不是“针对实现”开发软件的理念。采用这种方法,Android 系统不知道也不需要知道如何访问或管理硬件。
作为硬件和系统之间的中间层,Android HAL 通常使用本地技术——C/C++和共享库来开发。谷歌没有关于我们如何实现我们的 HAL 和设备驱动程序的约束:我们有权根据我们的场景设计它。只有一个简单的规则:
我们的实现必须提供系统所期望的相同接口。
库和应用程序框架
在架构层次结构中向上,我们发现两个最重要的软件层。Android 应用程序框架和 Android 系统库是裸硬件(由 Linux 内核管理)和我们在智能手机上拥有的所有那些花哨、闪亮的应用程序之间的中间件。
库
Android 系统库是一组专门为 Android 创建的库,用于允许并帮助系统组件和应用程序开发。其中最重要的是:
-
SQLite:SQLite 是进入 SQL 世界的大门。它是一个针对嵌入式系统的微小 SQL 实现,它提供了一种标准方式来访问内容提供者发布的数据或用户创建的 SQL 数据库。
-
SSL:SSL 为网络通信提供标准的安全环境。
-
OpenGL: OpenGL 库是 Java(以及 C/C++ JNI)世界与 OpenGL/ES 3D 图形渲染 API 之间的桥梁。
-
SGL:SGL 提供了一种访问 2D 渲染引擎的方法。
-
媒体框架:媒体框架为渲染、录制和回放最常见的媒体格式提供编解码器。
-
WebKit: WebKit 是流行的 HTML 渲染引擎。
-
libc:libc 库是 BSD 派生的标准 C 库实现,专门针对嵌入式基于 Linux 的设备进行优化以获得最佳性能。
-
Surface manager:Surface manager 管理对显示子系统的访问。
应用程序框架
这是 Android 软件生态系统的核心。它提供了一系列管理器,这些管理器简化了 Android 开发者和 Android 系统本身的常见任务。应用程序框架最重要的组件包括:
-
活动管理器:提供导航回退栈并管理 Android 活动生命周期。
-
资源管理器:提供对应用中包含的非代码资源的访问:图形、本地化字符串、样式和颜色。
-
位置管理器:负责提供最准确的位置信息,使用由 GPS 传感器收集的数据、附近的基站和 Wi-Fi 网络。
-
通知管理器:根据谷歌设计指南,允许应用在状态栏中显示通知警报,以提供一致且熟悉的用户体验。
-
内容提供者:提供了一种在不同应用之间共享数据的方法,例如访问联系人数据或在两个应用之间共享公共数据集。
-
视图和控件:这些构成了 Android 体验的 UI 核心。按钮、文本字段和布局是每个 Android 系统组件和用户应用的构建块。
在 Android 上实现一切都是使用官方的 Android SDK,它提供了一种一致且文档化的方式来使用所有这些系统管理器、视图和逻辑组件,让您能够创建下一个 Google Play Store 的大热门。
Binder IPC
从应用程序框架的角度来看,Binder 进程间通信(IPC)是一个隐藏层。它负责在高级 Android API(可通过 Android SDK 访问)和实际 Android 系统之间创建一个透明的通信通道。
应用程序层
所有由第三方实体(如智能手机制造商或 Android 程序员)创建的应用都将安装在应用程序层。
通常,这依赖于手机固态存储的读写区域,但对于制造商提供的软件,通常,它使用只读存储区域以确保这些应用无论发生什么情况都能始终安装。例如,Google Maps、YouTube、Samsung TouchWiz Nature 和 HTC Sense 都属于这一类应用:它们与设备的操作系统一起发货,安装在设备的只读存储区域,并且作为系统核心组件,它们旨在不可卸载。
正如我们将看到的,这并不完全正确——一旦你掌握了适当的技能集,你将能够操纵整个系统。在接下来的章节中,你将获得这些技能,并学习如何大幅度修改现有的 Android 版本,并在必要时删除这些应用。
Android 兼容性
市场上每一款成功的 Android 设备在上市前都经过了认证。制造商根据精确的指南、规则和限制设计了、开发和测试了他们的设备。
为了使任务尽可能简单,谷歌创建了 Android 兼容性计划,该计划定义了帮助 OEM 创建能够正确支持操作系统、SDK 和开发者期望的详细信息和工具:
"在多种 Android 设备上运行 Android 应用。"
作为制造商,创建和分发一个经过认证的设备具有至关重要的意义。我们的目标是创建一个独特但同时又熟悉的用户体验:我们必须酷,但不能奇怪!用户希望自定义他们的 Android 设备,并且他们想要确保他们最喜欢的应用能够顺畅运行,没有任何问题。开发者希望确保他们不会浪费时间在每一款不同的智能手机、平板电脑或电视上修复错误——他们希望有一个他们可以依赖的共同生态系统。
一个定义良好且得到良好支持的生态系统带来了更多经过认证的设备,这些设备带来了更多开发者,进而带来了更多快乐的用户。以下图表显示了 Android 生态系统如何通过不断创建设计精良、生产精良、经过认证的设备而得以生存:

Android 兼容性定义文档
Android 兼容性定义文档(CDD)是谷歌指定要考虑的指南、规则和约束的方式,以使设备兼容 Android。每个设备设计师和制造商都必须参考 CDD,以便能够轻松地将 Android 移植到自己的硬件平台上。
对于 Android 平台的每个版本,谷歌都提供了一份详细的 CDD。CDD 代表了 Android 兼容性的政策方面,其作用是编纂和阐明所有要求,消除任何歧义。主要目标是提供规则,让制造商能够创建与 Android SDK 和 Android 应用兼容的复杂硬件设备。
设计和开发一款新设备并非易事。即使是细节也至关重要。想想 OpenGL 支持。我们无法保证图形体验对用户来说会很好。唯一可能的是按照指南工作,然后“测试,测试,再测试”。这就是为什么尽可能提供尽可能多的细节和指南是帮助制造商实现目标唯一途径。
然而,CDD 并不试图全面——它不可能做到。它只是作为指导,尽可能容易地接近最终目标——一个兼容的设备。进一步的帮助来自源代码本身和 Android SDK API,它可以被视为兼容性测试平台。将 CDD 视为符合最小约束集的概述:它是旅程的第一步。
设备类型
在最初,Android 是为了在数码相机上运行而诞生的。幸运的是,从那时起发生了许多事情:智能手机入侵了我们的世界!然后我们有平板电脑和 MP3 播放器。如今,我们有电视、手表、媒体中心、眼镜,甚至汽车,都在运行 Android 和 Android 应用。市场上的每款设备都可能根据其功能被归入一个特定的类别。CDD 提供了一些关于您的设备将被放置在哪个类别的指导:
-
每个具有内置触摸屏、允许移动的电源和可以手持的设备都可以被认为是Android 手机。
-
Android 电视设备是一种为媒体内容设计的设备:视频、音乐、电视、游戏,用户距离大约三米或十英尺。这类设备必须具备内置屏幕或输出视频接口——HDMI、VGA、DVI 或无线显示端口。
-
一种设计用于佩戴在手腕上的设备,具有对角线长度在 2.79 厘米到 6.35 厘米之间的触摸屏显示器,被认为是Android 手表。
-
拥有一辆基于 Android 的信息娱乐系统汽车,为我们提供了Android Automotive 实现。
软件兼容性
从软件执行的角度来看,基本要求是能够执行 Android Dalvik 字节码。我们的设备必须支持Android 应用程序编程接口,并且必须提供任何由 Android SDK 公开或用@SystemAp注解标注的 API 的任何文档化行为的完整实现。
硬件兼容性是一项棘手的工作,因为即使我们的设备缺少某些特定硬件,例如 GPS 或加速度计,我们的实现也必须包含与 GPS 相关的代码,并且应该能够以合理的方式处理不适当的请求,以避免崩溃或不良行为。
软件兼容性的主要参与者之一是我们设备支持意图的能力。每个正确实现 Android API 的设备都必须支持 Android 松耦合意图系统。意图允许 Android 应用程序轻松请求其他 Android 组件的功能,并避免从头开始实现一切的努力。Android 系统有一套实现意图模式的内核应用程序:
-
桌面时钟
-
浏览器
-
日历
-
联系人
-
相册
-
全局搜索
-
启动器
-
音乐
-
设置
作为供应商,我们可以集成默认的 Android 组件或根据公共 API 实现自己的组件。这些组件将具有特殊系统权限,作为系统应用,并将是匹配意图过滤器首选的第一个选择。
例如,当开发者请求打开一个网页时,系统将建议“我们的浏览器组件”作为执行此任务的首选应用程序。当然,作为一个好公民,我们必须提供一个适当的设置菜单,使用户能够覆盖我们的默认选择,并让最终用户为任务选择不同的应用程序。
超越 Java
Android 应用程序开发主要基于 Java 编程。SDK 基于 Java,运行时系统完全符合 Java6,部分符合 Java7,而谷歌已经在尝试 Java8。如果开发者已经熟悉 Java 编程语言,他们很容易接近这个平台。然而,Android 为那些处理重型、性能导向场景的开发者提供了更多:Android 原生 API。
原生 API
原生 API 为开发者提供了从 Android Java 应用程序调用原生C和部分C++代码的机会。原生代码被编译成标准的 ELF .so 文件,并存储在应用程序 APK 文件中。作为原生代码,它必须为我们将要支持的每个架构进行编译,因为与字节码不同,它不能编译一次并在每个架构上运行。
作为集成者,我们必须接受一个或多个Android 应用程序二进制接口(ABIs)并力求与 Android NDK 完全兼容。当然,谷歌提供了指南和约束,以便轻松达到这一目标。这些是确保兼容性的基本规则:
-
我们的实现必须包括对在托管环境中运行代码的支持,即 Java 代码,以使用标准的Java 原生接口(JNI)语义调用原生代码
-
如果我们的实现支持 64 位 ABI,我们也必须支持其相关的 32 位版本,因为我们必须为非 64 位潜在设备提供兼容性
-
谷歌建议我们使用 Android 开源项目中可用的源代码和头文件来构建我们的实现——只是不要重新发明轮子
从库的角度来看,我们的实现必须与以下所有库源代码兼容(即,头文件兼容)和二进制兼容(对于 ABI):
-
libc (C 库)
-
libm (数学库)
-
liblog (Android 日志)
-
libz (Zlib 压缩)
-
libdl (动态链接器)
-
libGLESv1_CM.so (OpenGL ES 1.x)
-
libGLESv2.so (OpenGL ES 2.0)
-
libGLESv3.so (OpenGL ES 3.x)
-
libEGL.so (原生 OpenGL 表面管理)
-
libjnigraphics.so, libOpenSLES.so (OpenSL ES 1.0.1 音频支持)
-
libOpenMAXAL.so (OpenMAX AL 1.0.1 支持)
-
libandroid.so (原生 Android 活动支持)
-
libmediandk.so (原生媒体 API 支持)
这些库还提供了对 C++ JNI 接口的最小支持,以及 OpenGL 的支持。
我们的系统必须包含这些库的每个实现,才能与 Android NDK 兼容。这是一个动态列表,我们不能将其视为一个确定的库集合:Android 的未来版本可能会添加新的库,并增加开发可能性和场景。这就是为什么原生代码兼容性具有挑战性。因此,谷歌强烈建议使用 Android 开源项目中列出的库的实现,利用 Android 的开放源代码哲学,并享受良好支持和测试过的源代码。
维护 32 位支持
现在,所有主要制造商都在转向 64 位架构,新的 ARMv8 架构废弃了许多旧的 CPU 操作。不幸的是,市场上仍然充满了 32 位兼容的软件,甚至在 64 位架构上,我们也必须支持这些废弃的操作,以避免吓跑开发者并失去宝贵的市场份额。幸运的是,我们可以选择通过真实硬件支持或软件仿真来提供它们,这将以性能为代价。
支持 32 位架构可能非常棘手。我们可以考虑一个简单的场景,例如,访问/proc/cpuinfo文件。Android NDK 的旧版本使用/proc/cpuinfo来发现 CPU 特性。为了与使用 32 位 NDK 构建的应用程序兼容,我们必须在 32 位 ARM 应用程序读取时,在/proc/cpuinfo中特别包含以下内容:
-
特性:此列表之后是设备支持的任何可选 ARMv7 CPU 特性的列表
-
CPU 架构:此列表之后是一个整数,描述设备支持的最高 ARM 架构(例如,ARMv8 设备的 8)
困难的部分是,这些要求仅在 32 位 ARM 应用程序读取/proc/cpuinfo时适用。当由 64 位 ARM 或非 ARM 应用程序读取时,文件不得被修改。
从 Dalvik 到 ART 运行时
原始的 Android 运行时实现是 Dalvik。Dalvik 是一个虚拟机,专门为 Android 创建,由于需要针对低内存设备,它是系统的一个基本组成部分,直到 Android KitKat。
正如我们之前所说,Android 应用程序大多是用 Java 编写的。当 Dalvik 是正在使用的运行时系统时,Java 代码被编译成字节码。然后这些字节码被转换成 Dalvik 字节码,并最终存储在.dex(Dalvik 可执行文件)中。在此过程之后,Dalvik 能够运行 Android 应用程序。
尽管 Dalvik 是为慢速设备、低内存设计的,其性能从未令人惊叹,甚至在 Android 2.2 Froyo 中引入即时编译时也是如此。Dalvik JIT 本应给 Android 应用程序带来巨大的性能提升,从某些角度来看,它确实做到了,但有一些限制,例如臭名昭著的最大方法数,以及来自替代解决方案的压力迫使谷歌寻求新的运行时:

Android 运行时
当 Android 4.4 KitKat发布时,用户可以在设置菜单中选择一个新的实验性运行时环境:ART。Android RunTime 或简称 ART,是当前默认的运行时解决方案,自 Android 5 Lollipop以来取代了 Dalvik。前面的图表显示了 Dalvik 和 ART 架构的比较。
Dalvik 的 JIT(即时)执行背后的想法是在应用程序执行时分析应用程序,并动态地将字节码中最常用的部分编译成本地机器代码。这些最常用部分的本地执行称为跟踪,这将大大加快应用程序的执行速度,尽管大部分代码仍然需要解释。
一种新的旧方法 – AOT 编译
Art 重新引入了 AOT(提前时间)编译的概念。它的工作方式与大多数编译器类似,也就是说,它将整个应用程序代码编译成本地机器代码,完全不解释字节码。这需要一些时间,但用户下载应用程序时只编译一次,所以考虑到每次应用程序启动时所需的 JIT 分析和优化所需的时间和资源量,这是一个可接受的权衡。此外,由于整个应用程序现在都已编译,整体上更快,功耗降低,这提高了设备的自主性。
ART 自 Android 5 以来成为默认运行时,但 Android 需要确保与市场上所有已安装的应用程序以及运行先前版本 Android 且不会收到任何操作系统更新的所有设备保持兼容性。
由于向后兼容性的原因,ART 和 Dalvik 的输入字节码是相同的。应用程序 APK 文件仍然包含标准的.dex文件,但用标准的 Unix ELF 文件(可执行和链接格式)替换了.odex文件(优化后的 Dalvik 可执行文件)。在安装过程中,ART 使用dex2oat实用程序将字节码编译成存储在 ELF 文件中的本地代码。如前所述,此步骤只执行一次,并且比 Dalvik 的 JIT 编译需要更少的资源和开销。缺点是 APK 文件更大,因为它们实际上包含了双倍的代码(未编译的字节码和编译后的可执行文件)。在此编译之后,系统将仅运行 ELF 可执行文件。
最终结果是应用程序运行更快,但智能手机内存中的空闲空间会少一些。
垃圾回收和其他改进
AOT 编译不是 ART 带来的唯一改进。其中一个最重要的特性是改进的垃圾回收。垃圾回收(GC)是一种自动内存管理形式,与旧的想法完全不同,旧的想法是开发者在需要时分配内存,在不再需要时释放内存。
整个理念基于垃圾回收器的概念,这是一个尝试回收程序中不再使用的对象占用的内存的实体。它是 Java 世界中的一个知名工具,Android 一直受其缺点的影响——GC 非常慢且会阻塞。
Android 2.3 引入了并发垃圾回收器——当垃圾回收发生时,GC 不再阻塞应用程序,但发生时总会导致整体速度放缓。最后,ART 引入了一些更多的性能改进:
-
只需一次暂停进行垃圾回收,而不是 Dalvik 的两次暂停
-
GC 处理现在是并行化的,减少了 GC 暂停的持续时间
-
新的 Rosalloc 内存分配器使用线程局部区域分配来处理较小的对象,并为较大的对象使用单独的锁,而不是单个全局锁
-
只有当手机锁定时才运行完整的垃圾回收,这样用户就不会注意到 GC 何时运行
-
有一种压缩 GC 可以减少内存碎片,从而减少因为需要更大的连续内存块而杀死其他应用程序的需求
从开发和调试的角度来看,ART 引入了对采样分析器的支持,对更多调试功能的支持,以及在异常和崩溃报告中改进的诊断细节。
等待 Android Nougat
即将推出的 Android 版本将为当前的 ART 运行时带来一些增强。谷歌将引入所谓的基于配置文件的 JIT/AOT 编译。JIT 代表即时编译,类似于旧的 Dalvik 方法:一个具有代码分析能力的编译器。这个 JIT 编译器将与 ART 合作,并会提供持续的性能改进,因为它将不断分析代码和资源使用情况。
为了在安装阶段提高性能,ART 不会预先编译整个应用程序。相反,得益于分析方法,它将检测应用程序中的热点方法,并且只会预先编译这些方法,而将应用程序未使用的部分保持未编译状态。这个预编译过程在设备空闲且充电时智能执行,以最小化对用户体验的负面影响,并允许用户瞬间安装应用程序,这在 Android 6 中可能需要几秒钟才能安装。
整个新的方法旨在提高低端设备上的应用程序和系统性能,减少 RAM 内存占用,降低电池消耗,并提高运行时性能,以在广泛的设备上提供令人满意的 Android 体验。
符合兼容性测试套件
我们了解 CDD,并尽最大努力创建了一个兼容的设备。许多方面可能仍然存在故障,我们当然希望消除它们。为了确保一切按预期工作,我们的 Android 实现必须使用 Android 兼容性测试套件进行测试。Android CTS 将伴随我们走过认证设备之旅。我们将不断使用它来关注哪些正在工作,哪些尚未工作。
每个新的 Android 平台版本都附带一个新的兼容性测试套件(CTS)。这个自动化测试套件有两个主要组件:
-
Tradefed,它管理桌面上的文本执行。
-
在测试设备(DUT)上执行的测试用例。这些案例是使用 Java 编写的常规 JUnit 测试,打包成 Android
.apk文件,以便在目标设备上执行。
此外,还有 CTS 验证器,这是一个用于手动测试的工具,它包括在设备上执行并收集测试结果的验证器应用程序;以及在其他桌面机器上执行的其它可执行文件或脚本,以便为验证器应用程序中的某些测试用例提供更多数据或控制。
以下图表展示了 CTS 工作流程:

您计算机上的测试套件将在设备上安装测试并启动它。设备将测试特定的功能子集,并将结果返回到您计算机上的测试套件。测试套件将存储这些结果,安装下一个测试,并再次开始循环,直到每个测试都执行完毕。
目前,CTS 提供两种主要的测试用例类型:
-
单元测试
-
功能测试
单元测试测试 Android 平台中代码的最小逻辑单元,例如,一个单独的类,如java.util.HashMap。
功能测试用于测试特定的功能,这可能包括多个 API 方法调用。
Google 计划在测试用例的未来版本中提供更多测试。一些想法包括:
-
鲁棒性测试:这项测试是在压力条件下测试系统的耐用性
-
性能测试:这项测试是测试系统的性能,例如每秒帧数
以下表格显示了兼容性测试套件覆盖的区域:

CTS 设置
我们的旅程将非常实用和动手,这就是为什么在本节中我们将设置 Android 兼容性测试套件来测试现有设备。在不知道我们将支持什么以及要测试什么之前,我们不能开始我们的 Android 实现工作。为了能够运行 Android CTS,我们需要:
-
运行 Linux 或 OS X 的计算机
-
Android SDK:
developer.android.com/sdk/installing/index.html -
Java SDK 6 或 7:
www.oracle.com/technetwork/java/javase/downloads/index.html -
Android CTS:
source.android.com/compatibility/downloads.html -
Android CTS 媒体:
dl.google.com/dl/android/cts/android-cts-media-1.1.zip
需要下载的文件有很多。同时,我们将设置我们的设备。
设备设置
我们正在测试一个现有的设备,一部智能手机,所以我们已经满足了诸如拥有屏幕等需求,我们可以转向设备软件配置。
测试应在干净的设备上执行,因此我们应该运行出厂重置来擦除智能手机上的所有数据。如果您不是使用开发设备,请确保备份了您的数据。在 Android 4.4 KitKat 上,您可以通过导航到设置 | 备份与重置 | 出厂数据重置来访问特定菜单。
这将需要一些时间——设备将关闭,擦除过程将开始。该程序将移除不属于您设备上提供的原始 Android 系统中的每个字节,恢复所有设置,并将设备恢复到原始设置。
当设备重启时,我们需要通过导航到设置 | 语言和输入 | 语言来选择英语(美国)语言。
现在我们需要打开位置:我们需要 Wi-Fi 和 GPS,我们需要提供一些互联网连接。我们需要通过导航到设置 | 安全 | 屏幕锁定 = '无'来禁用任何屏幕锁定。
我们需要从开发者选项菜单中设置一些设置。在全新安装的纯净 Android 系统中,此菜单是隐藏的。我们可以通过以下步骤启用:
-
导航到设置 | 关于手机。
-
滚动到页面底部。
-
持续点击构建号项。
-
您现在是一名开发者!
注意
如果您使用的是 HTC、三星或索尼设备及其定制的 Android 版本,之前的步骤可能略有不同。我们将将其留作练习,以找到适合您非标准 Android 版本的正确导航路径。
一旦 开发者选项 菜单被启用,导航回 设置 屏幕。在 开发者选项 菜单中,我们需要启用以下选项:
-
USB 调试
-
保持清醒
-
允许模拟位置
在运行任何测试之前,确保设备在一个稳定的支撑物上,以避免触发加速度计和陀螺仪。相机应该指向一个可聚焦的对象。测试期间不要按任何按钮或键——这可能会使测试结果无效。
媒体文件设置
正确运行所有测试,我们需要在设备上准备一些多媒体文件——Android CTS 媒体文件。首先,让我们将设备连接到 USB。如果这是您第一次将此设备连接到这台主机 PC,设备将显示一个对话框以授权连接——允许连接:
任何 Android 设备都可以使用 Android ADB 与主机 PC 通信。这个关键工具将在下一章中详细介绍,所以现在我们可以根据您的平台从 developer.android.com/studio/index.html#downloads 开始下载最新的 Android SDK。下载完成后,解压缩文件,您将获得一个包含 platform-tools 文件夹的 android-sdk 文件夹,其中包含可执行的 adb。
现在回到我们的媒体文件设置:
-
打开终端。
-
导航到下载的文件,例如:
$ cd ~/Downloads -
解压文件:
$ unzip android-cts-media-1.1.zip -
使用以下命令进入全新的
android-cts-media文件夹:$ cd android-cts-media -
此文件夹包含一个我们必须使其可执行文件:
$ chmod u+x copy_media.sh -
现在我们准备将所有需要的媒体文件复制到设备上:
$ ./copy_media.sh all
下一个截图显示了整个过程的输出:

运行!
现在一切就绪,我们可以使用 cts-tradefed 运行一些测试计划。移动到 Android CTS 文件夹,并运行以下命令以进入 cts 控制台:
$ ./tools/cts-tradefed

之前的截图显示了 cts-tradefed 如何自动识别我们连接的设备并准备测试。
CTS 控制台提供了一些有用的命令:
-
list plans:这将列出存储库中所有可用的测试计划 -
list packages:这将列出存储库中所有可用的测试包 -
run:这将允许我们运行所有想要的测试
通常,以下测试计划可用:
-
所有兼容性所需的 CTS 测试
-
签名测试检查所有公共 API 的签名验证
-
Android API 的 Android 测试
-
Java 核心库的 Java 测试
-
ART 或 Dalvik 的虚拟机测试
-
对您的实现进行性能测试
作为我们对 CTS 的第一次尝试,我们将运行 CTS 计划:
cts-tf > run cts --plan CTS --disable-reboot
测试将立即开始,控制台将在一瞬间充满日志消息,如下面的截图所示:

现在,拿一些咖啡或泡一些好茶:这需要一段时间。cts-tradefed 将测试所有可以通过自动测试进行测试的内容。幸运的是,有很多内容可以通过这种方式进行测试。
分析测试结果
时间已经过去,茶已经喝完,测试结束了。在四核智能手机上,例如摩托罗拉 Moto G 或 Nexus 4,这可能需要长达 10 小时。最终,我们得到了一些可以检查的不错的结果。根据我们正在工作的文件夹路径,我们将在 cts 文件夹中有一个 .zip 文件的结果:
$ unzip ~/bin/android-cts/repository/results/START_TIME.zip
解压文件后,我们会找到一个 testResult.xml 文件。使用最新的网络浏览器(Firefox 在这里运行良好)打开此文件将显示许多有意义的表格,包含各种测试和结果。下一个截图显示了初始测试摘要。我们有关于测试持续时间、执行了多少测试、多少测试通过以及多少测试失败的信息:

如您所见,即使是测试目前市场上认证的手机,也会产生一些失败的测试。这让您对制造完美安卓设备的复杂性有了概念。
下一个截图显示了按包分组的测试摘要,指定了一个接一个的测试结果。为了简洁,我们只显示了一部分结果:

之前的测试摘要截图显示有 29 个测试失败。如果我们深入研究测试结果文件,我们会看到还有详细的报告可用。这些进一步的信息对于精确地定位失败的测试,如以下截图所示,并调查问题非常有用:

测试结果文件试图表现得礼貌一些,为了简洁,没有显示失败测试的完整堆栈跟踪。要达到失败的堆栈跟踪,我们必须检查 testResult.xml 的源代码。对于每个执行的测试,都有一个相应的 <Test> 标签。对于失败的测试,我们也会有 <StackTrace> 标签。这就是我们要找的!
最后一点需要注意的是,testResult.xml 包含了一个包含所有已检索到的设备信息的巨大部分。这是一大批数据,为了简洁,我们在这里没有报告,甚至没有作为例子。
深入使用 CTS Verifier
我们已经知道,有很多 API 和函数我们可以使用 cts-tradefed 自动测试,但那些在自动化环境中无法测试的其他 API 和函数怎么办?
当设备上无法手动输入的情况下,API 或函数无法测试时,CTS Verifier 就会出现。这些场景包括音频质量、触摸屏有效性、加速度计精度和反应性、相机质量,以及专门为人类交互而设计的功能,这些功能在没有人类交互的情况下无法测试。
设置
运行 CTS Verifier 所需的只是 Android 认证设备和相应的 CTS Verifier APK 文件。由于我们正在测试 Android 4.4 设备,我们需要注意下载正确的 CTS Verifier 版本。您可以从这里下载适合您 Android 版本和设备架构的 APK:source.android.com/compatibility/downloads.html。
您只需解压下载的文件,您将找到一个文件夹层次结构和两个 .apk 文件。您可以使用 ADB 安装 CtsVerifier.apk:
$ adb install –r CtsVerifier.apk
下一个屏幕截图显示了正确安装的 CTS Verified 应用程序和初始屏幕:

手动测试
如我们所知,CTS Verifier 包含需要手动输入来执行、评估、通过或失败的测试。每个测试都有自己的“信息”屏幕,帮助测试人员执行测试。例如,我们将运行“加速度计测试”,在“传感器”部分。
启动测试,我们欢迎信息屏幕,如图所示的下一个屏幕截图:

“信息”按钮解释了如何执行测试以及要评估的内容。随着我们进入测试阶段,我们可以评估加速度计是否按预期工作。以下屏幕截图显示了测试的三个不同时刻:
-
智能手机放在桌子上
-
智能手机手持,竖屏模式
-
智能手机手持,横屏模式
![图片]()
如“信息”按钮中所述,箭头始终指向与重力相同的方向:传感器工作正常。我们可以认为我们已经通过了测试,并点击“通过”按钮。
我们已经通过了第一次测试。CTS Verifier 提供了数十项测试,我们将逐一运行、验证并通过它们,在通往我们第一个 Android 认证设备的漫长旅程中。
获取结果
当每个测试都执行完毕后,我们可以使用初始屏幕右上角的“保存”图标来保存结果,如图所示的前一个屏幕截图。结果将保存在设备上,并将显示精确路径的对话框,如图所示的下一个屏幕截图:

现在,让我们打开一个终端,并将所有结果从手机复制到我们的电脑上:
$ adb pull /mnt/sdcard/ctsVerifierReports/ .
$ unzip *.zip
到目前为止,我们有一个包含我们手动执行的所有测试信息的 ctsVerifierReport-[…].xml 文件。
恭喜!您已完全测试了一款 Android 设备。我们旅程的第 0 步已完成。
摘要
在本章中,我们学习了创建认证 Android 设备所需的内容。我们看到了 Android 兼容性定义文档,并学习了如何设计一个与 Android 架构相匹配的系统。我们还对两个不同的运行时系统:Dalvik 和 ART 及其主要区别有了概述。
我们对 Android 设备测试进行了全面的学习,我们学习了如何在已认证的设备上运行 CTS 自动化测试和 CTS 手动测试。
下一章将非常实用。我们将学习如何检索 Android 源代码,并了解代码的结构和组织。
第二章. 获取源代码 – 结构和哲学
在上一章中,我们概述了基于系统层的架构,并有了我们的第一次实际操作体验,使用 CTS 工具套件测试了一个真实世界的设备。
在本章中,用户将了解 Android 和谷歌开发模型背后的哲学。我们将展示源代码的组织方式,主要分支是什么,以及工作流程是什么。我们将创建一个逐步的旅程,以检索Android 开源项目(AOSP)的源代码,并通过安装所有必需的工具来准备环境。
用户将学习如何为 Android 开源项目做出贡献,如何使用诸如git、版本控制系统和repo、仓库管理器等工具。
为了完成本章,我们将深入研究 AOSP 文件夹结构,分析创建世界上最受欢迎的移动操作系统的最重要的组件。
Android 的哲学
Android 是一个开源平台,旨在与大量不同的设备兼容,从硬件角度来看,到用途角度来看。主要目标是提供一个免费可用的软件平台,供大型企业公司和小型独立制造商甚至单个开发者使用。Android 旨在提供一个简单的方法来创建创新解决方案,并轻松地将它们推向市场。
一切始于 2005 年,当时谷歌收购了 Android Inc.,这是一家小型公司,正在为移动设备开发操作系统。几年后,开放手机联盟诞生了。2007 年,包括移动运营商、手机制造商、半导体和软件公司在内的 84 家公司公开宣布了他们全新的、即将推出的移动操作系统。
在接下来的整整一年里,该项目一直保密。谷歌努力将项目带到 1.0 版本,并于 2008 年将 Android 展示给了世界。在接下来的七年里,系统发布了四个主要版本。以下表格讲述了所有 Android 版本的年代顺序历史。每个版本都作为开源软件发布给了世界。每个版本,除了Honeycomb,这对谷歌来说是一次丑陋的公关失误,谷歌花费了大量精力来降低其影响,并尽快用Ice Cream Sandwich来替代它:

许可证
创建一个开源平台引发了一些关于哪种许可证能在保护和自由之间达到完美平衡的担忧。目标是给制造商足够的自由来调整系统以适应他们自己的硬件,同时不必担心暗箱操作许可证,试图窃取他们的知识产权。为了达到这个目标,谷歌选择了当时最著名的开源许可证之一,并将其应用于操作系统的绝大部分。
谷歌用来保护安卓开源项目(AOSP)的许可证是 Apache 软件许可证,版本 2.0,也称为 Apache 2.0,它涵盖了发布到 AOSP 的几乎所有代码行。Apache 2 许可证不适用于一个大型系统组件——内核。Linux 内核受 GNU 公共许可证 V2 保护,并附带一个系统例外,以便能够与 Android 一起分发。
作为开源且易于适应流行硬件,它以火箭般的速度将 Android 推向了移动市场的顶端,全球拥有超过一亿活跃设备,在 Google Play Store 上有超过一百万个应用程序可用。一亿活跃设备是获胜策略的结果——为制造商提供易于集成和定制的软件解决方案,免费且社区支持,在一个由苹果主导的市场中。
开源,闭源
仔细观察项目,可以看出 Android 与其他开源项目略有不同:Android 是在闭门中由谷歌开发的。社区中很多人不同意将 Android 视为像 Linux 那样开放的。事实上,这两种方法完全不同。是的,它们都有开源许可证,但 Linux 是一个社区开发的项目,而 Android 则完全由谷歌开发。
每次技术讨论、每个决定、每个路线图步骤都是由谷歌做出的。当开发周期完成后,谷歌发布操作系统的最新版本,更新公共源代码仓库,任何人都可以下载操作系统的最新版本。
当然,有用于讨论和支持的电子邮件列表,也有几种方式可以贡献到项目中,但所有决定都由 Android 开发团队做出。
Android 开发模型
为了确保 Android 当前版本的稳定性,谷歌将源代码保持在代码行中。这种方法提供了一个适当的机制,将当前稳定版本(可在所有最新设备上使用)与当前正在开发的不稳定版本分开。正如你可能容易注意到的那样,谷歌在 Android 的命名惯例上与开源项目的通常命名法不同——使用code line而不是branch,因为单个code line可以基于多个 git branches。
以下图表展示了源代码历史随时间演变的过程,通过不同的分支和版本:

上一张图表显示了左侧的主要public branch,即上游分支。这个分支是主要的公共开发分支,其中不断发布所有关键的错误修复,并且在这里进行所有关于新设备和新技术的主要实验。每个开发人员或制造商都可以获取这一套源代码,并开始创建他们自己的 Android 设备。
在图表的右侧,我们可以看到谷歌的私有分支。这个分支包含 Android 的“下一个版本”。通常,所有开发工作都是由谷歌自己完成的,并得到一个硬件合作伙伴的支持,该合作伙伴提供参考设备。通常,这个设备是一个高端、顶级设备,谷歌将其指定为谷歌的下一个参考设备,也称为 Nexus。每个新的 Nexus 都是根据谷歌 Android 的发展路线图来选择的——每个技术硬件规范都支持或反对特定的软件开发,以创造设备和操作系统之间的完美共生。
当内部开发达到期望的稳定点时,新版本就会被发布,每个分支都会相应更新,然后开始新一轮的公开/私有开发周期。
源代码标签和构建
为了有效地引用特定的 Android 版本,自 Android 1.5 以来,每个公开版本都附带了一个花哨的代号、一个版本号和一个更面向开发者的 API 级别。
下表展示了代号/版本/API 级别之间的对应关系并不总是一对一的关系。大多数情况下,代号的寿命周期比版本号长:

Nexus
Nexus 系列包含了谷歌设计、生产和销售的 Android 设备,这得益于其硬件制造商合作伙伴的帮助。
Nexus 系列的一个独特之处在于它为设备配备的裸 Android 系统——无论是制造商还是电话运营商都没有进行任何定制。系统基于纯 Android 源代码,为用户提供最纯粹的 Android 体验。作为一个高级提示,设备的引导加载程序可以轻松且合法地解锁,以允许任何专家用户可能希望进行的修改。在安全性方面,Nexus 设备是首先接收安全修复和系统更新的设备——保持更新,保持安全!
Nexus 系列在数量和质量上持续增长,现在它包含了智能手机、平板电脑,甚至数字播放器。下表展示了目前所有可用的型号概览。
手机
这里是所有标准 Nexus 手机的列表:

平板电脑
这里是所有标准 Nexus 平板电脑的列表:

数字媒体播放器
这里是所有标准 Nexus 数字媒体播放器的列表:

表中的每一款设备都有其自己的 Android 构建版本,专门针对其硬件和用途进行定制。下表是谷歌为所有想要手动更换操作系统并希望获取官方构建版本的专家级 Android 用户提供的构建代号和版本示例:

每个构建都有一个build-code来标识,例如,LMY470。第一个字母是代码名的首字母,例如,Lollipop;第二个字母标识用于生成此构建的分支;接下来的两个字母标识发布日期,基于季度表示法——A 是 2009 年 Q1,F 是 2010 年 Q2,以此类推。季度字母后面的两个数字指定发布日。最后一个字母标识构建号。这种表示法并不非常精确。谷歌经常为多个构建重复使用相同的构建代码。我们需要将其视为发布日期的粗略估计。
源代码工具
Android 是一个庞大的项目,拥有惊人的源代码量。谷歌本身管理源代码,并将其存储在其服务器上,这些服务器对开发者和高级用户公开可访问。
考虑到项目的复杂性,我们只需要两个工具来检索源代码:
-
git
-
repo
让我们快速了解一下这些强大的工具,它们将伴随我们完成我们的旅程。
Git
Git 是目前世界上最受欢迎的源代码版本控制系统,公开可用。它是由林纳斯·托瓦兹(Linus Torvalds)创建的令人印象深刻的工具(是的,就是创建了 Android 内核中的 Linux 内核的那个林纳斯·托瓦兹)。
2005 年,托瓦兹(Torvalds)在努力寻找一种适当的方式来管理为其 Linux 内核工作的开发者的代码量和贡献。当时可用的任何工具都不够用,在几天内,他创建了其新的分布式版本控制系统的工作版本,该系统能够以速度和灵活性管理大型项目。
Git 为模块化系统提供了有效且易于实现的解决方案,谷歌充分利用了这一机会。Android 代码库的每个贡献都使用 git 功能提供——提交、分支和补丁。
系统尽可能地保持模块化,以便开发者和原始设备制造商提取和替换需要定制的组件。了解这一点后,很容易猜测 Android 包含多少个 git 仓库——数十个不同大小和结构的相互连接的仓库。
Repo
为了克服管理大量不同仓库的困难,谷歌创建了git-repo,这是一个用 Python 编写的工具,它在 git 上充当协调器,允许自动化工作流程的一些常见部分。
Repo 在许多场景中都很有用。最重要的是,所有涉及源代码的网络操作:检索、更新和将代码推送到远程服务器。Repo 是一个枢纽工具,我们将在下一节中了解到很多关于它的内容。
Gerrit
值得注意的是,Gerrit 是用于评估和批准提交给 AOSP 的每个贡献的代码审查工具。它提供了一个图形用户界面来监控代码库的演变,并代表所有贡献在被接受和合并到主代码库之前结束的中心点,或者在审查结果为 no, th 时被拒绝。
谢谢。
设置环境
Android 构建系统由 Ubuntu Linux 正式支持。谷歌保证系统设置、环境设置以及所有要求都可以在这个特定的 Linux 发行版上轻松重现。事实上,如今,几乎每个 Linux 发行版都可以通过少许努力来准备进行适当的 Android 构建。
为了尽可能接近指南,并且因为我们认为 Ubuntu 是最容易设置的系统,在这本书中,我们将使用 Ubuntu Linux 15.04 来执行所有构建过程。
如果您正在使用 OS X 或 Windows,并且您希望坚持使用这些操作系统,我们将向您展示即使使用虚拟机也能实现我们的目标。
空间充足
构建系统所需的硬盘空间非常大。Android 源代码本身可以达到 100 GB 的占用空间。当我们转向更高级的场景,例如使用缓存系统(如 ccache)来加速多个系统构建时,我们很容易达到 200 GB 的占用空间。确保您的机器上有这种类型的可用空间非常重要,因为在构建过程中填满硬盘可能会使系统处于不稳定状态。
另一个需要注意的是您的连接速度——考虑到准备构建所需的数据量,请确保您有一个快速的互联网连接,或者适当的耐心。
安装所需工具
即使官方支持的 Linux 发行版是 Ubuntu,以下步骤和命令在基于 Debian 的每个发行版上都是同样正确的,如果它实际上安装在了您的计算机上,或者作为虚拟机运行。
要获取源代码,我们需要从 Google git 仓库中检索它,我们需要安装 git。让我们打开终端并运行:
~$ sudo apt-get install git
Apt 将要求我们输入超级用户密码,并将负责在系统中安装 git。一旦我们有了 git,我们还需要它的信任伙伴工具——repo。Repo 不需要真正的安装。它是一个 Python 脚本,所以我们只需要下载它并将其放置在一个方便的文件夹中。
让我们在主文件夹中创建一个 bin 文件夹并将其添加到系统路径:
~$ mkdir ~/bin
~$ export PATH=~/bin:$PATH
现在我们有了文件夹,我们可以使用 curl 来下载 repo:
~$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
~$ chmod a+x ~/bin/repo
注意
如果您的系统中没有 curl,您可以使用 apt-get 安装它,如下命令所示:
~$ sudo apt-get install curl
以下是输出结果:

上一张截图显示了下载和我们的chmod命令,以确保repo能够正确执行。repo工具附带一系列帮助命令,可以通过这种方式访问:
$ repo help
此命令列出了所有可用的命令,如下一张截图所示:

对于进一步的帮助,每个命令,例如info,都有自己的帮助屏幕,可以通过这种方式访问:
$ repo help command
以下截图显示了info命令的帮助屏幕:

准备工作
如我们所知,谷歌是支持整个 Android 硬件基础设施的官方管理者——一切均由谷歌托管和维护。源代码仓库还提供了一个图形化导航源代码的 Web 界面。这个源代码浏览器可在android.googlesource.com/找到。
以下截图显示了页面外观的一个示例:

上一张截图显示的列表中的每一项都是一个git仓库。这可以让你充分了解谷歌的repo工具的重要性——手动管理这么多仓库将是纯粹的疯狂!使用repo,检索、下载和创建适当的文件夹结构只需几行代码。让我们动手吧!
首先,创建一个工作文件夹。打开终端并创建一个类似这样的文件夹:
~$ mkdir WORKING_DIRECTORY
~$ cd WORKING_DIRECTORY
一旦进入文件夹,运行:
~/WORKING_DIRECTORY$ repo init -u https://android.googlesource.com/platform/manifest
URL 指定了项目清单文件。清单文件指定了下载所需的哪些仓库以及必须预期的文件夹结构。
在这个阶段,repo将要求你提供全名和电子邮件。这种注册是为了使系统能够接收你的贡献。Gerrit 将使用这些信息通过通知和新闻与你沟通。显然,你的名字将与你在未来提交的每个贡献、修复或功能相关联。确保提供的电子邮件地址是一个有效的谷歌账户是一个明智的选择。
成功初始化将以以下方式结束:
repo has been initialized
恭喜!我们现在在当前文件夹中有一个初始化的 repo 和一个名为.repo的配置文件夹,其中包含例如下载的清单文件。
在你的终端中运行ls -la以找到.repo文件夹。
目前,我们的文件夹包含整个 Android 项目的master分支。它必须被视为一个开发分支,所以我们不能保证系统将在设备上工作或在我们的系统上构建。我们可以使用这个分支提交一些贡献,但这将是我们旅程的另一步。我们当前的目标是尝试构建一个可工作的系统,所以明智的做法是切换到一个或一个支持特定设备的标签。
要切换到特定的分支,我们将使用repo,它会负责配置所有涉及的仓库,带我们到一个稳定且可靠的环境:
:~/WORKING_DIRECTORY$ repo init -u https://android.googlesource.com/platform/manifest -b android-5.1.1_r3
之前显示的表格包含了我们可以切换到的所有可能的分支。
为了检索整个可用的分支列表,我们将使用一个技巧——克隆一个提供这些信息的特定仓库:manifest.git。让我们打开一个终端并克隆它:
$ git clone http://https://android.googlesource.com/platform/manifest.git manifest
进入我们刚刚创建的文件夹并获取列表:
$ cd manifest
$ git branch -a
以下截图显示了可用的分支列表的一部分:

下载代码
一切就绪:文件夹已准备就绪,repo 已正确配置,我们有大量的空闲硬盘空间和快速的互联网连接。让我们同步!
打开一个终端并运行:
$ repo sync
喝一杯美味的咖啡吧,因为这需要一些时间!repo 工具将下载清单文件中指定的每个仓库的每个文件,超过 50GB。
亲自动手编写代码
知道我们将适应我们的硬件,了解创建和提交贡献的工作流程非常重要。为了实现这个目标,我们将使用repo和git。
贡献工作流程基于五个步骤:
-
我们创建一个新的主题分支:
$ repo branch -
我们开发所有我们想要的编辑、修复和功能。我们将这些贡献添加到下一个提交中:
$ git add our_files -
我们将我们的暂存文件保存到 git 仓库中:
$ git commit -m "Add awesome new feature" -
我们将我们的新提交提交到代码审查服务器:
$ repo uploads
我们已经提交了代码,正在等待审查—— fingers crossed!
如果你不想下载整个代码库,并且你知道你将要定制哪个特定的模块,你可以只同步这个模块:
$ repo sync art
当我们的模块已经同步后,我们需要创建一个新的分支来保持我们的环境组织有序,具有清晰的架构和方便比较我们的编辑与原始内容的方式。要创建我们的新主题分支,我们需要进入模块文件夹并运行一个repo命令:
$ cd art/
$ repo start my_branch .
如果一切就绪,我们运行这个命令:
$ repo status .
这个命令会让人感到一丝安慰:
:$[…]/art$ repo status .
project art/ branch my_branch
在我们的工作中,我们可以创建我们需要的任何数量的分支,并且我们可以如下列出它们:
$ git branch
以下截图显示了当前模块中所有分支的列表:

当前分支是带有星号(asterisk)的分支。现在我们知道有哪些可用的分支,我们可以使用以下方式在分支之间切换:
$ git checkout branch_name
对于我们添加的每个修复或功能,一个新的 Git 提交将出现在我们的分支上:
$ git add art_new_feature
$ git commit -m "Add new awesome feature to ART"
一旦我们的所有编辑都完成,我们需要准备好将我们的贡献提交到 Gerrit 系统以及负责审查每个代码提案的开发者。
在能够提交我们的补丁之前,我们需要生成一个新的密码来访问源代码仓库。Google 提供了一个快速服务,可以在 URL android-review.googlesource.com/new-password 上生成密码。
选择你想要连接到 Android 源代码仓库的谷歌账户,你将到达 git cookie 配置页面。谷歌已经为你设置好了一切。只需将配置复制粘贴到你的一个终端中,你就可以开始了。
要提交我们的分支,我们更新模块以确保它与upstream保持一致,然后我们更新:
$ repo sync
$ repo upload
一旦我们请求上传,repo将要求确认,显示我们正在提交的所有贡献,如下面的截图所示:

确认后,repo将与仓库服务器建立安全连接,并将你的贡献存储在线。你现在是一名 Android 开发者,或者至少你正在朝着这个方向前进!
查看 AOSP 内部
在这一点上,我们已经有了 AOSP 的副本,因此我们可以开始查看项目由什么组成。
在深入探讨之前,我们必须警告你,当你从头开始生成新的构建镜像时,你不会找到大多数 Android 设备上都能找到的谷歌应用。这是因为谷歌应用不是在 Apache 2.0 许可证下授权的,因此它们没有包含在公共项目中。我们谈论的是如 Play 商店、Gmail、YouTube、地图以及所有其他官方谷歌应用。
这些应用仅提供给兼容设备,也就是说,那些通过我们在第一章中遇到的兼容性测试套件的设备。
能够分发一个预装所有谷歌应用的 Android 设备并非易事。在确认设备通过 CTS 兼容后,还需要通过直接联系谷歌来获得特定的谷歌移动服务(GMS)许可证。
显然,你可以在互联网上找到这些应用的二进制形式,并将其添加到你的构建中。这不是实现目标的官方方式,我们支持更清洁的发布方式来分发我们出色的设备,但值得一提的是,存在一些模糊的快捷方式。
回到我们的源代码,让我们看看我们的WORKING_DIRECTORY内部,看看我们可以在哪里找到 AOSP 由其组成的 Android 基本组件。
下一个截图显示了根目录中包含的所有文件夹的清晰概览:

ART 目录
最重要的文件夹之一无疑是art/。它包含由谷歌设计和开发的新的Runtime Environment的源代码。
ART 是 Android RunTime 的缩写,它是在 Android 4.4 Kitkat 中作为 Dalvik 虚拟机的替代品被引入的。它在 Android 5.0 Lollipop 中完全取代了 Dalvik。旧的 Dalvik VM 基于即时编译(JIT)技术,即它实时解释和编译应用程序源代码为机器代码。这种实现有其优点,但也有缺点,因为运行时编译无疑会影响系统性能。
ART 基于 AOT(即时编译)技术,在应用安装时编译所有应用代码,即在执行之前。这显然需要更多时间来安装应用,但考虑到最新 Android 设备的硬件性能,这个时间通常是不易察觉的。
bionic 目录
Bionic 是 Android 的 C 运行时。与大多数 Linux 发行版不同,Android 不使用 GNU C 库(glibc)。GNU C 库和bionic之间的主要区别是许可证——glibc在 GPL 许可证下分发,而 bionic 有 BSD 许可证。在一个如此商业化的世界中,更宽松的许可证至关重要。
其他非常重要的特性是轻量级和大小。Bionic 比 glibc 小得多,这使得它更适合用于嵌入式系统,如手机。此外,它还考虑到低性能处理器,因此性能更佳。
Bionic 源代码的大部分来自 OpenBSD 项目,但也有部分,如pthread和动态链接器,是从头开始编写的,以确保满足性能、轻量级和灵活性的要求。
构建目录
此目录包含整个 Android 构建系统。它包含了所有的makefile核心模板。
此外,它还包含envsetup.sh脚本,允许开发者无需与环境管理问题作斗争即可使用 Android 源代码。我们将在本书的后续部分详细解释它,但简而言之,运行此脚本会提供各种实用工具,使你能够对源代码执行各种操作,例如编译特定模块或对特定文件进行搜索,例如所有.java文件等。
外部目录
Android 使用的所有开源项目的包都可以在这个目录中找到。它包含了各种库以及非常重要的实用工具,如zlib、SQLite和webkit。
设备目录
在这里,你可以找到针对特定设备的所有配置和定义。以下截图展示了内容概览。正如你所见,它充满了以知名制造商命名的文件夹:

这里包含了所有官方 Google 设备的定义,即所有 Nexus 设备,但也有其他目录,例如:
-
common:此目录包含有关 GPS 的信息以及一个脚本,允许你提取特定设备的二进制部分,以便它们可以包含在镜像构建中。 -
generic:此目录包含名为“goldfish”的通用设备的配置,并用于构建模拟器镜像。 -
google: 此目录包含 配件开发套件(ADK)的代码。它还包含一个 DEMOKIT Android 应用,允许你控制 ADK 板。ADK 是硬件制造商和爱好者的参考实现,可以作为制作 Android 配件的起点。 -
sample: 此目录包含如何编写自己的 Android 共享库的完整示例,而不修改 Android 框架。它还展示了如何编写 JNI 代码并将其包含在库中,以及使用此类库的客户端应用程序。
The frameworks directory
这个文件夹非常重要,因为它包含 Android 框架的源代码。在这里,你可以找到 Android 的所有主要组件,如 Activity、Services 等。在这里,你还可以找到在 C/C++ 原生代码和 Java 代码之间使用的映射。
The out directory
尽管听起来可能很直观,但当构建完成时,编译的结果就在这个目录中。在这里,我们可以找到准备闪存到我们的设备或模拟器上的图像,例如在名为 out/product/generic/ 的子目录下为模拟器图像。在其子文件夹之一 out/host/linux-x86/ 中,你还可以找到从主机端需要的所有工具,例如 fastboot、zipalign、dexdump 等等。
The packages directory
如文件夹名称所示,在这里你可以找到所有标准的 Android 应用程序包,例如 Camera、Calculator、Dialer、Launcher、Settings 等。再次强调,这些不是像 YouTube 或 Maps 这样的 Google 应用,而是每个 Android 安装中常见的系统应用。
The system directory
system/ 目录包含 Android system core 的源代码,这是一个在 ART 虚拟机启动任何基于 Java 的服务之前负责设备初始化的最小 Linux 系统。
在这个文件夹中,你可以找到 init 进程的源代码以及相关的 init.rc 默认脚本,该脚本初始化并动态配置平台,以及 Toolbox(Android 的 BusyBox 替代品)等应用程序的源代码,以及我们将在接下来的章节中更详细解释的 adb 和 fastboot 工具的源代码。
The rest of the directory structure
这里是 AOSP 部分剩余的文件夹:
-
abi: 这是libgabi++的源文件。 -
bootable: 这包括启动和启动相关的代码。其中一些是遗留的,例如 Nexus 等设备上的引导加载程序实现的 fastboot 协议信息可能很有趣。 -
cts: 此目录包含兼容性测试套件的代码。 -
dalvik: 此目录包含 Dalvik 虚拟机的代码。 -
development: 此目录包含开发工具——SDK 和 NDK 的源代码。 -
docs:此目录包含与 Android 开源项目相关的文档。它包含一个名为source.android.com的子文件夹,其中包含生成静态 HTML 所需的所有文件。您可以在source.android.com/上查看构建结果。 -
注意:此目录是在线版本,通常与 AOSP 中的版本不一致。 -
hardware:此文件夹包含 HAL(硬件抽象层),这些库使得与设备硬件进行接口交互成为可能。 -
libcore:此目录包含 Apache Harmony。 -
ndk:此目录包含生成原生开发工具包的脚本,它允许在 Android 应用程序中使用用 C/C++编写的原生代码。 -
pdk:这是平台开发工具包,一组 Google 发送给各种 OEM 的实用工具,以便他们在重要系统更新之前更新自己的框架。 -
prebuilts:此目录包含预编译的文件,包括各种工具链版本。 -
sdk:这是软件开发工具包。 -
tools:这些是一些外部 IDE 工具。
摘要
在本章中,我们学习了代表 Android 基础的许多非常重要的事情。
我们从 Android 关于许可证和开发模型的理念开始,涉及到后续的不同版本的 Android。我们学习了如何安装和使用必要的工具来为 AOSP 项目做出贡献,以及如何下载 AOSP 源代码副本,选择正确的 TAG 以获取所需的 Android 版本。
在下一章中,我们将进行第一次构建,为模拟器生成一个镜像,但在此之前,我们将解释 Android 构建系统的工作原理以及我们需要安装哪些工具。
第三章. 设置和构建 – 模拟器方法
在上一章中,我们学习了如何检索源代码,并对文件夹结构有了概述。现在我们知道了分支模型的工作原理以及如何为项目做出贡献。这是一个重要的话题,因为 Android 是一个开源协议,但它的管理方式与其他流行的开源项目大相径庭。
在本章中,我们将设置整个环境,为构建我们的第一个 Android 系统并将其闪存到实际目标设备做准备。我们的努力将集中在创建一个完全工作的官方 Android 模拟器版本。
用户将学习如何使用adb和fastboot等工具,这两者都是谷歌提供的最重要的工具之一。
准备主机系统
要构建像 Android 这样的复杂系统,我们需要满足一些硬件和软件要求。首先,是主机系统。
支持 Android 构建环境的官方 Linux 发行版是Ubuntu Linux。谷歌定期为其设备发布新的 Android 构建版本,所有这些版本都是使用 Ubuntu 创建的。目前,谷歌正在使用 Ubuntu 14.04,尽管这不是最新可用的版本。
本书中的每个示例都将在一个常见的笔记本电脑上开发和执行,该笔记本电脑配备英特尔 i5 CPU 和 4GB 的 RAM,运行 Ubuntu Linux 15.05,这是最新可用的版本。使用不同的 Linux 版本可以证明,如果所有要求都得到满足,你可以使用任何 Linux 发行版甚至 Mac OS X 来构建 Android——如果你无法设置 Ubuntu,尝试不同的版本将具有挑战性,但作为一个学习经验,值得一试。
如果你是一名 Microsoft Windows 用户,遗憾的是,你将无法使用原生操作系统构建 Android。一个可能的解决方案是使用运行 Ubuntu 的虚拟机,例如。
硬件要求
深入了解硬件要求,你只需要一台最新的个人电脑。正如前文所预期的那样,我们将使用一款中端笔记本电脑作为我们的示例。它是一款联想 x220,配备英特尔 i5 CPU 和 4GB 的 RAM:这足以完成工作,而且价格合理,但构建时间不会太短。
为了加快构建时间,使用高端 PC 是可取的。更快的 CPU、更多核心和更多 RAM 将利用多线程和并行构建,并将显著减少构建时间,让你在旅程中能够进行更多实验。
环境设置的一个关键点是必要的硬盘空闲空间。所需的空间相当大——仅源代码就需要大约 100GB 的存储空间。整个构建过程将需要大约 150GB。如果我们试图尽可能快地构建,可能我们会启用构建系统缓存选项ccache。缓存系统将需要更多的空闲空间。
下表将为您提供关于最低和推荐硬件的粗略估计:

软件要求
在这本书中,我们将使用 Ubuntu Linux 15.04 构建系统。如果您无法获得这个版本,您可以使用较旧版本,例如谷歌的团队,一个完全不同的发行版,甚至是一个虚拟机。
当涉及到操作系统时,一个基本的要求是架构:如果我们计划构建 Android 2.3 或更高版本,我们需要一个 64 位系统。较旧的 Android 版本可以使用 32 位系统,但这不太可能。
安装 Java JDK
Oracle 的 Java 开发工具包 是构建 Android 的关键要求,是必不可少的。每个 Android 版本都需要特定的 JDK 版本。根据我们想要构建的版本,我们将安装:
-
Cupcake 到 Froyo 的 JDK 5
-
Gingerbread 到 KitKat 的 JDK 6
-
KitKat、Lollipop 和 Marshmallow 的 JDK 7
我们将构建 Android Lollipop 5.1.1,并且我们需要至少 JDK 7。在 Ubuntu 上安装 JDK 非常简单。让我们先打开一个终端并运行以下命令:
~$ sudo apt-get install openjdk-7-jdk
apt-get 命令将解决所有依赖,下载所有必需的软件包并安装它们。如果您是 鼠标和图标 用户,您可以使用 Ubuntu 软件中心 实现相同的目标,如下面的截图所示:

如果您是 Java 开发者或者出于特定原因计划构建不同的 Android 版本,比如冰淇淋三明治和棒棒糖,您可能会拥有不止一个版本的 Java 开发工具包。这种多用途场景需要更多的配置步骤。我们需要指定哪个 JDK 版本将作为系统中的默认版本。使用我们信任的终端,让我们运行以下命令:
~$ sudo update-alternative –config javac
以下截图显示了输出。如您所见,它列出了所有可用的 JDK 版本,并允许您选择一个设置为默认版本。在我们的场景中,我们使用 JDK 7,因为我们计划构建 Android 5 或更高版本。

安装系统依赖
即使 Java 在 Android 世界中是关键玩家,我们还需要一些 低级 工具来满足所有 Android 构建系统要求。其中一些是通用工具,它们可能已经安装,但我们的目标是从头开始设置整个系统:我们不能冒险遗漏一个依赖项。
使用您的终端,运行以下 apt-get 命令:
~$ sudo apt-get install bison g++-multilib git gperf libxml2-utils \
make python-networkx zlib1g-dev:i386 zip
如同往常,apt-get 将解决所有依赖并安装所有必需的软件包。以下截图显示了在您已经拥有所有必需软件包的情况下的命令输出,真幸运:

到目前为止,您的 Ubuntu 已经包含了构建世界上最受欢迎的移动操作系统所需的所有软件包和应用程序。
设置 Mac OS X 环境
构建 Android 最重要的要求之一是大小写敏感的文件系统。如果你计划使用 OS X 构建 Android,满足这一要求的最实际方法是创建一个包含大小写敏感文件系统的分区或磁盘镜像。
创建大小写敏感的磁盘镜像
OS X 提供了一个方便的图形工具来创建新的磁盘镜像。打开Spotlight并启动Disk Utility。上面的工具栏中有一个新建镜像按钮,它会带你到磁盘镜像创建屏幕,如下面的截图所示:

如前一个截图所示,关键设置是格式:它必须是Case-sensitive, Journaled。对于大小设置,越大越好,考虑到 Android 构建可能很快就会使用数百 GB。我们建议的最小大小至少为 50 GB。
如果你更倾向于使用命令行,可以使用 Terminal 和hdiutil创建这个磁盘镜像,如下面的命令所示:
~$ hdiutil create -type SPARSE -fs 'Case-sensitive Journaled HFS+' \-size 50g ~/android.dmg
如果磁盘镜像创建成功,我们现在磁盘上有一个.dmg或.dmg.sparsefile文件。一旦我们挂载它,我们就可以像正常硬盘一样使用它——下载 Android 源代码并继续构建过程。
以下两个命令将给你提供挂载和卸载磁盘镜像的能力:
~$ hdiutil attach ~/android.dmg -mountpoint /Volumes/android-disk;
~$ hdiutil detach /Volumes/android-disk;
注意
如果你用完了空间,以下命令将给你机会调整磁盘镜像的大小,并允许你继续你的 Android 构建工作:
~$ hdiutil resize -size <new-size-you-want>g ~/android.dmg.sparseimage
安装所需的软件
一旦我们有了安装磁盘镜像,就像 Linux 一样,我们需要安装所有那些我们需要正确构建系统的软件要求。
安装 Java 开发工具包非常简单:只需从www.oracle.com下载适当的.dmg文件并安装它。关于 Android 目标版本和所需 Java 版本的规定在这里同样适用。
此外,我们还需要:
-
Xcode:Xcode 的安装有很好的文档记录,可以在
developer.apple.com找到,因为 Xcode 是 iOS 开发的主要参与者。 -
MacPorts:这是一个开源项目,将帮助我们安装许多有用的工具。你可以按照
www.macports.org/install.php上的安装信息进行安装。
一旦我们有了这两个主要拼图,我们需要使用 MacPorts 安装make、git、bison和GPG软件包,在你的 Terminal 上使用以下命令:
~$ POSIXLY_CORRECT=1 sudo port install gmake bison libsdl git gnupg
最后但同样重要的是,我们需要增加可能的最大文件描述符数。OS X 自带一个非常小的值——普通用户不需要所有这些文件描述符,但鉴于 Android 构建过程中涉及数百个文件,我们将需要更多的数量。为了增加这个值,我们需要启动我们的 Terminal 并运行以下命令:
~$ ulimit –S –n 1024
现在,限制已提升至 1,024 个文件。我们可以通过将以下内容添加到~/.bash_profile文件中,在您的home文件夹中,使这个值保持持久。
Android 构建系统
在深入配置和构建您的第一个 Android 系统之前,我们将概述构建系统本身、涉及的工具以及谷歌对整个过程的独特方法。
关于创建新模块以及构建系统本身的官方文档非常少。您在这个旅程结束时的大部分知识将来自您自己的动手经验以及我们放入这些页面中的经验。
概述
就像许多其他项目一样,开源或闭源,Android 使用强大的工具make来构建整个系统,但与其他所有项目相比,Android 使用它的方式完全不同。
使用make的常见方法将是使用 Makefile 的层次结构:一个单一的根 Makefile 检索并运行包含在项目某些子文件夹中的其他所有 Makefile。通常,每个子文件夹代表主项目的一个子模块,它可以独立构建或依赖于其他模块。与其他项目不同,Android 没有menuconfig或其他任何图形配置实用程序来定制构建系统、启用或禁用模块。所有类型的构建配置都是通过环境变量完成的,我们将在下一节中展示。
此外,整个模块构建是非传统的。以 Linux 内核为例,通常,当构建一个模块时,在源代码相同的文件夹中,我们会得到编译文件。模块一个接一个,构建系统编译一切,最后,它检索所需的文件,将它们链接在一起,并生成最终输出。Android 以不同的方式工作。正如您在接近构建完成时会注意到的那样,Android 试图尽可能保持每个模块文件夹的清洁——每个编译文件最终都会进入/out文件夹,这样就可以更容易地清理一切,只需删除这个文件夹,一切就可以在一瞬间变得井然有序。
如您所猜的那样,构建系统完全是谷歌定制的。所有东西都是从零开始设计和开发的,使用现有工具,但以不寻常的方式处理问题。Android 开发者创建了一个巨大的 Makefile,其中包含构建每个模块和组装最终系统映像所需的所有信息。
整个构建系统都包含在build/文件夹中。这个文件夹包含:
-
工具 shell 脚本
-
工具 Python 脚本
-
一组包含创建所有系统模块所需信息的
.mk文件
每个模块都有自己的文件夹。这个文件夹包含构建模块最重要的文件Android.mk。这个文件包含了构建模块源代码和生成二进制文件所需的所有信息。
模块“Android.mk”文件是构建过程的第一步——构建系统会扫描每个文件夹以查找这些文件,并将它们包含到它将用于后续步骤的单个巨大的 Makefile 中。
源代码根目录包含一个包含以下内容的 Makefile:
### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###
文件看起来很空,但它包含了所有最重要的.mk文件——“main.mk”。这个文件位于“build/core”,包含了一系列检查和所有必要的操作,以检索构建所有模块所需的全部“Android.mk”文件。
注意
没有特殊的配置,Android 构建系统只创建 Android 系统镜像。要生成 CTS、NDK 和 SDK,我们需要更多的设置工作,正如我们稍后将会看到的。
引导
整个构建系统都是通过一个单一的 shell 脚本——“build/envsetup.sh”来启动的。正如您在下面的屏幕截图中所看到的,这个脚本负责准备构建环境。它设置了一些配置并提供了一些有用的工具,使我们的工作变得更加容易:它是 Android 构建系统的瑞士军刀。
启动您的终端并按照以下方式运行脚本:
~$ . build/envsetup.sh
这里是输出结果:

之前的屏幕截图显示了“envsetup.sh”的输出,它使系统完全运行并准备好构建。要获取我们现在可用的所有命令列表,请在您的终端中运行:
:~$ hmm

之前的屏幕截图显示了“hmm”命令的输出。我们稍后会看到很多这样的命令,但作为一个美味的期待:
-
“
lunch”:这个命令可以帮助您通过一个命令配置针对特定目标所需的所有内容 -
“
mm”:这个命令允许您仅编译当前文件夹中包含的模块
设置
合适的配置环境是构建系统最重要的因素之一。每个构建系统都提供了一种明确的方式来指定,例如,我们要构建哪个平台上的哪个模块。以 Linux 内核为例,它提供了一个方便的图形菜单来执行所有必要的配置:
$ make menuconfig
Menuconfig 允许您启用或禁用要构建的模块,选择所需的平台,以及许多其他可能的配置。每个单独的配置位都保存在一个.config文件中,可以轻松读取或编辑,并用于构建过程。
正如我们所预期的,Android 基于完全不同的东西。没有图形界面来执行配置。唯一的交互式或自动配置系统是“envsetup.sh”,我们之前已经学过了。那么为什么 Android 没有提供任何酷炫的工具来配置构建系统呢?简单地说,因为它不需要!我们不应该禁用我们不希望构建的所有模块,所以 Android 并没有提供一种简单的方法。
假设我们正在为刚刚创建的新设备构建 Android,而这个设备没有内置摄像头。我们可能想要移除管理摄像头的系统部分。没有官方的方法来做这件事。如果我们想这么做,我们需要亲自动手,随着时间的推移和页面的积累,我们将能够做到这一点。
我们可以安全地说,整个 Android 构建系统配置可以简化为设置几个环境变量。构建系统将使用这些变量来确定我们针对哪个设备,或者它应该使用哪个工具链。
最重要的变量是:
-
TARGET_PRODUCT
-
TARGET_BUILD_VARIANT
-
TARGET_BUILD_TYPE
-
TARGET_TOOLS_PREFIX
-
TARGET_PREBUILT_KERNEL
-
OUT_DIR
在接下来的章节中,我们将学习所有可以操纵以完善构建的变量。
TARGET_PRODUCT 变量
这个变量包含指定我们为哪个设备准备系统的信息。我们目前的目标是官方模拟器,所以我们将设置这个变量为aosp_arm。如果我们想为 Google 的 Nexus 6 构建系统,我们将设置变量为aosp_shamu,或者为 Google 的 Nexus 5 设置变量为aosp_hummerhead。
为了快速访问所有针对所有支持设备的特定值,我们提供了一个方便的表格,如下所示:

如您所想,每个设备都支持特定版本的系统。例如,使用我们当前下载的源代码库,标记 android-5.1.1:
-
aosp_arm
-
aosp_arm64
-
aosp_mips
-
aosp_mips64
-
aosp_x86
-
aosp_x86_64
-
aosp_manta
-
aosp_flounder
-
mini_emulator_x86_64
-
mini_emulator_mips
-
mini_emulator_x86
-
mini_emulator_arm64
-
m_e_arm
-
aosp_mako
-
aosp_hammerhead
-
aosp_shamu
-
full_fugu
-
aosp_fugu
-
aosp_deb
-
aosp_tilapia
-
aosp_flo
-
aosp_grouper
一旦我们确定了目标设备,打开终端并运行:
$ export TARGET_PRODUCT=aosp_arm
TARGET_BUILD_VARIANT 变量
每个Android.mk文件都引用这个变量来启用或禁用其代码库的编译部分。这个变量有三个可能的值,它指定了构建变体。我们可以将其设置为:
-
eng: 在这里,每个标记为用户、调试和eng的模块都被启用 -
userdebug: 在这里,每个标记为用户和调试的模块都被启用 -
user: 在这里,每个标记为用户的模块都被启用
我们可以使用变量如下:
$ export TARGET_BUILD_VARIANT=eng
TARGET_BUILD_TYPE 变量
这个变量指定了我们将为每个模块执行哪种类型的构建。如果我们打算创建一个开发系统,我们可能需要更多的日志信息,例如。对于这种情况,我们将设置这个变量为debug,构建并测试我们的系统。在这个阶段之后,我们将使用这个变量设置为release来重建系统,以禁用冗长的日志记录和所有开发特性。
TARGET_TOOLS_PREFIX 变量
这个变量指定了在构建过程中使用自定义工具链的路径。通常,它保持为空,但随着经验的积累,你应该尝试不同的工具链,这些工具链在互联网上免费提供。最著名且经过优化的自定义工具链之一是由Linaro团队开发和分发的。
OUT_DIR变量
如果出于某些特定原因我们想要覆盖out/文件夹的默认路径,我们可以使用这个变量来指定一个自定义路径。这个变量在所有有多个硬盘或网络共享的场景中都非常有用。例如,我们可以在快速的 SSD 磁盘上运行构建过程,并将输出存储在标准的旧式磁盘上,甚至是一个网络磁盘,以便与我们的队友共享。
TARGET_PREBUILT_KERNEL变量
这是一个相当高级的变量。它允许我们向系统提供一个与默认不同的内核。每个目标设备都附带一个预编译的默认内核,因为 Android 构建系统不会构建它——它已经存在了。
注入自定义内核是一个非常有趣的话题,它打开了众多有趣的场景。在第五章“自定义内核和引导序列”中,我们将构建一个自定义内核并将其注入到我们的 Android 系统中,以创建一个完全定制的 Android 体验:这个变量将是拼图中最重要的部分之一。
buildspec.mk文件
如果我们想要持久化这些变量,我们可以将它们添加到buildspec.mk文件中。每次我们运行make时,系统都会检查这个文件,评估所有变量,然后相应地继续。buildspec.mk文件在build/文件夹中以buildspec.mk.default为名的方便模板版本提供。这个模板文件包含所有可用的变量。每个变量都有注释,默认禁用,并附带关于其用途和如何使用的简要说明。
我们可以将这个文件视为 Linux 内核.config文件的等价物,即使我们可能有更少的配置选项。
lunch命令
在前面的几个部分中,我们已经对lunch有了一个初步的了解。如果我们不想手动设置所有这些环境变量,或者我们不想使用
使用buildspec.mk,我们可以使用lunch。在我们执行了envsetup.sh之后,我们可以在系统中找到它。
让我们来看看这个命令。打开一个终端并进入你的WORKING_DIRECTORY。确保你已经启动了envsetup.sh,然后运行:
$ lunch

前面的截图显示了命令的输出,正如你很容易看到的,它帮助我们选择我们想要的变量的确切组合。每个特定的 Android 版本都有自己的lunch命令,每个lunch命令版本都有自己的输出。前面的截图显示了 android-5.1.1 标签的输出。
一旦你选择了所需的配置,lunch将显示它将要设置的每个变量的摘要,然后返回终端,如下面的截图所示:

我们现在可以运行make命令并构建我们的第一个 Android 版本了!
构建系统
你已经下载了源代码,使用envsetup.sh初始化了整个环境,并使用lunch配置了每个系统变量。你现在可以构建系统了。打开一个终端并运行:
:~$ make –j8
构建系统将启动,寻找所有那些模块和Android.mk文件以包含在构建过程中并执行编译。
如果你想要享受更详细的编译输出,你可以运行:
:~$ make –j8 showcommands
使用这个额外参数,构建系统将打印所有GCC编译日志和所有javac编译日志,以便在构建过程中尽可能提供更多信息。
更多关于 make 的信息
make命令提供了一些在特定场景下很有用的有趣选项。
构建模块
例如,如果你想只构建一个单独的模块,你可以运行:
~$ make art
在这个例子中,我们只构建art。模块名称包含在module文件夹中的Android.mk文件中。只需滚动文件,你就会找到一个表示要使用make的精确模块名称的变量LOCAL_MODULE。
我们也可以使用mm命令检索模块名称。在终端中,只需到达模块文件夹并运行:
$ mm
清理模块
如果模块构建完成后我们还不满意,我们可以清理所有编译文件并从头开始。打开一个终端,到达模块文件夹,并运行:
~$ make clean-<module>
清理所有内容
如果你想要清理整个项目并为从头开始的新构建准备系统,打开一个终端,到达WORKING_DIRECTORY,并运行:
~$ make clean
此命令从我们在OUT_DIR变量中指定的文件夹中删除所有编译文件。
列出模块
$ make modules
此命令显示 AOSP 架构中每个模块的列表。可用的模块数量庞大:我们可能需要等待几秒钟才能看到此命令的任何输出。
重新创建镜像
此命令根据当前源代码库的状态,使用增量构建方法重新创建系统镜像,如下所示:
$ make snod
这是一个开发过程中的关键命令。想想只开发一个模块。当你达到一个开发里程碑时,你可以使用以下命令构建模块:
$ make module_name
如果一切正常,你可能想将你的全新模块注入到 Android 系统镜像中。你可以通过以下方式实现:
:$ make module_name snod
构建工具
以下命令将创建并提供给我们两个对 Android 专家来说最重要的工具——adb和fastboot:
:$ make tools
我们将在下一页有足够的时间了解它们。
超越系统镜像
我们目前正在构建一个准备闪存到设备上的系统镜像。不幸的是,这个程序排除了我们想要构建的一些有用工具:NDK、SDK 和 CTS。
Android SDK
Google 通过 Android 开发者网站提供官方的 Android SDK。它已经为每个平台编译好,可供下载。在更高级的场景中,你可能需要扩展 SDK 并将其重新分发为你的版本。构建自定义 SDK 是一个三步命令的工作,这些命令我们在前面的章节中已经学过:
~$ . build/envsetup.sh
~$ lunch sdk-eng
~$ make
此过程的输出将是一个全新的自定义 Android SDK,位于out/host/linux-x86/sdk/。
Android NDK
Android NDK 是基于 C/C++的本地等效物,基于 Java 的 Android SDK。要构建 NDK,打开终端,到达WORKING_DIRECTORY,然后运行:
~$ cd ndk/build/tools
~$ export ANDROID_NDK_ROOT=path/to/WORKING_DIRECTORY/ndk
~$ ./make-release
系统将提醒你关于过程可能的长持续时间。只需接受消息,同时准备一些咖啡。
Android CTS
CTS 是一个知名的工具。我们在前面的章节中学到了关于它的所有内容。要构建我们自己的版本,我们只需要一个命令:
~$ make cts
在 AOSP 模块内部
AOSP 项目极其庞大。源代码库中包含的模块数量巨大。Android 5 Lollipop 大约包含 4,000 个不同的模块。它们从用 C/C++编写的本地模块,到提供系统组件:守护进程、库和 Java 模块,以提供从 APK 到 JAR 文件所需的一切。
每个模块都包含一个 Android.mk 文件。这个文件包含构建模块所需的所有信息。Android 构建系统不使用递归 make 方法,而是将每个 Android.mk 文件合并为一个单一的巨大 Makefile 来构建系统:每个 Android.mk 文件都是拼图的一部分。
除了 Android.mk,模块文件夹还包含CleanSpeck.mk文件。这个文件帮助我们正确清理在执行模块清理命令时编译的每个文件。
深入 Android.mk
获取知识的最快途径是亲自动手。我们将分析 Android 源代码中的一个真实的 Android.mk 文件,以了解其结构和目的。在上一章中,我们了解到external/文件夹包含许多第三方工具,这些工具丰富了 Android 系统。其中之一是netcat。让我们看看它的 Android.mk 文件:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=\
netcat.c \
atomicio.c
LOCAL_CFLAGS:=-O2 -g
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
LOCAL_MODULE:=nc
# gold in binutils 2.22 will warn about the usage of mktemp
LOCAL_LDFLAGS += -Wl,--no-fatal-warnings
include $(BUILD_EXECUTABLE)
几行需要更详细研究的神秘代码:
LOCAL_PATH:= $(call my-dir)
这一行指定了LOCAL_PATH变量并将其设置为当前模块路径。正如你所猜到的,$(call my-dir)函数返回当前模块路径。
这个函数是系统提供的一组有用的函数之一,用于在开发新模块期间使用。整个列表包含在build/core/definitions.mk中。每个函数显然都带有代码,以及一个微小但有效的目的描述,如下一张截图所示:

include $(CLEAR_VARS)
这一行解决了由于 Android 构建系统的特性而产生的一个大问题——将所有的 Android.mk 文件合并成一个单独的 Makefile,会创建一个危险的场景,其中模块 A 的 LOCAL_ 变量可能会被模块 B 错误地使用。$(CLEAR_VARS) 函数重置了之前设置的变量,并允许当前模块安全地访问其本地变量,使用以下代码:
LOCAL_SRC_FILES:=\
netcat.c \
atomicio.c
以下一行指定了当前模块中包含的源文件:
LOCAL_CFLAGS:=-O2 -g
以下一行指定了我们将要传递给编译器的参数:
LOCAL_MODULE_TAGS := eng
这一行指定了该模块属于哪个变体。这与我们在前几节中了解到的环境变量 TARGET_BUILD_VARIANT 密切相关。在这里指定 eng 将使此模块在构建系统的 eng 构建变体时可用:
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
这一行指定了在构建过程成功时编译的可执行文件安装的位置。在这种情况下,最终文件将被放置在系统镜像的 xbin/ 文件夹中。这个变量是可选的。系统将根据全局配置中已指定的默认值进行操作。我们可以使用这个变量来指定一个 不同的 目标文件夹:
LOCAL_MODULE:=nc
这一行在几节前就已经被预期到了。它指定了模块名称。它必须是唯一的,它也将是可执行文件的最终名称。在这种情况下,我们的 netcat 工具将成为 nc 可执行文件,正如在 *nix 系统中常见的那样:
LOCAL_LDFLAGS += -Wl,--no-fatal-warnings
对于编译器来说,链接器也将有一组特定的参数。这一行指定了链接器将根据以下选项进行操作:
include $(BUILD_EXECUTABLE)
这一行指定了我们正在尝试构建的模块类型。我们当前的模块是一个可执行实用程序,因此我们将指定 $(BUILD_EXECUTABLE),系统将正确地从所有模块源代码生成可执行文件。
Android.mk 变量生态系统
在上一节中,我们分析了一个实际的 Android.mk。这让我们在创建自己的 Android 模块时获得了一些信心。在本节中,我们将继续我们的旅程,概述我们可以在 Android.mk 中使用的所有变量:
LOCAL_ 变量是所有必要的变量,用于实现适当的模块配置和编译。这类变量会被 $(CLEAN_VARS) 取消,并且在所有 Android.mk 文件中是最常见的类型。
INTERNAL_、HOST_ 和 TARGET_ 变量不应用于自定义目的,因为它们通常由构建系统本身使用。
BUILD_ 变量指定了构建类型,正如我们在之前的示例中看到的,我们使用了 BUILD_EXECUTABLE。
从技术上来说,我们可以使用任何类型的变量,但这是一场危险的游戏。很难预测构建系统将如何操纵我们的 Android.mk 文件来创建其 Makefile:顺序可能不被尊重,名称可能被覆盖,作用域可能被无效化。为了安全地玩耍并依赖于构建系统架构,让我们专注于只使用 LOCAL_ 变量来完成我们的任务。
关于这些变量没有官方文档。即将到来的列表是辛勤工作、冒险、猜测以及从整个构建系统中搜集信息的结果。
LOCAL_ 变量
当涉及到 LOCAL_ 变量时,我们可以根据此列表自定义我们的模块:
-
LOCAL_PATH: 这指定了模块的路径。通常,该值是通过使用$(call my-dir)函数检索的。 -
LOCAL_MODULE: 这指定了模块的名称,如果我们在处理可执行模块,则指定可执行文件的名称。 -
LOCAL_MODULE_CLASS: 这指定了模块所属的类别。根据其类别,模块构建过程的每个结果都将放置在正确的文件夹中。可能的类别的例子有EXECUTABLE、ETC、SHARED_LIBRARY、STATIC_LIBRARY和APPS。 -
LOCAL_SRC_FILES: 这指定了模块中包含的所有源文件列表,由空格分隔。 -
LOCAL_PACKAGE_NAME: 这指定了应用程序的名称,例如:联系人、电话、计算器等等。 -
LOCAL_SHARED_LIBRARIES: 这指定了可能需要的共享库。 -
LOCAL_MODULE_TAGS: 这指定了一个标签,例如eng,系统将包括此模块在所有以eng类型作为TARGET_BUILD_VARIANT的目标构建中。 -
LOCAL_MODULE_PATH: 这指定了一个自定义安装路径,以覆盖BUILD_模板中指定的路径。 -
LOCAL_CC: 这指定了要使用的不同 C 编译器。 -
LOCAL_CXX: 这指定了要使用的不同 C++ 编译器。 -
LOCAL_CFLAGS: 这有助于向 C 编译器命令行添加模式标志。 -
LOCAL_CPPFLAGS: 这有助于向 C++ 编译器命令行添加模式标志。 -
LOCAL_CPP_EXTENSION: 这指定了 C++ 文件的自定义扩展,如果由于某种原因实际扩展不是.cpp。 -
LOCAL_C_INCLUDE: 这指定了构建模块所需的自定义 C 头文件路径。 -
LOCAL_LDFLAGS: 这有助于向链接器命令行添加模式标志。 -
LOCAL_PREBUILT_EXECUTABLES: 在创建BUILD_PREBUILD类型的模块期间,此变量将包含将成为最终系统镜像一部分的所有二进制可执行文件。我们将在下一章中了解更多关于这一点。 -
LOCAL_PREBUILT_LIBS: 在创建BUILD_PREBUILD类型的模块期间,此变量将包含将成为最终系统镜像一部分的所有库。 -
LOCAL_PREBUILT_PACKAGE:在创建BUILD_PREBUILD类型的模块期间,此变量将包含将成为最终系统映像一部分的所有预构建 APK。
BUILD_ 变量
以下列表包含在自定义模块开发过程中可用的最常见 BUILD_ 变量:
-
BUILD_EXECUTABLE:当需要使用原生 C/C++ 代码进行构建时,我们可以在配置中添加此行:include $(BUILD_EXECUTABLE) -
BUILD_PREBUILT:这允许我们将二进制组件添加到我们的最终映像中。 -
BUILD_MULTI_PREBUILT:这允许我们创建将具有相同类别二进制组件的最终映像注入模块。它通常与LOCAL_MODULE_CLASS一起使用,以指定类别和放置二进制文件的位置。 -
BUILD_PACKAGE:这允许我们创建生成 APK 文件的模块。 -
BUILD_SHARED_LIBRARY:这允许我们创建生成共享库文件的模块。 -
BUILD_STATIC_LIBRARY:这允许我们创建生成静态库文件的模块。 -
BUILD_JAVA_LIBRARY:这允许我们创建生成 Java 库文件的模块。
模块模板示例
在本节中,我们将分析现实世界的模块模板代码片段,以便清楚地了解一个完全运行的模块模板看起来像什么。
原生可执行文件模板
如果你打算对一个通用的原生单文件可执行应用程序进行工作,例如,your_executable.c,你可以使用以下代码片段来构建它:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= your_executable.c
LOCAL_MODULE:= your_executable
include $(BUILD_EXECUTABLE)
共享库模板
如果你正在处理所谓的共享库,这个代码片段会很有用:你的库将由一组文件组成,即 foo.c 和 bar.c,并将相应地构建:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= foo.c bar.c
LOCAL_MODULE:= libmysharedlib
LOCAL_PRELINK_MODULE := false # Prevent from prelink error
include $(BUILD_SHARED_LIBRARY)
应用程序模板
如果你打算对一个完整的应用程序进行工作,你可以使用以下代码片段:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS:= eng
LOCAL_SRC_FILES:= $(call all-java-files-under src)
LOCAL_PACKAGE_NAME:= MyApplication
include $(BUILD_PACKAGE)
此代码片段将构建指定路径和包中的每个 .java 文件,并将所有内容打包为 MyApplication。
创建自定义设备
在我们的旅程中,你学习了如何检索源代码以及如何设置构建系统。在本节中,你将学习如何创建一个新的目标设备并将其添加到构建系统中。我们现在要创建的设备具有特定的硬件功能。这是一个概念验证设备,其唯一目的是向你展示你可以多么容易和快速地创建一个全新的设备,然后对其进行定制。
每个设备定义都包含在 device/ 文件夹中。第一级文件夹包含所有制造商的文件夹。每个制造商文件夹包含其自己的设备。让我们创建我们自己的制造商和设备文件夹:我们的品牌是 Irarref,我们的型号是 F488。打开终端,到达 WORKING_DIRECTORY 文件夹,并运行:
~$ mkdir –p device/irarref/f488
一旦我们建立了文件夹结构,我们需要创建所有那些允许构建系统检测我们的设备并将其作为构建系统的目标使其可用的文件。我们将创建以下文件:
-
Android.mk: 以通用方式描述如何编译源文件。本质上,它代表了一个将被构建系统在适当时间合并的全球 Makefile 的片段。 -
AndroidProducts.mk: 这个文件包含一个PRODUCS_MAKEFILEs变量,列出了所有可用的产品。在我们的场景中,我们只有一个设备,它由这些文件表示。 -
full_f488.mk: 这个文件指定了关于设备的相关信息。 -
BoardConfig.mk: 这个文件指定了关于设备板的相关信息。 -
vendorsetup.sh: 这个脚本使设备对envsetup.sh和lunch可用。
深入设备配置
如我们所知,我们的第一个设备相当简单,但非常有教育意义。让我们看看我们的设备规范是如何分布在我们所有的配置文件中的:
-
Android.mk:LOCAL_PATH:= $(call my-dir) Include $(CLEAN_VARS) Ifneq ($(filter f488, $(TARGET_DEVICE)),) Include $(call all-makefile-unter, $(LOCAL_PATH)) Endif -
AndroidProducts.mk:PRODUCT_MAKEFILES:= $(LOCAL_DIR)/full_f488.mk -
full_f488.mk:$(call inherit-product, $(SRC_TARGET_DIR)/product/aosp_base.mk # DEVICE_PACKAGE_OVERLAY:= PRODUCT_PACKAGE+= PRODUCT_COPY_FILES+= PRODUCT_NAME:= full_f488 PRODUCT_DEVICE:= f488 PRODUCT_MODEL:= Android for Irarref F488 -
BoardConfig.mk:TARGET_NO_BOOTLOADER := true TARGET_NO_KERNEL := true TARGET_CPU_ABI := armeabi HAVE_HTC_AUDIO_DRIVER := true BOARD_USES_GENERIC_AUDIO := true # no hardware camera USE_CAMERA_STUB := true # CPU TARGET_ARCH_VARIANT := armv7-a-neon ARCH_ARM_HAVE_TLS_REGISTER := true -
vendorsetup.sh:add_lunch_combo full_f488-eng
我们的Android.mk相当标准,完全基于我们在前面的章节中学到的内容。AndroidProducts.mk正如预期的那样,只包括了full_f488.mk。
full_f488.mk文件包含了一些有趣的行。首先,它包括了aosp_base.mk,这是一个由系统提供的配置文件,对于许多真实设备来说是通用的。
接下来,我们发现了一些有趣的变量:
-
DEVICE_PACKAGE_OVERLAY:=: 这个变量允许我们创建一个自定义覆盖,例如,自定义 AOSP 系统中特定模块的一些设置。例如,如果你检查shamu设备的等效文件中的这个变量,你可以注意到他们正在使用它来自定义启动器应用程序中的几个设置。 -
PRODUCT_PACKAGE+=: 这个变量允许我们在编译过程中添加包。 -
PRODUCT_COPY_FILES+=: 这个变量执行文件复制操作。语法相当直接:source_file:dest_file -
PRODUCT_NAME:= full_f488: 这个变量指定了产品名称。这是lunch将打印为TARGET_PRODUCT的确切相同值。 -
PRODUCT_DEVICE:= f488: 这个变量指定了设备名称。 -
PRODUCT_MODEL:=: Irarref F488 的 Android:这个变量指定了设备型号标签,我们将在 Android 系统中的设置 | 关于手机 | 型号下找到。
在所有这些文件就绪后,你现在可以重新启动envsetup.sh,我们全新的原型设备将出现在可用设备列表中。
从零到屏幕锁定
到目前为止,我们已经收集了关于架构、如何配置构建系统以及我们的原型设备的大量信息。是时候为真实设备创建我们的第一个镜像并使用它了!我们希望避免所有可能的硬件相关的问题,因此我们将目标定位在最简单的非硬件 Android 设备上:Android 模拟器。
我们将构建最新可用的 Android Lollipop 源代码。正如我们所学的,我们将下载它,配置它以针对模拟器,构建它,并在设备上尝试它。
设置
让我们设置我们的WORKING_DIRECTORY并下载我们宝贵的源代码。打开终端并运行以下命令:
:$ mkdir WORKING_DIRECTORY
:$ cd WORKING_DIRECTORY
:$ repo init –u https://android.googlesource.com/platform/manifest -b \
android-5.1.1_r9
:$ repo sync
下载完成后,我们可以配置环境。让我们运行:
:$ build/envsetup.sh
这将创建我们在工作中需要的所有实用工具。现在我们有lunch,例如,运行它我们可以继续配置环境:
:$ lunch
让我们选择一个目标设备:
aosp_arm-eng
lunch命令将设置一切并显示一个配置报告,如下一个截图所示:

构建
一切就绪。我们只需要一个命令来启动构建过程:
:$ make –j8
构建过程完成后,前往out/target/product/generic/。这个文件夹将包含我们构建的镜像。以下截图显示了构建过程的结果:一个充满.img文件的文件夹,准备闪存到设备中:

运行
要启动模拟器,Android 提供了emulator命令。这个命令将在编译结束时可用。使用我们out/文件夹中的.img文件,我们可以这样运行它:
$ emulator -system out/target/product/generic/system.img -ramdisk out/target/product/generic/ramdisk.img -data out/target/product/generic/userdata-qemu.img
几分钟后,模拟器窗口将弹出,你将看到如下截图所示的内容:

你可以使用鼠标和键盘使用模拟器,执行你会在真实设备上做的相同操作。Android 模拟器是一个强大的工具,其可能性几乎是无穷无尽的。如果你想深入了解这个话题,Android 开发者网站提供了一个专门的页面:developer.android.com/tools/help/emulator.html。
摘要
本章是一次精彩的运行!你为构建你的第一个 Android 系统准备好了系统。你学习了如何配置和定制构建系统。你学习了创建自定义模块并将其包含到系统镜像中的基本技能。你从头开始创建了一个系统镜像,并在 Android 模拟器上进行了测试。
在下一章中,我们将提高标准。我们将把我们的努力转移到真实的硬件设备上。我们将使用智能手机 Nexus 5 和开发板 UDOOU。我们将操作引导加载程序和恢复分区,以完全控制系统。
第四章. 移动到真实硬件
在上一章中,你学习了如何设置必要的环境配置以及如何构建你的第一个纯系统,针对模拟器。在本章中,我们将快速概述每个专家 Android 用户的基本工具,并将完成我们的第一个针对真实设备的系统——配置、构建、烧录和测试。
调试工具
调试工具是每个开发者都无法离开的工具之一。在嵌入式系统(如 Android 系统)中,它们甚至更为重要。Android 提供了大量的调试工具,以简化复杂或无聊的任务。其中两个最重要的工具无疑是 adb 和 fastboot。
介绍 ADB
ADB 代表 Android Debug Bridge,它是由两个关键部分组成的工具包:
-
在设备上运行的 Adb 服务器
-
在 PC 上运行的 Adb 客户端
通常,adb 被认为是命令行工具,但如果你更喜欢以图形化的方式使用它,你可以在网上找到一些图形前端。Android Studio,谷歌为 Android 开发提供的官方 IDE,使用 adb 与每个设备通信并提供如 Android 设备监控器 这样的酷炫工具。使用图形界面,我们可以分析来自设备的日志,甚至为了调试目的进行截图。
以下截图显示了如何使用 Android 设备监控器 从运行在设备上的设备和应用中检索大量信息:

在我们的旅程中,我们将主要在命令行中使用 adb,因为我们的工作具有嵌入式特性。正如我们所知,模拟器表现得像硬件设备,因此我们可以轻松使用 adb 与之通信。让我们看看一些与运行中的模拟器交互的有用命令。
首先,我们需要一个所有可用命令的便捷列表。这可以通过以下命令轻松实现:
$ adb --help
现在,我们需要检测连接的设备。在我们的可靠终端上运行以下命令:
$ adb devices

之前的命令将扫描所有连接的设备并将它们列出。之前的截图显示我们的模拟器已连接并准备好通信。在多设备场景中,我们可能会在正确检测设备时遇到一些问题。Adb 给我们提供了另一个选项 -l:

使用 -l 选项,adb 将显示有关设备的更多详细信息,这有助于我们正确识别它们,如之前的截图所示。
一旦我们检测到设备,我们可以通过几种方式与之通信。最常见的方式之一是将它连接到设备的内部 shell。每个 Android 设备都自带一个系统 shell:它是嵌入式或远程系统的常用工具。要连接到内部 shell,我们只需运行以下命令:
$ adb shell
如果我们有多台设备,我们需要指定我们想要连接到哪台设备,如下所示:
$ adb -s ZX1B226467 shell
一旦我们连接到内部 shell,我们可以将系统当作一个普通的 *nix 系统来处理。我们可以运行一个 ls 命令:
$ ls –l
如下一个截图所示,我们获得了目录列表:

我们建议您探索文件系统并进行实验。您会发现您几乎可以做到任何事情,从操作文件到操作应用程序。
将文件推送到设备
Adb 给我们提供了数十个有用的命令来管理我们的设备:
$ adb push
adb push 命令无疑是其中最有用的一个。它允许我们从我们的电脑复制文件到我们的 Android 设备。下一个截图显示了如何将单个文件上传到我们的设备:

我们创建了一个新的文件,pippo.txt,其中包含一行,hello pippo,然后我们将这个文件上传到我们的连接设备,到 /sdcard/ 文件夹中。正如你所见,第一个参数是文件名,第二个参数是我们想要复制文件到的目标位置。
下一个截图显示了 pippo.txt 已成功上传到设备的 /sdcard/ 文件夹:

从设备中拉取文件
在开发过程中,我们可能需要从设备中检索一个文件。为了实现这一点,adb 给我们提供了 push 的对立面,即 pull:
$ adb pull
前面的命令能够从连接的设备中检索文件并将其复制到我们的电脑。语法与 push 类似,只是结果相反。下一个截图显示了如何从设备中 pull 我们的 pippo.txt 并将其复制到当前目录:

我们已经从当前文件夹中删除了原始文件,使用 . 作为目标将设备上的文件拉到当前文件夹,并检查复制的 pippo.txt 文件是否包含预期的行,hello pippo。
安装 Android APK 文件
正如我们所知,任何 Android 应用都包含在一个 APK 文件中。通常,用户看不到这个文件,因为他们都是通过 Google Play 商店安装所有应用的。作为高级用户,我们经常处理尚未发布的应用,用于调试和测试。这些应用在 Google Play 商店上还没有提供,所以 adb 给我们提供了手动安装它们的机会,使用以下命令:
~$ adb install <path to .apk file>
下一个截图显示了 APK 文件已成功安装到我们的设备 ZX1B226467 上:

Logcat
任何复杂的系统,如 Android,都需要一个日志系统。Android 通过 logcat 提供日志功能,以帮助用户进行开发和监控。使用以下命令:
~$ adb logcat
我们可以指示 adb 连接到 Android 日志系统,选择默认缓冲区,并开始实时将每个系统日志消息打印到我们的终端。Android 为高级使用提供了其他两个日志缓冲区:
-
radio:这包含与无线电通信系统相关的所有相关日志消息 -
events:这包含与系统事件相关的消息
我们可以使用 –b 选项选择一个不同于默认的缓冲区。例如,如果我们想查看所有与事件相关的日志,我们可以使用以下命令:
:~$ adb logcat –b events
Adb logcat 随附几种有趣的输出模式。我们可以使用 –v 选项和模式名称来选择它们:
-
简短
-
颜色
-
长度
-
可打印
-
进程
-
原始
-
标签
-
线程
-
线程时间
-
时间
-
微秒
下一张截图显示了选择 color 模式时的 logcat 输出:

如您所见,logcat 将为每个不同的日志级别使用不同的颜色。我们甚至可以使用以下命令根据日志级别本身进行过滤:
~$ adb logcat *:E
在这种情况下,我们只显示错误信息。下一张截图显示了我们可以使用的每个可用过滤参数:

要获取 logcat 所有可能选项的完整列表,您可以使用以下命令访问 logcat 命令的帮助:
~$ adb logcat –h
以下截图显示了所有可用选项及其描述的完整列表:

Fastboot
Fastboot 是 Android 提供给我们使用计算机和 USB 连接来操作设备闪存及其分区的工具。Fastboot 不与 Android 系统通信。它与能够在一个最小系统环境中交互的特定固件通信:引导加载程序模式。
在引导加载程序模式下,系统只初始化完成所有最关键操作所需的最小硬件和软件:
-
flash:此选项用于从主机计算机部署新的二进制系统镜像到设备分区 -
erase:此选项用于删除特定的分区 -
reboot:此选项用于将设备重新启动到可用的引导模式之一:恢复模式、引导加载程序或标准模式 -
format:此选项用于格式化特定的分区
下一张截图显示了以下命令的输出,列出了所有可用的 fastboot 选项:
~$ fastboot –-help
如您所想象的那样,fastboot 将在未来扮演重要角色,当我们开始构建和测试我们的自定义 Android 系统时:

选择我们的硬件
在前面的章节中,我们学习了如何获取源代码,构建系统是如何工作的,以及如何为模拟器构建我们的第一个自定义 Android 系统。我们对真实硬件的了解只有:Android 主要用于智能手机和平板电脑,并且我们可以根据 Android 兼容性定义文档 (CDD) 以及其所有约束和规则来认证我们的硬件。事实是,Android CDD 的目的是提供指导,以便将符合 Google 移动服务要求的设备推向市场。这是关键信息,因为它给了我们在目标不是为大众消费市场开发智能手机或平板电脑时选择不同硬件的自由。
在过去两年中,非智能手机或平板电脑,但能够运行 Android 的设备数量急剧增加。出现了一个全新的生态系统,即所谓的开发板,可以运行 Android 或 Ubuntu Linux 等。这些板中的大多数不符合 CDD 规范——它们没有 Google Play Store、YouTube、Google Maps 等应用程序,但它们仍然运行 Android,并且可以针对 Android CTS 进行测试。这对想要进行实验的制造商或高级用户来说是一个巨大的机会。
这种情况现在成为可能,因为启动 Android 的实际硬件要求现在变得越来越低。记住,Android 基于 Linux 内核,如果剥离 Google 应用生态系统,系统本身也有一些相似之处。如今,大多数配备足够硬件以运行 Linux 的板都有很好的机会运行 Android。
硬件架构
我们在 Android 市场上找到的最受欢迎的硬件架构无疑是 ARM 系列,包括 ARMv7 和 ARMv8-A。随着时间的推移,x86 和 MIPS 平台获得了官方支持,并在最近几个月中获得了市场份额。另外,Android 5 Lollipop 引入了对 64 位架构的支持。
最小要求
就像仅仅为了玩游戏而设定的最小要求一样,即使是 Android 本身也是如此。例如,Android 5.1 要求在标准显示密度设备上安装时至少有 512 MB RAM。否则,如果你计划将其移植到高密度显示设备上,你将至少需要 1.8GB RAM。
早期版本对 RAM 的要求较低。例如,Android 4.4 KitKat 只需要 512 MB RAM。不幸的是,KitKat 还有一些其他限制——不支持 64 位架构,并且需要 OpenGL ES 2.0 GPU。
许多其他硬件组件,如摄像头、GPS 传感器、加速度计、陀螺仪、触摸屏等,非常常见,但它们绝对是可选的——如果你的设备不需要摄像头,你可以节省一些钱。你可以根据你的使用案例,从非常基础的系统开始,定制到具体所需。
系统芯片 – SoC
先进嵌入式系统的出现,如智能手机和平板电脑,对新型嵌入式芯片产生了巨大需求——越来越多的小型且功能强大的芯片。当你想到计算机时,你会想到 CPU、主板、显卡以及大量的外部设备。在嵌入式领域,你会想到 SoC。
SoC 代表系统芯片,它超越了 CPU 的简单概念。大多数当前的 SoC 解决方案集成了多核 CPU、RAM 控制器、ROMs、EEPROMs 或闪存、USB 支持、以太网支持、USART、SPI,甚至电源管理系统。所有这些都在一个单独的芯片中,如下一张示例 SoC 架构的截图所示:

如你所想,这种方法的直接优势是系统的小型化。我们现在可以拥有强大、功能更完整、更复杂的系统,在更小、更小的封装中拥有更小的功耗,以满足市场的每一个需求。
这里最大的玩家如下:
-
三星
-
高通
-
华为
-
美达科
-
英伟达
-
英特尔
-
飞思卡尔
-
德州仪器
-
博通
基带处理器
如果你计划开发智能手机或带有无线电功能的设备,你将需要处理某种基带处理器(BP)。基带处理器是一个独立的组件;大多数时候它位于负责所有与无线电通信相关的 SoC 之外。
BP 是一个关键组件,出于安全原因被单独保留。各国政府对无线电组件认证有严格的政策,基本上,每个政府都要求这些组件配备只读固件。由于其性质,BP 通常配备特定的实时操作系统,并通过基于 AT 命令的串行总线与外部世界通信。
我们硬件的选择
这本书的主要目标是教授如何为现有设备创建一个自定义系统,以及如何为可以转变为 Android 设备的设备创建一个可工作的 Android 系统。
我们将在这次旅程中使用两个流行的设备:
-
摩托罗拉的谷歌 Nexus 6
-
Aidilab 和 SECO 的 UDOO
摩托罗拉 Nexus 6
在第二章中,我们学习了关于谷歌设备——智能手机、平板电脑等。在这一章中,我们将使用他们目前可用的最新智能手机——Nexus 6。

Nexus 6,代号 Shamu,目前是谷歌提供的顶级设备。其技术规格令人印象深刻:
-
高通®骁龙™ 805,四核 2.7 GHz CPU
-
显示 QHD AMOLED,5.96 英寸 2,560 x 1,440 (493 ppi),16:9
-
后置摄像头:13 MP,LED 闪光灯,f/2.0
-
前置摄像头:2 MP
-
GPU:Adreno 420
-
无线:802.11ac 2x2 (MIMO)
-
蓝牙:4.1
-
NFC
-
RAM:3 GB
-
存储:32 GB 或 64 GB
-
传感器:GPS、陀螺仪、加速度计、光传感器、气压计
-
网络连接:
-
GSM:850/900/1,800/1,900 MHz
-
Band WCDMA:1/2/4/5/6/8/9/19
-
Band LTE:1/3/5/7/8/9/19/20/28/41
-
CA DL:B3-B5,B3-B8
-
-
电池:3,220 mAh,无线充电系统
下面的截图显示了内部结构——SoC、电池、显示屏:

Nexus 6 显然是一个完全符合 CDD 和 CTS 的平台。它配备了完整的谷歌应用包,并将作为我们的参考认证设备。
UDOO Quad
与 Nexus 6 完全不同,UDOO 不是一个智能手机或 Google 认证设备——这里没有 Google 应用。它是一个所谓的单板计算机——一个可以配备 Android 或 Ubuntu Linux 的开发和实验板。UDOO 将成为我们的参考板,以证明我们可以从与智能手机相当不同的硬件中创建一个可工作的 Android 系统。
让我们看看它的技术规格:
-
Freescale ARM i.MX6 Cortex A9 四核 1GHz CPU
-
GPU Vivante GC 2000 + Vivante GC 355 + Vivante GC 320
-
Atmel SAM3X8E ARM Cortex-M3 CPU(与 Arduino Due 相同)
-
76 个完全可用的 GPIO:62 个数字 + 14 个数字/模拟
-
RAM:DDR3 1GB
-
以太网最高 1,000Mbit/s
-
板载 micro SD 卡作为主要存储
-
HDMI 端口
-
LVDS 端口
-
Wi-Fi 模块
-
SATA 接口
-
RTC 模块
-
CSI 摄像头连接
-
2 个 USB 端口
如你所见,没有传感器——没有花哨的光传感器或陀螺仪,没有加速度计,也没有 GPS。也没有基带处理器——我们无法打电话,但足以在上面运行 Android 了!
注意
你肯定注意到了 Atmel 微处理器。基本上,UDOO 配备了一个嵌入式 Arduino 微处理器,可以用来进一步推动你的实验——去做吧!
为实际设备编译 Android
到现在为止,你已经知道了关于构建系统和如何检索源代码所需的一切。检索 Google 官方设备的正确源代码并不是什么大问题,但生活并不总是这么简单。与许多不同的设备一起工作,你肯定会遇到一个不愿意提供源代码的制造商。他们没有法律义务发布它。这是一个不幸的情况,希望将来会被视为不良营销并消失。
对于我们的示例,我们将要玩两个提供良好支持且将完美服务于目的的设备。
Nexus 6
我们将要探索的第一台设备是摩托罗拉官方的 Google Nexus 6。我们已经对该设备有了概述。如果你想进一步推动它,可以参考官方摩托罗拉 Nexus 6 网页:
www.motorola.in/consumers/View-all-Mobile-Phones/Nexus-6-by-Motorola/nexus-6-in.html
在第二章中,我们学习了如何检索 Google 官方设备的源代码。我们现在需要知道的是特定的标签来引用:
android-5.1.1_r14
当我们有了源代码,我们可以使用设置脚本设置环境,并运行 lunch 命令来特别针对我们的 Nexus 6。下一张截图显示了我们是怎样选择设备编号 16,Nexus 6——代号 Shamu:
aosp_shamu_userdebug
这里是输出:

由于安全和版权原因,我们获取的源代码库并不包含构建系统所需的所有内容。现实世界的设备,与模拟器不同,包含必须单独下载的专有软件组件。例如,我们的 Nexus 6 有其三个组件制造商的专有软件:
-
Broadcom: NFC、蓝牙和 Wi-Fi
-
Motorola: 媒体、音频、热管理、触摸屏和传感器
-
Qualcomm: GPS、音频、摄像头、手势、图形、DRM、视频和传感器
软件组件以二进制文件的形式分发,可以在 developers.google.com/android/nexus/drivers 下载,查找 Nexus 6,构建代号 LMY48M。下载三个文件并将它们提取到您的 WORKING_DIRECTORY 中。下一个截图显示了您的下载文件夹内容,包括三个下载的文件:

每个下载的包在提取内容后都包含一个脚本,一旦您运行这个脚本,它将显示您需要接受才能继续的许可协议。下一个截图显示了 extract-broadcom-shamu.sh 文件的流程:
$ chmod +x extract-broadcom-shamu.sh
$ ./extract-broadcom-shamu.sh

这三个脚本是在启动实际构建过程之前的最终配置步骤。在我们接受所有三个许可协议后,我们可以运行我们信任的 make 命令,并耐心等待构建过程完成。
构建过程完成后,out/target/product/shamu/ 文件夹将包含您为 Google Nexus 6 的第一个 Android 构建。
UDOO Quad
UDOO 是市场上最受欢迎的开发板之一。硬件一流,用户社区良好,文档详尽,是无数实验的理想工作台。
UDOO 不是 Google 设备,所以我们没有机会使用我们已有的源代码来创建我们定制的 Android 系统。我们必须坚持使用 UDOO 制造商提供给高级用户的源代码。您可以从以下链接下载源代码:
download.udoo.org/files/Sources/UDOO_Android_4.4.2_Source_v1.0.tar.gz
下载文件后,您可以使用终端和以下命令提取它:
$ tar zxf UDOO_Android_4.4.2_Source_v1.0.tar.gz
注意
如您所已了解,目前可用的最后版本的 UDOO Android 源代码库是 KitKat。当我们的冒险结束时,您可以尝试将 Lollipop 移植到这个平台,作为一个新的具有挑战性的 Android 项目。
提取的文件和文件夹看起来与我们在 Nexus 6 中看到的官方 Android 文件夹结构完全一样。唯一的真正区别是 UDOO 为我们提供了几乎所有组件的源代码——您将找到引导加载程序源代码甚至 Linux 内核源代码。引导加载程序和内核将在构建过程中编译,与 Nexus 6 的情况不同,那时我们得到了预编译的文件。Android 系统、引导加载程序和内核将被组合,以创建我们需要部署到 UDOO 的最终镜像集。
设置
在启动envsetup脚本之前,我们需要配置环境,以便能够构建引导加载程序。我们将在下一节中学习很多关于引导加载程序的知识。现在,您只需打开您的终端并运行以下命令:
$ export ARCH=arm
"$ export CROSS_COMPILE=$PWD/prebuilts/gcc/linux-x86/arm/arm-eabi-4.6/bin/arm-eabi-
$ export PATH=$PWD/bootable/bootloader/u-boot-imx/tools:$PATH
$ source build/envsetup.sh
作为最后的配置步骤,我们需要设置构建系统,以便正确生成适用于我们的 UDOO 的系统镜像:
~$: lunch udoo-eng
引导加载程序
一切就绪。我们现在可以编译引导加载程序了。打开终端并导航到bootloader文件夹:
$ cd bootbable/bootloader/uboot-imx
这个文件夹包含执行引导加载程序编译的可执行文件。按照以下方式运行它:
$ ./compile –c
之前的命令将显示一个配置对话框,就像下一个截图中的那样。您将选择您要针对的硬件配置——CPU、RAM 等等。当一切配置妥当后,编译过程将被执行,并将生成引导加载程序二进制镜像:

系统
一旦我们有了引导加载程序镜像,我们就可以回到源代码根目录,并使用以下命令启动主系统镜像构建过程:
$ make
这可能需要一些时间,所以请耐心等待。像往常一样,当编译完成后,您将在out/文件夹中找到所有需要的二进制镜像,准备安装到我们的硬件上并使其活跃起来。
内核
Linux 内核将在 Android 系统构建过程中自动编译。如果您愿意,也可以使用以下命令自行编译内核:
$ make -C kernel_imx imx6_udoo_android_defconfig
$ make bootimage
该过程将在out/文件夹中生成一个新的boot.img。您可以在kernel_imx/arch/arm/boot中找到特定的内核文件。
引导加载程序模式
上一节指导您创建了第一个系统镜像,准备将其烧录到您的硬件上。这些镜像将被部署到设备内存中。Nexus 6 有一个内置的 Nand 内存。UDOO 有一个相当标准的 SD 卡。部署的第一步是将设备切换到引导加载程序模式。
引导加载程序模式是设备的一种特定状态,允许我们使用fastboot实用程序将系统镜像传输和部署到设备本身。每个运行 Android 的设备都有这种模式,但并非每个设备都允许我们访问它。一些设备配备了锁定的引导加载程序,出于安全原因或仅仅是因为制造商的短视。
显然,我们将能够访问我们设备上的引导加载程序:Google 是一个慷慨的制造商,每个 Nexus 设备都配备了未锁定或可解锁的引导加载程序;UDOO 作为开发板,旨在对开发者友好。
Nexus 设备
每个 Nexus 设备都会让我们访问引导加载程序模式,但每个设备都会以自己的方式完成。根据型号,我们需要一系列特定的步骤来在引导加载程序模式下启动设备。以下表格显示了如何为每个 Nexus 设备执行此操作。请确保关闭您的设备并拔掉 USB 线缆,从表格中选择型号,并按下正确的按钮:

对于我们的 Nexus 6,我们需要按下 音量下 然后也按下 电源 并保持两者按下。智能手机将启动,您将看到如下截图所示的屏幕:

我们现在处于 Bootloader Mode!
您首先会注意到的是相当明确的:
设备已锁定
正如我们所说,Nexus 设备配备了可解锁的引导加载程序。我们只需将设备通过标准 USB 线缆连接到我们的电脑,打开终端并运行以下命令:
$ fastboot oem unlock
您将看到一个警告消息,它会警告您解锁引导加载程序将擦除设备上的所有内容。是的,它会。这很不幸,但从安全和系统角度来看,这是必要的。
注意
这正是考虑数据备份的合适时机。您仍然可以中止进程,重新启动您的智能手机,保存您的数据,然后再次尝试。我们将等待您!
如果您足够勇敢,而且您不再需要手机上的所有小猫图片,只需选择 YES,引导加载程序将顺利解锁。如果出于任何原因您希望引导加载程序再次锁定,可以使用以下命令:
$ fastboot oem lock
在我们解锁引导加载程序后,我们获得了对 Nand 存储器的完全控制权——我们可以擦除分区或刷入我们创建的系统镜像。不幸的是,Google 没有发布引导加载程序源代码,所以我们不知道他们是如何实现整个 fastboot 协议的。幸运的是,我们将借助 UDOO 来解决这个问题。UDOO 制造商为我们提供了完整的源代码库,包括引导加载程序的源代码。
UDOO 家族板
UDOO 就像一本打开的书。我们可以几乎不费吹灰之力访问其内存上的每个分区。没有“按钮忍者组合”来切换到引导加载程序模式。我们可以使用串行连接来分析整个引导过程,停止它,并使用控制台与之交互:
-
连接串行接口
-
停止引导序列
-
访问 u-boot 控制台
-
运行 fastboot
我们现在已准备好 fastboot 服务器。有了服务器,我们将能够从我们的电脑连接到 fastboot,使用我们已知的 fastboot 客户端。
这个过程可能看起来比 Nexus 的过程要难一些。这是真的。事实是,UDOO 并没有像 Nexus 或市场上任何其他主流智能手机那样配备默认的保密引导加载程序。UDOO 主要是一块开发板,就像很多这样的设备一样,它给你提供了选择你喜欢的引导加载程序的自由和权力。然而,为了更加友好地对待开发者,UDOO 可以与最流行的开源引导加载程序解决方案——uboot 完美地协同工作。
uboot 解决方案完全符合引导加载程序正确启动操作系统的标准要求——硬件初始化、内存测试等等。它还实现了 fastboot 协议和从构建系统生成的 boot.img 中提取内核。这两个特性使得它与 Android 完全兼容。
刷 Android 系统镜像
到这里了。拼图的每一块都到位了——你终于可以开始将你全新的自定义 Android 版本安装到你的设备上了。
注意
作为提醒,我们构建了所谓的库存版本的 Android 系统:你在这里找不到任何 Google 应用程序——没有 YouTube,没有 Google Play Store。
Nexus 6
构建过程完成后,你将在 out/target/product/shamu 文件夹中找到你需要的所有系统镜像:
-
system.img:这个是,嗯,系统镜像。它包含整个操作系统——Android 框架、系统本地库以及系统实用应用程序,例如计算器或时钟。 -
recovery.img:这个镜像包含我们将放置在 Recovery 分区中的内容。它包含一个内核和恢复软件本身。 -
boot.img:这个镜像包含 Linux 内核和一个小型的 RamDisk。这个镜像将被放置在引导分区中,并将包含初始化系统所需的所有文件:例如init.rc,以及启动系统所需的每一个组件。
每个分区都可以使用特定的分区镜像和适当的命令进行刷写。将你的 Nexus 切换到引导加载程序模式,插入 USB 线,然后刷写几个分区。启动你的终端,导航到 out/target/product/shamu,并执行以下命令:
:$ fastboot flash system system.img
:$ fastboot flash boot boot.img
:$ fastboot flash recovery recovery.img
:$ fastboot reboot
最后一条命令将重新启动你的设备,你全新的自定义 Android 版本将开始运行!这个系统的第一个版本肯定看起来会很简陋,没有 Google Play Store。没有安装应用程序的可能性,实际上我们几乎无法使用这个设备。请不要露出悲伤的表情!在接下来的章节中,我们将学习如何获取和安装我们需要的 Google 应用程序以及如何定制我们的系统。
作为最后的说明,在这个第一次运行中,我们使用了 Google 提供的 Linux 内核——我们没有从源代码编译它。在接下来的章节中,我们将学习如何进行编译并完全控制。
UDOO
如往常一样,UDOO 略有不同。我们有几种可能的路径来实现我们的目标,但首先的事情是分区。第一步是准备带有适当分区的 SD 卡。与 Nexus 及其预分区 Nand 内存不同,Nexus 可以直接烧录,UDOO 我们完全控制系统,包括内存分区。
自由和力量伴随着责任——我们需要在安装系统之前创建适当的分区。为了方便开发者,UDOO 开发团队提供了一个方便的脚本来加快这项工作。你的 UDOO 工作目录的根目录中包含一个make_sd.sh文件。将 UDOO SD 卡插入你的电脑并检测磁盘号:
-
在 Linux 上,使用
df –f,你应该寻找类似/dev/mmcblkX的东西。 -
在 OS X 上,使用
diskutil list,你应该寻找类似/dev/rdisksX的东西。
一个超级简单的技巧是将 SD 卡插入并记下所有的磁盘号。取出 SD 卡,找出现在缺失的那个!一旦你检测到磁盘号,你可以像这样运行脚本,指定正确的磁盘名称:
$ ./make_sd.sh /dev/mmcblkX
脚本将自动擦除 SD 卡,创建分区结构,并复制构建系统在out/中生成和部署的所有文件。这可能需要一段时间,具体取决于你的 SD 卡速度。
正如我们所见,为谷歌设备开发相当直接:我们下载源代码并开始配置系统以生成我们的构建镜像。最终我们得到一个可以稍后决定按需定制的系统版本。将 Android 开发或移植到新硬件则完全是另一回事:这相当不同,需要一些努力和承诺。
当你决定开始这样的旅程时,第一步是选择合适的硬件平台。市场提供了大量的供应商,每个供应商都提供他自己的特定解决方案——不同的 SoC、不同的板载传感器、便宜的低端板或超快的昂贵板。这本书中没有讨论选择昂贵板或不选择昂贵板的讨论空间。我们专注于开发者和他们的世界,作为一个专业人士,99%的时间他们都会发现自己在与所谓的参考板打交道*。
参考板是一种特殊类型的发展板,每个供应商都向其潜在客户提供。通常,参考板会配备尽可能多的板载组件——大量的传感器、大量的外部设备、大量的连接器以及可能的应用目的。最终目标是向开发者提供一个能够真正展示 SoC 和整个硬件解决方案全部潜力的板子。一切都是为了使开发者的生活更轻松:提供了 Linux 内核源代码,提供了硬件组件规格,并提供了文档。
在本章中,我们使用的 UDOO 板可以被认为是参考板。它没有每个可能传感器,但它很容易通过外部传感器进行扩展,并且我们知道如何与这些传感器通信,因为平台是开放的且易于调试。一种调试我们软件和硬件的简单方法是至关重要的,这有助于使我们的开发时间更有效。
UDOO 配备了一个方便的 micro-USB 连接,它也是一个串行到 USB 转换器。使用此连接,我们可以在最低级别的监视器之一与板子交互并操作启动序列。为了正确连接到板子控制台,我们需要在计算机上安装特定的软件:一个名为 minicom 的调制解调器控制和终端仿真器。
您可以使用 apt-get 在 Ubuntu 上安装它:
$ sudo apt-get install minicom
您可以使用 brew 在 OS X 上安装它:
$ brew install minicom
当我们有了 minicom,我们可以将关闭的 UDOO 连接到 USB 端口,并在我们的终端上运行以下命令:
$ minicom –b 115200 –D /dev/ttyUSB0
ttyUSB0 是操作系统与 UDOO 连接关联的系统设备。在您的系统中,它可能不同,例如 ttyUSB1、ttyUSB2,这取决于硬件配置、其他连接的 USB 设备等因素。可能需要进行一些尝试和错误。
现在,我们可以插入电源线并打开板子。如果连接配置正确,您将看到以下截图所示的启动序列:

我们可以通过几种有趣的方式监控启动序列并与系统交互。我们现在感兴趣的是停止启动序列并切换到引导加载程序模式。
在启动序列过程中,您将看到一条消息,提示如何停止启动序列本身并访问 uboot。一旦进入,按照以下截图所示运行 fastboot:

现在,我们可以刷写我们拥有的系统镜像:
$ fastboot flash system system.img
$ fastboot flash boot boot.img
$ fastboot flash recovery recovery.img
$ fastboot reboot
在保持串行连接的情况下,当系统重启时,我们可以享受启动序列提供的所有系统消息:系统初始化和 Linux 内核加载,直到我们达到 Android 加载并完成 Android 系统控制台提示。这正是深入访问和了解您的系统和硬件的美丽与力量。以下截图显示了内核部署精确时刻的启动序列的一部分:

摘要
在本章中,您为实际设备构建并安装了您的第一个 Android 系统。您现在对 Google Nexus 6 和 UDOO 板了解得更多。您已经学习了如何使用 ADB 和 Fastboot。您已经学习了如何通过串行连接和 minicom、监控以及操作启动序列等工具与开发板交互。
在下一章中,我们将深入了解 Linux 内核的构建和定制。
第五章. 自定义内核和引导序列
在上一章中,我们创建并部署了我们自己的第一个自定义 Android 版本。我们为商业智能手机 Google Nexus 6 创建了一个版本,并为开发板 Udoo Quad 创建了一个更硬核的版本。我们学习了更多开发工具,如 ADB 和 Fastboot。我们专注于调试工具,掌握串行连接和引导序列。
在本章中,我们将深入系统——从内核定制到引导序列。您将学习如何获取谷歌设备的正确源代码,如何设置构建环境,如何构建您的第一个自定义 Linux 内核版本,并将其部署到您的设备上。您将了解:
-
工具链概述
-
如何配置主机系统以编译自己的 Linux 内核
-
如何配置 Linux 内核
-
Linux 内核概述
-
Android 引导序列
-
Init进程
Linux 内核概述
在 Linux 内核及其构建中。选择 Linux 内核的一个原因是其无可置疑的灵活性和无限的可能性,可以将其调整到任何特定场景和需求。正是这些特性使 Linux 成为嵌入式行业中最受欢迎的内核。
Linux 内核附带 GPL 许可证。这个特定的许可证允许谷歌从 Android 的早期阶段开始为项目做出贡献。谷歌提供了错误修复和新功能,帮助 Linux 克服了 2.6 版本的几个障碍和限制。最初,Linux 2.6.32 是 Android 设备市场中最受欢迎的版本。如今,我们看到越来越多的设备配备了新的 3.x 版本。
下面的截图显示了官方谷歌摩托罗拉 Nexus 6 的当前构建,内核版本为 3.10.40:

在前几章中,我们创建并部署了我们自己的 Android 版本,该版本配备了 Linux 内核的二进制版本。使用已经编译好的内核版本是标准做法:正如我们所见,AOSP 提供了这种类型的体验。
作为高级用户,我们可以更进一步,为我们的自定义 Android 系统构建一个自定义内核。Nexus 系列提供了进入这个世界的便捷途径,因为我们可以轻松地获取构建自定义版本所需的内核源代码。我们还可以将我们的自定义 Linux 内核装备到我们的自定义 Android 系统中,我们将拥有一个完全定制的 ROM,适用于我们的特定需求。
在这本书中,我们故意使用 Nexus 设备——谷歌是少数几家正式提供内核源代码的公司之一。即使每个生产和销售 Android 设备的公司都受到法律强制要求发布内核源代码,但其中很少有人真正这样做,尽管有 GPL 许可证规则。
获取内核
谷歌为 Nexus 系列中每个设备的每个 Android 版本提供了内核源代码和二进制版本。
以下表格显示了二进制版本和源代码的位置,按设备代码名称排序:

正如第四章“转向现实硬件”中所述,我们将使用摩托罗拉 Nexus 6,代码名称Shamu。
内核的二进制版本和内核源代码都存储在 git 存储库中。我们只需要编写正确的 URL 并克隆相应的存储库。
获取内核的二进制版本
在本节中,我们将获取内核作为二进制、预构建文件。我们需要的只是之前显示每个设备型号、其代码名称及其二进制位置的表格,我们可以使用这些信息来组成下载 URL。我们针对的是谷歌 Nexus 6,代码名称shamu,二进制位置:
device/moto/shamu-kernel
因此,要获取摩托罗拉 Nexus 6 内核的二进制版本,我们需要以下命令:
$ git clone https://android.googlesource.com/device/moto/shamu-kernel
上一条命令将克隆存储库并将其放置在shamu-kernel文件夹中。这个文件夹包含一个名为zImage-dtb的文件——这个文件是实际可以集成到我们的 ROM 并刷入我们设备的内核镜像。
获取内核镜像后,我们可以使用以下命令获取内核版本:
$ $ dd if=kernel bs=1 skip=$(LC_ALL=C grep -a -b -o $'\x1f\x8b\x08\x00\x00\x00\x00\x00' kernel | cut -d ':' -f 1) | zgrep -a 'Linux version'
输出:

上一张截图显示了命令输出:我们的内核镜像版本是 3.10.40,它是在 10 月 22 日 22:49 使用 GCC 版本 4.8 编译的。
获取内核源代码
对于二进制版本,上一张表格对于下载内核源代码也是关键的。针对谷歌 Nexus 6,我们使用设备代码名称shamu的源位置字符串创建下载 URL:
kernel/msm.git
一旦我们有了确切的 URL,我们就可以使用以下命令克隆 GIT 存储库:
$ git clone https://android.googlesource.com/kernel/msm.git
Git 将创建一个msm文件夹。文件夹会奇怪地空着——这是因为文件夹默认跟踪master分支。为了获取我们的 Nexus 6 的内核,我们需要切换到正确的分支。
可用的分支有很多,我们可以使用以下命令查看列表:
$ git branch -a
列表将显示每个分支,针对特定 Nexus 设备的特定 Android 版本。以下截图显示了这些存储库的子集:

现在你已经知道了分支名称,对于你的设备和你的 Android 版本,你只需要检出正确的分支:
$ git checkout android-msm-shamu-3.10-lollipop-release
以下截图显示了预期的命令输出:

设置工具链
工具链是所有用于有效地将特定软件编译成二进制版本所需工具的集合,使用户能够运行它。在我们的特定领域,工具链允许我们创建一个系统镜像,该镜像可以用于烧录到我们的 Android 设备上。有趣的是,工具链允许我们为与当前架构不同的架构创建系统镜像:我们很可能是使用 x86 系统,但我们想创建一个针对 ARM(高级精简指令集机器)设备的系统镜像。针对与宿主系统架构不同的架构编译软件被称为交叉编译。
互联网为这项任务提供了一些方便的解决方案——我们可以使用与 AOSP(Android 开源项目)一起提供的标准工具链,或者我们可以使用一个替代的、非常流行的工具链,即 Linaro 工具链。这两个工具链都能完成工作——为 ARM 架构编译每一个 C/C++文件。
如同往常,工具链既可用预编译的二进制文件提供,也可作为源代码提供,以便编译。对于我们的旅程,我们将使用由 Google 提供的官方工具链,但当你需要更深入地探索这个领域时,你可以尝试下载来自www.linaro.org/download的 Linaro 工具链的二进制版本。Linaro 工具链被认为是市场上最优化和性能最好的工具链,但我们的目标不是比较工具链或固执地使用最好的或最受欢迎的一个。我们的目标是创建尽可能平滑的体验,从整个构建自定义 Android 系统方程中移除不必要的变量。
获取工具链
我们将使用由 Google 提供的官方工具链。我们可以通过 Android 源代码或单独下载来获取它。如果您手头有信任的 Android 源代码文件夹,您可以在以下文件夹中找到工具链:
AOSP/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/
这个文件夹包含构建自定义内核所需的所有内容——编译器、链接器以及一些其他工具,如调试器。
如果由于某些不幸的原因,您缺少 Android 源代码文件夹,您可以使用以下 git 命令下载工具链:
$ git clone https://android.googlesource.com/platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8
准备宿主系统
要成功编译我们的自定义内核,我们需要一个配置正确的宿主系统。要求与我们在上一章中构建整个 Android 系统时满足的要求相似:
-
Ubuntu
-
Linux 内核源代码
-
工具链
-
Fastboot
Ubuntu 需要一点爱来完成任务:我们需要安装ncurses-dev包:
$ sudo apt-get install ncurses-dev
一旦安装了所有必需的工具,我们就可以开始配置所需的环境变量。这些变量在交叉编译过程中使用,并且可以通过控制台进行设置。启动您信任的终端并运行以下命令:
$ export PATH=<toolchain-path>/arm-eabi-4.8/bin:$PATH
$ export ARCH=arm
$ export SUBARCH=arm
$ export CROSS_COMPILE=arm-eabi-
配置内核
在能够编译内核之前,我们需要正确配置它。Android 仓库中的每个设备都有一个特定的分支,带有特定的内核和特定的配置要应用。
第 2 页的表格中有一列包含我们需要的精确信息——构建配置。这些信息代表了我们正确配置内核构建系统所需的参数。让我们为我们的 Google Nexus 6 配置一切。在你的终端中,运行以下命令:
$ make shamu_defconfig
此命令将创建一个针对你的设备的特定内核配置。以下截图显示了命令的运行情况和最终的成功消息:

一旦.config文件就位,你就可以使用默认配置来构建内核。作为高级用户,我们想要更多,这就是为什么我们将完全控制系统,深入内核配置。编辑配置可以启用缺失的功能或禁用不需要的硬件支持,以创建完美的自定义内核,满足你的需求。
幸运的是,为了改变内核配置,我们不需要手动编辑.config文件。Linux 内核提供了一个图形工具,它将允许你导航整个配置文件结构,获取关于单个可配置项的文档,并毫不费力地准备一个自定义配置文件。
要访问配置菜单,打开你的终端,导航到kernel文件夹并运行以下命令:
$ make menuconfig
以下截图显示了官方 Linux 内核配置工具——没有花哨的功能,但非常有效:

在截图的上半部分,你可以看到我们将要定制的内核版本以及如何导航所有这些菜单项的快速文档:你使用箭头键导航,使用Enter 键进入子菜单,使用Y/N或空格键选择或取消选择一个项。
权力越大,责任越大,所以启用和禁用功能时要小心——检查menuconfig中的文档,检查互联网,最重要的是,要有信心。错误的配置可能会在引导过程中导致系统冻结,这将迫使你学习,创建不同的配置并再次尝试。
作为现实世界的例子,我们将启用 FTDI 支持。Future Technology Devices International 或 FTDI 是一家全球知名的半导体公司,以其 RS-232/TTL 到 USB 设备而闻名。这些设备在通过标准 USB 连接与嵌入式设备通信时非常有用。要启用 FTDI 支持,你需要按照以下步骤导航到正确的菜单:
Device Drivers|USB support|USB Serial Converter support
一旦你到达这个部分,你需要启用以下项:
USB FTDI Single Port Serial Driver
以下截图显示了正确选择的项,并给你一个我们可能支持多少设备的想法(此屏幕只显示 USB 串行转换器支持):

一旦一切就绪,只需选择退出并保存配置,如图所示:

使用完全相同的方法,您可以添加您想要的每一个新功能。一个重要的注意事项,我们添加了 FTDI 包,将其合并到内核映像中。Linux 内核给您提供了将功能作为模块提供的可能性。模块是一个外部文件,具有.ko扩展名,可以在运行时注入和加载到内核中。内核模块在您在纯 Linux 系统上工作时是一个伟大且方便的功能,但在 Android 上它们非常不实用。希望有一个模块化内核,您应该自己编写整个模块加载系统,这会给系统增加不必要的复杂性。我们选择将 FTDI 功能包含在内核映像中,从尺寸角度来看可能会受到惩罚,但可以免除对模块本身的手动管理。这就是为什么常见的策略是将我们想要的每一个新功能直接集成到内核核心中。
编译内核
一旦您有一个正确配置的环境和一个全新的配置文件,您只需要一个命令就可以开始构建过程。在您的终端模拟器中,在内核源文件夹中,启动:
$ make
make命令将完成必要的配置并启动编译和汇编过程。这个过程所需的时间很大程度上取决于您系统的性能:可能是一分钟或一个小时。作为一个参考,i5 2.40 GHz CPU,8 GB RAM 的系统完成一个干净的构建需要 5-10 分钟。这比编译整个 AOSP 镜像要快得多,正如您所看到的,这是因为代码库的复杂性和大小不同。
与非谷歌设备一起工作
到目前为止,我们一直使用谷歌设备,享受谷歌开源的心态。作为高级用户,我们经常处理来自谷歌或甚至不是智能手机的设备。作为一个现实世界的例子,我们将再次使用 UDOO 板:一款支持 Ubuntu 或 Android 的单板计算机。目前,UDOO 最流行的版本是 UDOO Quad,这也是我们针对的版本。
对于其他所有设备,标准方法是将制造商的网站视为获取内核源代码和任何有用文档的过程的信任来源:最重要的是,如何正确地将新内核刷入系统。当使用自定义内核时,程序相当规范。您需要源代码、工具链、几个配置步骤,也许还需要在您的宿主系统上安装一些特定的软件包。当涉及到刷写内核时,每个设备都可能有不同的程序。这取决于系统的设计方式和制造团队提供的工具。Google 提供了 fastboot 来将我们的镜像刷入我们的设备。其他制造商通常提供类似或可以轻松完成类似任务的工具。
UDOO 开发团队努力使 UDOO 板完全兼容 fastboot——而不是强迫您适应他们的工具,他们调整了他们的设备以与您已经熟悉的工具一起工作。他们调整了板上的引导加载程序,现在您可以使用 fastboot 来刷写 boot.img,就像您在刷写标准的 Google Android 设备一样。
要获取内核,我们只需要克隆一个 git 仓库。使用您信任的终端,运行以下命令:
$ git clone http://github.com/UDOOBoard/Kernel_Unico kernel
一旦我们有了内核,我们需要在 Ubuntu 系统中安装一些软件包,以便能够与之一起工作。使用以下命令,所有内容都将被安装并就绪:
$ sudo apt-get install build-essential ncurses-dev u-boot-tools
是时候选择工具链了!UDOO 给您提供了一些选择——您可以使用与 Nexus 6 相同的工具链,或者使用 UDOO 团队提供的工具链。如果您决定使用 UDOO 官方工具链,您可以使用几个终端命令下载它。请确保您已经安装了 curl。如果没有,只需使用以下命令安装它:
$ sudo apt-get install curl
一旦您安装了 curl,您可以使用以下命令下载工具链:
$ curl http://download.udoo.org/files/crosscompiler/arm-fsl-linux-gnueabi.tar.gz | tar -xzf
现在,您已经准备好启动构建过程:
$ cd kernel
$ make ARCH=arm UDOO_defconfig
以下是输出结果:

上一张截图显示了配置过程的输出。当默认的 .config 文件准备就绪时,您可以使用以下命令启动构建过程:
$ make –j4 CROSS_COMPILE ../arm-fsl-linux-gnueabi/bin/arm-fsl-linux-gnueabi- ARCH=arm uImage modules
当构建过程完成后,您可以在 arch 文件夹中找到内核镜像:
$ arch/arm/boot/uImage
对于 Nexus 6,我们可以使用 menuconfig 来自定义 UDOO 内核。从内核源代码文件夹中,运行以下命令:
$ make ARCH=arm menuconfig
以下截图显示了 UDOO 内核配置菜单。它与 Nexus 6 配置菜单非常相似。我们有相同的按键组合来导航、选择和取消选择功能等:

在使用 UDOO 时,与 Nexus 6 一样,我们在移除内核组件时也要小心——其中一些只是为了支持特定硬件而存在,而另一些则是系统启动的关键。像往常一样,您可以自由实验,但请注意不要冒险!
与智能手机相比,这种类型的开发设备使内核调试变得稍微容易一些。UDOO,就像许多其他嵌入式开发板一样,提供了一个串行连接,使您能够监控整个启动序列。如果您正在为某些硬件开发驱动程序并将其集成到内核中,或者您只是想玩一些自定义内核配置,这将非常有用。每个内核和启动相关的消息都将打印到串行控制台,以便捕获和分析。
下一个截图显示了我们的 UDOO Quad 板的启动序列:

如您所见,从板子开机到 Android 系统提示,有大量的调试信息。
驾驶员管理
自从 2.6.x 版本以来,Linux 给开发者提供了将内核的部分编译为分离的模块的机会,这些模块可以注入到核心中,以便在运行时添加更多功能。这种方法提供了灵活性和自由度:无需重新启动系统即可享受新功能,如果您只需要更新特定模块,则无需重新构建整个内核。这种方法在 PC 世界中广泛使用,包括路由器、智能电视,甚至是我们熟悉的 UDOO 板。
编写一个新的内核模块并非易事,这远非本书的目的:关于这个主题有很多书籍,而大部分技能都来自经验。在这些页面中,您将了解整体情况、关键点和可能性。
不幸的是,Android 并不使用这种模块化方法:每个必需的功能都构建在一个单一的二进制内核文件中,出于实用性和简单性的原因。在过去的几年里,有一种趋势是将 Wi-Fi 功能所需的逻辑也集成到内核中,这在之前是从分离的模块在启动序列中加载的。
正如我们在上一页的 FTDI 示例中看到的那样,将新驱动程序添加到我们的 Android 内核中最实用的方法是使用 menuconfig 并将功能作为内核的核心部分构建。
在下一章中,我们将更深入地探讨这个主题,并为我们的内核添加一些默认配置中不存在的功能。
修改 CPU 频率
超频 CPU 是高级用户中最受欢迎的话题之一。从您的设备中获取最大性能的想法令人兴奋。论坛和博客充满了关于超频的讨论,在本节中,我们将概述并澄清您在旅程中可能会遇到的几个棘手方面。
每个 CPU 都是设计用来与特定的时钟频率或特定频率范围内工作的。任何现代 CPU 都有可能在需要时调整其时钟频率以最大化性能,在不需要性能时降低功耗,从而在我们的心爱移动设备上节省宝贵的电池。因此,超频表示通过软件改变这个工作时钟频率的可能性,将其提高到高于 CPU 设计频率的水平。
与我们经常在无良论坛帖子或博客上读到的内容相反,CPU 超频可能是一个非常危险的操作:我们正在迫使 CPU 以正式尚未测试过的时钟频率工作。这可能会对我们产生反效果,导致设备自动重启以保护自身,或者在最坏的情况下,我们甚至可能损坏 CPU。
管理 CPU 时钟频率的另一个有趣方面是所谓的降频。利用 CPU 时钟频率调整功能,我们可以根据 CPU 负载和其他方面设计并实施调整策略,以最大化效率。例如,当设备空闲或处于睡眠模式时,我们可以降低频率;当设备处于重负载时,我们可以将时钟频率推至最大,以在每个场景中享受最大的效果。进一步推进 CPU 管理,许多智能手机 CPU 都采用多核架构:如果当前场景不需要,你可以完全关闭一个核心。
降低 CPU 频率的关键概念是在制造商提供的最低频率以下添加一个新的频率。通过软件,我们可以强制设备运行在这个频率上并节省电池。这个过程并非没有风险。我们可能会创建出设备 CPU 频率如此之低,以至于会导致设备无响应或甚至冻结的情况。至于超频,这些是未知的领域,只有谨慎、经验和运气才能让你得到满意的结果。
总体概述
Linux 内核使用称为控制器的特定策略来管理 CPU 频率调整。Linux 内核中已经预建了一些控制器,可以通过menuconfig访问,但你也可以添加定制的控制器,以满足你的特定需求。
以下截图显示了 Google Nexus 6 的menuconfig部分,用于 CPU 频率调整配置:

如您所见,有六个预构建的控制器。命名约定非常有用,使得名称具有自解释性:例如,performance控制器旨在始终保持 CPU 在最高频率,以在任何时候实现最高性能,牺牲电池寿命。
在 Android 上最受欢迎的控制器无疑是ondemand和interactive控制器:这些在许多基于 Android 的设备内核中相当常见。我们的参考设备 Google Nexus 6 使用interactive作为默认控制器。
如你所预期,出于安全原因,谷歌禁止直接管理 CPU 频率。在 Android 上没有快速选择特定频率或特定管理器的方法。然而,高级用户可以通过一点努力来满足他们的好奇心或需求。在下一章中,你将了解更多关于 CPU 管理的内容,但现在,让我们定制你的引导镜像。
定制引导镜像
到目前为止,你已经学习了如何获取内核源代码,如何设置系统,如何配置内核,以及如何创建你的第一个自定义内核镜像。下一步是给你的设备配备你的新内核。为了实现这一点,我们将分析每个 Android 设备使用的 boot.img 文件的内部结构。
创建引导镜像
一个自定义固件包含四个 .img 文件,这些文件是创建工作 Android 系统所必需的。其中两个(system.img 和 data.img)是兼容 Linux 文件系统的压缩镜像。
剩下的两个文件(boot.img 和 recovery.img)不包含标准文件系统。相反,它们是针对 Android 的自定义镜像文件。这些镜像包含一个 2KB 的头部扇区,内核核心,使用 gzip 压缩,ramdisk 以及可选的第二状态加载器。
Android 在 AOSP 源文件夹中 mkbootimg 包含的 boot.img.h 文件中提供了关于镜像文件内部结构的更多信息。
以下截图显示了该文件内容的一个片段:

如你所见,该图像包含 boot.img 结构的图形表示。这种 ASCII 艺术包含对大小和页面的更深入解释。
要创建一个有效的 boot.img 文件,你需要你刚刚构建的内核镜像和一个 ramdisk。ramdisk 是一个在引导时挂载到系统 RAM 中的小型文件系统。ramdisk 提供了一组对引导序列至关重要的文件,对于成功的引导序列是必需的。例如,它包含负责启动引导序列中所需所有服务的 init 文件。
生成引导镜像主要有两种方法:
-
我们可以使用
mkbootimg工具 -
我们可以使用 Android 构建系统
使用 mkbootimg 给你提供了很多自由度,但同时也带来了很多复杂性。你需要大量的命令行参数来正确配置生成系统并创建一个有效的镜像。另一方面,Android 构建系统已经包含了整个配置参数集,已经设置好并准备好使用,我们无需做任何努力就可以创建一个有效的镜像。仅为了给你一个关于 mkbootimg 复杂性的大致概念,以下截图显示了所需的参数概览:

玩弄如此强大的东西很有吸引力,但正如你所看到的,传递给 mkbootimg 的可能错误参数数量很大。作为务实的开发者,目前处理 mkbootimg 的风险不值得。我们想要完成任务,所以我们将使用 Android 构建系统轻松生成一个有效的引导镜像。
在前面的章节中,你使用 Android 源代码和正确配置的构建系统创建了一个整个系统的自定义版本。我们将利用我们已经完成的所有工作来完成这一新步骤。你所需要做的只是导出一个新的环境变量,指向你几页前创建的内核镜像。使用你信任的终端模拟器,启动:
$ export TARGET_PREBUILT_KERNEL=<kernel_src>/arch/arm/boot/zImage-dtb
一旦你设置了并导出了 TARGET_PREBUILT_KERNEL 环境变量,你可以启动:
$ make bootimage
一个全新的、完全定制的引导镜像将由 Android 构建系统创建,并将放置在以下文件夹中:
$ target/product/<device-name>/boot.img
只需几条命令,我们就有了全新的 boot.img 文件,准备好刷入。使用 Android 构建系统生成引导镜像是所有 Nexus 设备以及那些设计得尽可能接近官方 Google 设备的设备的首选方式。
对于所有符合这一理念的市场上的设备,事情开始变得棘手,但并非不可能。一些制造商利用 Apache v2 许可证,不提供完整的 Android 源代码。你可能会发现自己只有内核源代码,而无法利用 Android 构建系统创建你的引导镜像,甚至无法理解 boot.img 实际的结构。
在这些情况下,一个可能的方法是从一个工作设备中提取 boot.img,提取内容,用你的自定义版本替换默认内核,然后使用 mkbootimg 重新创建 boot.img:说起来容易做起来难。
目前,我们想要关注主要场景,处理一个不与我们抗争的系统。在接下来的章节中,你将学习如何反击并完全控制系统。
升级新的引导镜像
一旦你有了全新的、定制的引导镜像,其中包含你的自定义内核镜像,你只需要将其刷入你的设备。我们正在处理 Google 设备或至少是兼容 Google 的设备,因此你将能够使用 fastboot 将你的 boot.img 文件刷入你的设备。
要能够将镜像刷入设备,你需要将设备置于 fastboot mode,也称为 bootloader mode。每个设备都有进入此模式的方法,因此,根据你使用的设备,你可以查看第四章 迈向现实硬件 中的表格,其中包含了到达 fastboot 模式的所有步骤。
一旦您的设备进入快启模式,您可以通过 USB 将其连接到主机计算机。启动一个终端模拟器并运行升级引导分区的命令:
$ sudo fastboot flash boot boot.img
几秒钟后,fastboot将用您的boot.img文件的内容替换设备引导分区的内容。当烧录过程成功完成后,您可以使用以下命令重启您的设备:
$ sudo fastboot reboot
设备将使用您的新内核重启,多亏了您在前几页添加的新 USB TTL 支持,您将能够使用终端模拟器监控整个引导序列。
Android 引导序列
为了全面理解所有 Android 内部结构,我们将学习整个引导序列是如何工作的:从开机到实际的 Android 系统引导。Android 的引导序列与基于 Linux 的任何其他嵌入式系统类似:在非常抽象的方式下,在开机后,系统初始化硬件,加载内核,并最终加载 Android 框架。任何基于 Linux 的系统在其引导序列中都会经历一个类似的过程:您的 Ubuntu 计算机甚至您的家庭 DSL 路由器。
在接下来的几节中,我们将深入探讨这些步骤,以全面理解我们如此喜爱的操作系统。
内部 ROM – BIOS
当您按下设备上的电源按钮时,系统会加载存储在 ROM 内存中的一小部分代码。您可以将这想象成您 PC 上所拥有的 BIOS 软件的等效物。该软件负责设置 CPU 时钟的所有参数并运行 RAM 内存检查。之后,系统将引导加载程序加载到内存中并启动它。
引导加载程序概述
到目前为止,引导加载程序已经加载到 RAM 内存并启动。引导加载程序负责将系统内核加载到 RAM 内存中并启动它,以继续引导序列。
对于 Android 设备来说,最流行的引导加载程序软件是 U-Boot,即通用引导加载程序。U-Boot 在各种嵌入式系统中得到广泛应用:如 DSL 路由器、智能电视、信息娱乐系统等。U-boot 是开源软件,它可以根据任何设备进行定制,这种灵活性无疑是其受欢迎的原因之一。
U-boot 的主要任务是读取引导分区中的内核镜像,将其加载到 RAM 内存中,并运行它。从这一刻起,内核负责完成引导序列。
您可以将 Android 系统中的 U-boot 想象成 Ubuntu 系统中的 GRUB:它读取内核镜像,解压缩它,将其加载到 RAM 内存中,并执行它。以下图表为您展示了嵌入式 Linux 系统、Android 系统和 Linux PC 上的整个引导序列的图形表示:

内核
引导加载程序加载内核后,内核的第一个任务是初始化硬件。当所有必要的硬件都正确设置后,内核从boot.img挂载 ramdisk 并启动init。
初始化进程
在标准的 Linux 系统中,init 进程负责启动引导系统所需的所有核心服务。最终目标是完成引导序列并启动图形界面或命令行,以便用户可以使用系统。整个过程基于一系列特定的系统脚本,以严格的顺序执行,以确保系统完整性和正确的配置。
Android 遵循相同的理念,但行为不同。在标准的 Android 系统中,ramdisk(包含在 boot.img 中)提供了 init 脚本和引导所需的所有脚本。
Android 初始化进程由两个主要文件组成:
-
init.rc
-
init.${ro.hardware}.rc
init.rc 文件是系统的第一个初始化脚本。它负责初始化所有 Android 系统共有的方面。第二个文件非常特定于硬件。正如你所猜到的,${ro.hardware} 是引导序列发生时特定硬件的占位符。例如,在模拟器引导配置中,${ro.hardware} 被替换为 goldfinsh。
在标准的 Linux 系统中,初始化序列执行一组 bash 脚本。这些 bash 脚本启动一组系统服务。Bash 脚本在许多 Linux 系统中是一个常见的解决方案,因为它非常标准化且相当流行。
Android 系统使用不同的语言来处理初始化序列:Android 初始化语言。
Android 初始化语言
Android 团队选择不使用 Bash 来编写 Android 初始化脚本,而是创建了自己的语言来执行配置和服务启动。
Android 初始化语言基于五个语句类别:
-
动作
-
命令
-
服务
-
选项
-
导入
每个语句都是面向行的,并且基于特定的标记,由空白字符分隔。注释行以 # 符号开头。
动作
动作是一系列与特定触发器绑定的命令,用于在特定时刻执行特定动作。当发生期望的事件时,动作被放入执行队列,准备执行。
这个片段展示了动作语句的一个示例:
on <trigger> [&& <trigger>]*
<command>
<command>
<command>
动作具有独特的名称。如果在同一文件中创建了具有相同名称的第二个动作,则其命令集将添加到第一个动作的命令集中,作为一个单独的动作进行设置和执行。
服务
服务是初始化序列在引导过程中执行的程序。如果需要这些服务保持运行状态,则可以对其进行监控和重启。以下片段展示了服务语句的一个示例:
service <name> <pathname> [ <argument> ]*
<option>
<option>
...
服务具有独特的名称。如果在同一文件中存在一个非唯一名称的服务,则只有第一个被视为有效;第二个将被忽略,并且开发者会收到错误消息。
选项
选项语句与服务相关联。它们旨在影响 init 如何以及何时管理特定服务。
Android 提供了相当多的可能选项语句:
-
critical: 这指定了一个设备关键服务。服务将被持续监控,如果它在四分钟内死亡超过四次,设备将在恢复模式下重启。 -
disabled:此服务将处于默认停止状态。init 不会启动它。禁用的服务只能通过手动启动,指定其名称。 -
setenv <name> <value>: 使用name和value设置环境变量。 -
socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]: 此命令创建一个 Unix 套接字,指定name(/dev/socket/<name>),并为指定的服务提供文件描述符。<type>指定套接字类型:dgram、stream或seqpacket。默认的user和group是 0。seclabel指定创建套接字的 SELinx 安全上下文。 -
user <username>: 在服务执行之前更改用户名。默认用户名是root。 -
group <groupname> [ <groupname> ]*: 在服务执行之前更改组名。 -
seclabel <seclabel>: 在启动服务之前更改 SELinux 级别。 -
oneshot: 这将禁用服务监控,当服务终止时,它不会被重启。 -
class <name>: 这指定了一个服务类。可以同时启动或停止服务类。未指定class值的服务的将关联到默认类。 -
onrestart: 当服务重启时执行一个命令。 -
writepid <file...>: 当服务分叉时,此选项将进程 ID(PID)写入指定的文件。
触发器
触发器指定了一个必须满足的条件,以便执行特定的动作。它们可以是事件触发器或属性触发器。事件触发器可以通过触发命令或QueueEventTrigger()函数来触发。示例事件触发器有boot和late-init。属性触发器可以在观察到的属性值改变时触发。每个动作可以有多个属性触发器,但只有一个事件触发器;以下代码为例:
on boot && property:a=b
当boot事件被触发且属性a等于b时,此动作将被执行。
命令
命令语句指定了在引导序列中可以执行的命令,将其放置在init.rc文件中。大多数这些命令是常见的 Linux 系统命令。列表相当广泛。让我们详细看看它们:
-
bootchart_init: 如果配置正确,这将启动 bootchart。Bootchart 是一个性能监控器,可以提供有关设备引导性能的见解。 -
chmod <octal-mode-permissions> <filename>: 这更改文件权限。 -
chown <owner> <group> <filename>: 更改指定文件的拥有者和组。 -
class_start <serviceclass>: 这通过类名启动指定的服务。 -
class_stop <serviceclass>: 停止并禁用由其类名指定的服务。 -
class_reset <serviceclass>: 停止由其类名指定的服务。它不会禁用服务。 -
copy <src> <dst>: 将源文件复制到新的目标文件。 -
domainname <name>: 设置域名。 -
enable <servicename>: 通过名称启动一个服务。如果该服务已经排队等待启动,则立即启动该服务。 -
exec [<seclabel>[<user>[<group> ]* ]] -- <command> [ <argument> ]*: 分叉并执行指定的命令。执行是阻塞的:在此期间不能执行其他命令。 -
export <name> <value>: 设置并导出一个环境变量。 -
hostname <name>: 设置主机名。 -
ifup <interface>: 启用指定的网络接口。 -
insmod <path>: 加载指定的内核模块。 -
load_all_props: 加载所有系统属性。 -
load_persist_props: 在成功解密/data分区后,加载持久属性。 -
loglevel <level>: 设置内核日志级别。 -
mkdir <path> [mode] [owner] [group]: 创建具有指定名称、权限、所有者和组的文件夹。默认权限为 755,所有者和组为 root。 -
mount_all <fstab>: 将fstab文件中的所有分区挂载。 -
mount <type> <device> <dir> [ <flag> ]* [<options>]: 在特定文件夹中挂载特定设备。一些挂载标志可用:rw、ro、remount、noatime以及所有常见的 Linux 挂载标志。 -
powerctl: 用于响应sys.powerctl系统参数的变化,对于重启路由的实现至关重要。 -
restart <service>: 重新启动指定的服务。 -
rm <filename>: 删除指定的文件。 -
rmdir <foldername>: 删除指定的文件夹。 -
setpropr <name> <value>: 使用指定的值设置具有指定名称的系统属性。 -
start <service>: 启动一个服务。 -
stop <service>: 停止一个服务。 -
swapon_all <fstab>: 启用fstab文件中指定的交换分区。 -
symlink <target> <path>: 从目标文件到目标路径创建一个符号链接。 -
sysclktz <mins_west_of_gtm>: 设置系统时钟。 -
trigger <event>: 以编程方式触发指定的事件。 -
wait <filename > [ <timeout> ]: 监视路径以等待文件出现。可以指定超时时间。如果没有指定,默认超时值为 5 秒。 -
write <filename> <content>: 将指定的内容写入指定的文件。如果文件不存在,则创建文件。如果文件已存在,则不会追加内容,但会覆盖整个文件。
导入
导入指定了当前文件中需要的所有外部文件并将它们导入:
import <path>
之前的代码片段是一个示例,说明了当前的 init 脚本如何扩展,导入外部 init 脚本。path 可以是一个单个文件,甚至是一个文件夹。如果 path 是一个文件夹,那么该文件夹第一级中的所有文件都将被导入。该命令不会递归地作用于文件夹:嵌套文件夹必须逐个通过程序导入。
摘要
在本章中,你学习了如何获取适用于你的设备的 Linux 内核,如何设置你的主机 PC 以正确构建自定义内核,如何向内核添加新功能,构建它,打包它,并将其闪存到你的设备上。
你学习了 Android 启动序列的工作原理以及如何操作 init 脚本来自定义启动序列。
在下一章中,你将学习如何制作你的第一个自定义 ROM,如何给你的设备 root 权限,以及如何替换恢复分区。
第六章. “制作”你的第一个 ROM
在第五章“定制内核和引导序列”中,我们进行了一次令人惊叹的 Linux 内核之旅——现在你知道了如何为你的设备获取正确的版本以及如何构建它。我们定制和构建你自己的内核版本,针对你的设备——我们添加了新的硬件驱动程序,并移除了那些不必要的。你最终了解了引导序列。
在本章中,我们将进入修改的世界,并将继续前进,制作你的第一个定制 ROM。你将学习如何设置系统和如何创建一个定制 ROM。我们将概述最受欢迎的 ROM,以及你需要用到的所有工具以及如何使用它们。
本章将涵盖以下主题:
-
Android 修改的历史(Cyanogenmod)
-
定制恢复
-
根权限
-
厨房和其他工具
定制 ROM 的历史
首先要明确——什么是“定制 ROM”?
大多数 Android 设备都配备了所谓的NAND 存储器。NAND 存储器是一种特殊的闪存。闪存基于晶体管,而不是像旧硬盘那样的旋转磁盘。这种类型的内存完全由电管理——它可以写入、擦除,并且可以无限期地存储数据(非易失性)。了解这一点后,我们可能会认为在 Android 上可以写入任何东西。好吧,并不完全是这样!
缩写 ROM 代表只读存储器。这种类型的内存常用于嵌入式系统,用于安全地存储所有属于核心系统的文件。为了确保尽可能高的系统完整性,开发者必须确保核心系统在设备重启和可能的故障中保持完整。这就是为什么核心系统存储在只能写入一次的内存中——确切地说,就是只读存储器。随着时间的推移,Android 黑客社区采用了这个缩写并进行了转变。如今,当我们说定制 ROM 时,我们只是在说“这是我针对这个特定设备的自定义 Android 系统”,这就是我们在以下页面中将使用的含义。
对于 Linux 内核来说,Android 是目前开发中最为流行的开源项目之一。免费使用且可定制,被数百万用户使用,Android 是数百个定制操作系统的基本元素——其中大多数是实验性的,一些是为特定场景修复特定错误的定制版本,还有一些是原始系统的优化版本。
在最初,修改社区非常分散——许多孤独的狼,在他们的黑暗房间里进行黑客攻击。随着时间的推移,他们中的大多数人都汇聚到了更社交的环境中,在论坛和社区中结合他们的努力,组建了修改团队,为用户提供更好、更可靠的 ROM。
在第五章“自定义内核和引导序列”中,我们看到了如何使用源代码创建一个自定义版本的 Android。我们能够彻底改变原始系统以创建我们的版本,完美地满足我们的需求,那么关于修改的所有这些炒作是什么?为什么我们不能直接获取源代码并自定义我们的系统?事实是,不幸的是,谷歌就像大海捞针。大多数其他制造商在开源游戏中玩得略有不同,由于缺乏提供的源代码,因此不可能从头开始重建系统。
幸运的是,我们可以通过遵循不同的路径来实现 Android 定制——直接进入系统内存分区,反编译组件,并进行定制,或者所谓的表面修改。
在 Linux 内核领域,游戏规则完全不同。正如你可能记得的,Android 和 Linux 内核有不同的许可证——Android 在 Apache 许可证 v2 下分发,而 Linux 内核在 GPL 许可证下分发。GPL 许可证对修改和再分发的限制更严格,制造商很难保持内核的机密性。这就是为什么 Linux 内核总是可用,修改者可以添加、删除和改进他们想要的任何方面——新的驱动程序、改进的电源管理、改进的 CPU 管理,等等。
当你审视整个自定义 ROM 概念时,你会发现你似乎每天都在看到自定义 ROM——制造商的 ROM。如果我们认为真正纯净的 Android 系统是 Nexus 设备上搭载的系统,我们就会意识到制造商是第一个修改者,将原始系统变成了经常完全不同的东西。想想三星或 HTC 的定制 UI。这些都是对 UI 的巨大修改。想想那些带有 AM/FM 收音机的设备——再次,严重的定制。一些制造商在过去的几年中进行了如此多的定制,以至于他们的设备最终甚至与 Google Play 商店不兼容。
在接下来的几页中,我们将概述最受欢迎的自定义 ROM,以了解为什么它们如此受到高级用户的喜爱。
Cyanogenmod
毫无疑问,Cyanogenmod 是最受欢迎的 Android 定制 ROM 之一。它是其中最古老的之一,它带来了在官方 Android 系统中找不到的功能和性能:

从一开始,就在 Android 开源代码的第一个公开发布之后,Cyanogen 团队就开始将最新的 Android 版本回溯到旧设备上。他们基本上克服了制造商的商业决策,即让旧设备保留旧的 Android 版本,并努力让所谓的旧设备重获新生。
在这些年中,Cyanogenmod 团队添加和调整了大量的功能,这种方法吸引了成千上万的用户。这些改进如此之好,以至于官方的 Google Android 团队经常将它们合并到官方的 Android 源代码库中,体现了真正的开源精神。
如前所述,Cyanogenmod 团队并非从零开始这个项目。他们使用了 Android 开源项目并对其进行了增强。与其他许多定制者采用不同的方法,他们决定整个项目必须以开源代码的形式提供,让每个人都能享受所有功能,从源代码中学习,并为项目本身做出贡献。多年来,社区显著增长,大量的博客文章、教程和实用指南涌入网络空间,使 Cyanogenmod 成为目前最受欢迎的自定义 ROM 之一。
这是 Cyanogenmod 目前提供的最受欢迎的功能列表:
-
主题支持:整个系统 UI 可以使用用户创建的主题进行自定义,这些主题可以在系统运行时应用
-
FLAC 支持:无损音频编解码器(Free Lossless Audio Codec)是系统上可用的许多音频编解码器之一
-
更大的 APN(接入点网络)列表:随着时间的推移,添加了大量的不同 APN,使得在众多设备上快速设置互联网连接变得容易
-
OpenVPN 客户端:流行的 VPN 软件可用并准备好使用
-
丰富的关机菜单:关机菜单包含新的操作,如重启、恢复模式重启等
其他一些功能包括:
-
支持 Wi-Fi、蓝牙和 USB 共享网络连接
-
CPU 超频管理和系统级性能增强
-
软按钮的高级管理
-
系统通知菜单中的新切换按钮,如 GPS、蓝牙和 Wi-Fi
-
高级应用程序权限管理,为系统提供细致的安全保障
-
系统级图形增强
-
与任何其他从官方 Google 纯 Android 系统派生的 Android 系统相比,团队表示性能和可靠性有所提高
2013 年 4 月,Cyanogenmod 从社区项目转变为一家真正的公司。尽管如此,开源的本质仍然是公司的主要核心价值观之一。到目前为止,它有 17 名全职员工在项目上工作。在过去三年中,他们从第三方合作伙伴那里收到了一些捐赠,如 Benchmark Capital 和 Redpoint Ventures,推动了更简单的 Cyanogenmod 安装过程的开发。
2014 年,Cyanogenmod 宣布与智能手机制造商 OnePlus 建立合作伙伴关系,将预装 Cyanogenmod 的设备进行分发。根据他们的分析,Cyanogenmod 目前被 5000 万台设备使用。
构建 Cyanogenmod
受 Google AOSP 的启发,Cyanogenmod 提供了一个官方网站,您可以从该网站下载项目源代码并访问支持论坛:www.cyanogenmod.org。
网站还提供了一个支持的所有设备的完整列表。与仅正式支持 Nexus 设备的 Google AOSP 不同,Cyanogenmod 适用于数十种不同的设备。
Cyanogenmod 构建系统与你在前几章中已经掌握的完全相同。了解这一点后,我们将它留作练习,下载并构建你自己的 Cyanogenmod 版本,以完全理解 Android AOSP 可以定制和改进到何种程度。
安装预构建版本
作为开源项目,你可以从源代码构建 Cyanogen。如果你想找到一个更快的解决方案,Cyanogenmod 为众多设备提供了预构建的系统安装版本。只需检查网站,寻找你的设备——很可能它就在支持设备列表中。
一旦你发现你的设备受到支持,你可以选择许多版本中的一个。发布周期与 Google 的非常不同。整个 Cyanogenmod 世界中最冒险的特性之一是夜间构建——每晚,一个自动系统都会使用源代码仓库的最新贡献启动一个新的构建。这些版本很复杂,必须被视为不稳定,但将包含开发团队每天添加到系统中的所有新功能——仅限勇敢者!
除了不同的发布周期外,Cyanogenmod 还使用不同的版本命名约定。团队使用标签来指定 ROM 的不同版本:
-
夜间版:如前所述。
-
实验版:这是目前正在测试的版本。
-
M 快照,或里程碑快照:这比夜间版更稳定,但仍被视为不稳定。
-
发布候选版:这是达到稳定状态前的最后一步。这是第一个建议在日常设备上使用的版本。
-
稳定版:这是最终状态,面向所有用户。
Android Open Kang Project
Android Open Kang Project,简称 AOKP,是一个于 2011 年诞生的开源项目,旨在为智能手机和平板电脑提供官方 Google Android 的替代品:

如你所想,Kang 团队并没有从头开始创建系统。他们像 Cyanogenmod 一样,以 Google 的 Android 开源项目作为起点。这个特定的 Android 版本针对高端智能手机和平板电脑,并改进了一些方面,使系统更高效和可定制。以下是它的主要优点,这也是越来越多的用户决定切换到 AOKP 的原因。
用户喜爱的一个方面是 AOKP 团队专注于使系统尽可能轻量。他们移除了所有不必要的应用,基本上只留下了官方的 Google 应用,以创建尽可能小的系统。
如今,大多数智能手机和平板电脑都包含大量的美学
这些功能可能会减慢系统速度,并且对视觉影响较大。这类应用程序被称为冗余软件,通常是预装的系统应用程序,无法从系统中移除。AOKP 将消除这些无用应用程序作为其主要目标之一。
Kang 团队非常努力地工作,以确保用户系统的最大定制化水平。AOKP 提供了一个ROM 控制菜单来定制系统的许多方面,从 UI 定制到行为定制。在手势管理领域投入了大量精力,其中最酷的功能之一是可以通过手势启动任何所需的程序,而不是点击图标。
与 Cyanogenmod 一样,AOKP 也在他们的网站上提供了大量的文档和下载,网址为aokp.co。同样,你可以查看源代码自行编译,或者尝试已经编译好的版本。
这里是一个快速列表,列出了你可以在 AOKP 中找到的亮点:
-
振动模式:每个联系人都可以关联到特定的振动模式。
-
导航环:Android 锁屏可以通过用户选择的程序进行定制,以便在设备锁定的情况下快速访问。
-
LED 控制:可以根据颜色、闪烁和持续时间来定制系统 LED 的行为,以创建适用于自定义场景的通知。
-
自定义开关:通知区域可以通过不同的切换按钮进行定制,以创建适合你需求的完美设置。
下面的图片显示了实际系统中的两个截图:
-
第一部分展示了如何自定义导航环
-
第二部分展示了如何自定义振动模式
![img/epub_36702041_91.jpeg]()
安装 AOKP
AOKP 的版本与 Google 和 Cyanogenmod 不同。AOKP 只提供两个版本:
-
夜间版本
-
里程碑
夜间版本相当于 Cyanogenmod 的夜间构建。实际上,这只是 AOKP 构建系统每天自动生成的一个构建。这应该被视为高度不稳定,仅用于测试目的。
相比之下,里程碑是稳定的构建版本,旨在用于稳定的日常使用。
为了保持社区的参与度,Kang 团队创建了AOKP PUSH应用程序,该应用程序可以保持手机更新到新构建,并且还包括安装系统更新的功能。最后值得一提的是,与 Cyanogenmod 一样,AOKP 是完全免费且开放接受贡献的。
小型 ROM
在前面的章节中,我们看到了目前市场上最流行的两种定制 ROM 的概述,这些 ROM 适用于 Android 智能手机和平板电脑。正如你可以想象的那样,这仅仅是冰山一角——多年来,已经开发并发布了数十种不同的定制 ROM。其中许多针对特定场景,以解决特定问题或满足用户的具体需求,以他们自己的方式改进 Android 系统。大多数不是从头开始构建的,而是基于已经可用的系统,这些系统经过定制和重新分发。
大多数可用的定制 ROM 针对特定设备,以解决特定设备的问题并提高可用性和性能。DroniX(由本书作者创建的项目,针对特定设备,华为 Ideos U8150,当时一款非常流行的低端设备。开发团队专注于性能,从 Ideos CPU 中榨取了每一个可用的兆赫兹。由于有内核源代码可用,我们能够提高 CPU 频率和控制器。更好的电源管理意味着更好的电池管理,性能提升并增加了电池寿命。
总是小心尝试定制 ROM。其中一些可能非常极端,可能对您的设备造成危险。这是不幸的,但这是一个真实的情况。没有魔法可以烹饪定制 ROM,而且有很多事情可能会出错。例如,极端超频是危险的,明智的用户应该不相信试图销售这些功能的 ROM。在 Android 上进行实验可能很有趣、令人满意和具有挑战性,但必须具备知识和智慧。
我们无法列出野外所有可用的定制 ROM。我们能做的是指明正确的方向:www.xda-developers.com/。这可能是获取最新消息和最新疯狂事物的最著名论坛。
OEM 定制概述
即使它们通常不被认为是定制 ROM,制造商分发的所有 Android 变体都可以被认为是进行了大量定制。我们每天都在见证这些——每次你看到三星设备时,你就知道它不是纯 Android。
从系统启动器到设置菜单,这些系统的每一个组件都被 OEM 大量定制,与官方谷歌版本相去甚远。在某些情况下,系统差异如此之大,以至于普通用户甚至不知道他正在使用相同的 Android 5 系统,例如。
这是一个最受欢迎的 OEM 定制列表,以展示系统如何被修改,以及不同的制造商如何使相同的 Android 版本在设备上看起来如此不同。
三星 – TouchWiz
TouchWiz 是一个针对触摸界面的图形界面,由三星及其技术合作伙伴开发。通常,它被错误地定义为“定制操作系统”,但从技术角度来说,它只是对 Android UI 的重度定制。
TouchWiz 的第一个版本于 2010 年发布,针对 Android 2.1 和三星为其智能手机和平板电脑创建的操作系统 BADA。当前版本是 TouchWiz 5,我们可以在多年中找到许多改进。最初,TouchWiz 只是一个不同的 UI。今天,它是一系列定制系统应用、自定义 UI 小部件以及许多新的设置和功能,如声音配置文件、电源管理、开关等。以下截图展示了主屏幕和应用程序抽屉:

华为 EMUI
在三星工作的启发下,华为也为其设备提供了自己的 Android UI 版本。与三星一样,他们从定制 UI 开始,并添加了许多功能,如主题定制——图标、颜色、字体和锁屏。通知区域也进行了定制和改进。
最有用的新功能之一无疑是高级电源管理。它提供了三种可能的设置:超长续航、智能和普通。超长续航是极端的设置——一键操作,你可以关闭除了最基本的传感器之外的所有传感器,旨在实现尽可能长的电池寿命。智能尝试尽可能自动管理电力使用。普通模式则专注于性能——电池寿命不会很长,但设备将以全速运行。
下图展示了华为 EMUI 的主屏幕:

HTC Sense
2009 年,HTC 发布了其智能手机定制的第一个 UI 版本。它针对 Android 和 Windows Mobile,提供共享的图形用户界面,以避免用户混淆。
在 HTC 中最受欢迎的功能是一大堆主屏幕小部件,但还有其他同样有趣的功能,例如用于设备被盗时使用的追踪系统。该系统允许用户远程操作设备以定位它或擦除内存,或者简单地锁定它。甚至可以在锁屏上显示自定义消息,包括地址或奖励以重新获得设备。
下图展示了 HTC Sense 7 的主屏幕:

LG Optimus UI
LG,和其他厂商一样,提供定制的用户界面——用户可以选择系统图标、颜色和一些自定义设置。一个有趣的功能是语音命令拍照以及从连拍中选择最佳照片的能力。
下图展示了主屏幕和自定义通知区域:

小米 MIUI
这绝对是最重量级的定制系统,它有一个之前版本所没有的特定功能——它是开源的!小米从 Android 2.3.7 和 Cyanogenmod 7 开始着手开发 MIUI——这两个是系统的核心。多年来,他们创建了一个定制的 ROM,它不仅仅是一个定制的用户界面,还添加了越来越多的功能。
2011 年,小米进入市场,从系统定制者转变为设备制造商,推出了高端、低成本的设备,并配备了其 MIUI 系统。
下图展示了 MIUI 主屏幕和应用程序商店:

很不幸,这已经成为一种流行趋势——一种简单的方法用于品牌建设和确保客户忠诚度,但这并不总是推荐的做法。
有些制造商更喜欢在设备上预装纯净的 Android 系统——例如摩托罗拉。摩托罗拉的品牌策略是仅添加几款 由摩托罗拉提供 的应用程序。这些通常是实用程序应用程序,旨在丰富用户体验同时保持系统简洁。
摩托罗拉的策略还有一个很大的优点——与谷歌原始系统非常接近的系统意味着更新更快。每次谷歌发布一个新的 Android 版本,摩托罗拉设备在几天内也会收到系统更新。这对大多数其他制造商来说非常不寻常,某种程度上注定要停留在旧的 Android 版本上,因为更新这样一个高度定制的系统需要大量的工作。
Android 恢复的概述
整个 Android 架构中最重要的部分之一是 Recovery 分区。在嵌入式系统中,Recovery 分区非常常见,我们已经在之前的章节中对其进行了概述。正如我们所知,所谓的 Recovery 是一个最小运行时系统,完全与主 Android 系统解耦,并且完全自给自足。其主要目标是保证系统完整性,并提供必要的工具来解决常见的轻微问题,并恢复一个正常工作的系统。
使用 Android 纯净 Recovery,我们可以:
-
更新 Android 系统
-
擦除数据分区和缓存分区
如果我们想要将设备恢复到出厂默认设置,例如为了拥有一个干净的系统来开始实验特定的事情,或者如果我们只是想出售它,擦除数据和缓存分区是一种常见的做法。
深入 Android 恢复
Android Recovery 系统是完全独立的。这意味着无论主 Android 系统发生什么,recovery 总是能够恢复一个正常工作的系统。
为了达到这种级别的弹性,recovery 包含了自己的 Linux 内核和自己的 rootfs。以下截图显示了 recovery 实际上位于 Android 系统附近,但完全独立:

之前的截图显示了如何通过BootLoader访问recovery。BootLoader无法决定当前的引导序列将以运行recovery还是运行 Android 系统结束。
关闭设备时,可以通过按键组合进入恢复模式。以我们的参考设备 Google Nexus 6 为例,您可以采取以下步骤:
-
同时按下音量下、音量上和电源按钮。
-
当
Fastboot Mode菜单出现时,释放所有按钮。 -
使用音量按钮,直到屏幕上方的部分显示
Recovery Mode文本。 -
按电源键选择
Recovery Mode——之后您将看到一个倒置的 Android 图标。 -
按住电源按钮,然后按一次音量上按钮。
一旦进入恢复主屏幕,您可以使用音量按钮进行导航,并使用电源按钮确认您的选择。
您在恢复菜单中找到的选项可能会有所不同,但 Android 原生的recovery肯定会提供以下选项:
-
立即重启系统:此选项将重启系统。
-
从 ADB 应用更新:Android 调试桥接可以从主机计算机上传官方 Google 系统更新。由于恢复中实施的安全措施以保证系统完整性,只能上传和通过这种方式应用经过认证的更新。
-
擦除缓存分区:此选项将擦除缓存分区。此分区通常包含系统的临时数据和应用程序缓存数据。删除此文件将释放相当多的磁盘空间,而不会丢失用户数据或应用。
-
擦除数据/恢复出厂设置:此选项将擦除易失性内存并恢复原始出厂系统。所有严格与系统无关的内容都将被删除:视频、音乐、文档、用户应用等。缓存分区也将被清除。
以下截图显示了原装 Android recovery:

安装替代恢复
就像整个 Android 系统一样,recovery源代码也是可供研究和修改的,而且多年来,Android 社区已经开发了可以替代 Android 原装recovery的替代方案。
所有这些替代方案旨在改进和添加更多功能到原装恢复。最常见的特点是:
-
保存和恢复系统备份的能力:NANDroid 对于实验自定义系统和冒险配置极为有用
-
安装自定义 ROM 的能力:从自定义 ROM 开发者的角度来看,这可能是添加功能中最重要的一个
-
增强的 UI 和 UXD:这些自定义恢复中的一些提供了对触摸屏的支持,而不是默认的音量/电源按钮导航
最受欢迎的恢复替代方案有:
-
Clockworkmod
-
4EXT
-
Amon Ra Recovery
-
Team Win Recovery Project (TWRP)
它们中的每一个在某种程度上都是不同的——外观、高级功能等,但它们都为高级用户提供了安装定制 ROM 的明确方式。
Clockworkmod
这无疑是游戏中最受欢迎的定制恢复之一。它通常被称为CWM,由 Koushik "Koush" Dutta 开发。他从古老的 Android 2.1 恢复源代码开始,从那时起,他一直在不断添加功能。
其中一个主要功能是 NANDroid 备份,它允许用户安全地保存和恢复整个系统结构。另一个有趣的功能是能够通过 ADB 从计算机连接到恢复 shell。一个至关重要的功能是使用非官方更新包更新系统的能力。与原装恢复不同,Clockworkmod 忽略了所有签名证书,因为它知道只有高级用户才会尝试刷写定制的更新包。
可以通过 Google Play Store 分发的特定应用轻松安装 Clockworkmod 恢复,或者像我们将要看到的那样手动安装。
要在您信任的 Nexus 设备上手动安装它,您可以使用fastboot。按照以下步骤安装 Clockworkmod 恢复:
-
首先要做的事情是下载它。Clockworkmod 网站列出了所有支持的设备和特定的下载文件:
www.clockworkmod.com/rommanager。 -
一旦您有了文件,解压它,您将得到一个
.img文件。 -
现在,将您的设备置于
fastboot模式,正如我们在前面的章节中看到的,打开一个终端,并使用以下命令将.img文件刷入恢复分区:$~: sudo fastboot flash recovery recovery.img -
一旦安装了全新的恢复,您可以使用以下命令直接重启设备到恢复模式:
$~: sudo fastboot reboot recovery
从现在开始,我们可以安装定制 ROM 或执行完整系统备份。
由于该项目是开源的,您也可以从头开始重新编译 Clockworkmod 源代码。您还可以在之前段落中讨论过的定制 Cyanogenmod ROM 中找到自定义恢复源代码。从源代码构建 Cyanogenmod 遵循您已经遵循的构建官方 Android 的相同步骤:构建系统和构建设置是相同的。应用与纯 Android 相同的知识,您可以轻松创建 Cyanogenmod 系统镜像和 Clockworkmod 定制恢复。
下面是 Clockworkmod 用户界面的截图:

TWRP – Team Win Recovery Project
Clockworkmod 恢复的一个替代方案是 TWRP,代表 Team Win Recovery Project。这个恢复模式最重要的功能之一是支持触摸屏。
以这种方式,您可以直接通过屏幕与恢复模式进行交互,就像您通常使用 Android 一样,这非常方便,尤其是如果我们将其与其他所有恢复模式中使用的音量键进行比较。图形界面相当可用,有大按钮显示所有各种选项(它们与 Clockwork 模式中的按钮非常相似)。使用 TWRP,您可以安装非官方的 ROM,也可以执行完整的系统备份。
该项目始于 2011 年 7 月 30 日,是一个开源项目——在这里,您可以为您的设备下载二进制文件,或者从源代码重新编译。
您可以在官方网站上找到更多信息:teamw.in/。
这里是 TWRP 的一些截图:

使用 ADB 连接到恢复 shell
自定义恢复可以使用它们的标准 UI 操作,就像我们看到的,以及使用 ADB 连接。这个功能在标准恢复模式中不可用,在我们的实验中将会非常有用。
一旦安装了自定义恢复模式,启动终端并运行以下命令:
~$: adb devices
ADB 将列出所有可用的设备,如下面的截图所示:

知道只有一个设备,我们可以简单地使用以下命令来连接到恢复 shell:
~$: adb shell
您将看到一个#符号,这表明您作为root用户拥有管理员权限。作为root用户,您有机会执行高级任务,例如以读/写模式挂载system/目录,并添加或删除任何您想要的文件,而无需启动整个 Android 系统。
设备权限
正如我们在前面的章节中看到的,Android 基于 Linux,因此它也继承了与用户权限相关的部分。与标准的 Linux 系统一样,Android 也通过组和用户来管理一切。在默认配置中,无法获得管理员(root)访问权限,以防止对系统进行篡改。此外,拥有对整个操作系统的访问权限,很容易意外或故意地损坏系统本身(例如,使用病毒窃取用户数据)。
每个 Android 应用在系统上安装时,都会生成一个新的用户和组,并且应用间的通信根据 Android SDK 的约束和协议进行。然而,有时拥有对设备的完全控制权是有用的,例如,当安装管理 CPU 频率和 CPU 管理器的应用时。
现在我们来看看如何获得 root 访问权限以及 root 设备的影响。
Root 访问
Root 访问权限允许安装了 Android OS 的智能手机、平板电脑和其他设备的用户获得对整个 Android 操作系统的特权访问,也称为 root 访问权限。正如我们之前提到的,Android 使用 Linux 内核,因此获得 root 访问权限与获得常规 Linux 或类 Unix 操作系统(如 FreeBSD 或 Mac OS X)的管理员(超级用户)访问非常相似。
通常,获得 root 访问权限的原因是为了克服硬件制造商对设备施加的限制。作为 root 用户,你有能力修改或替换系统应用并更改设置。此外,你可以使用需要 root 权限的应用程序,这使你能够执行普通 Android 用户无法访问的操作。对设备进行 root 操作,即获得 root 访问权限,如果你想要完全删除设备操作系统并替换为另一个,可能更近期的版本,这也有帮助。
在接下来的段落中,我们将看到如何获得 root 访问权限,这是安装自定义 ROM 的关键前提条件。
SuperSu
要在 Android 应用程序中使用 root 权限,一位名叫 Chainfir Jorrit Jongma 的独立开发者开发了一个库,它允许你在应用程序中使用这些权限,因此可以执行 root 级别的操作。所有这些都是开源的,你可以在开发者的官方网站上探索有关 API 的文档:su.chainfire.eu。
如果你想查看库源代码,你可以在以下位置找到它(并贡献):github.com/Chainfire/libsuperuser。
获得 root 访问权限
现在是时候看看如何在我们的设备上实际获得 root 权限了。不幸的是,这并不简单,获得设备 root 权限的方法有很多种。每种设备都有其独特之处,因此需要执行不同的步骤来获得 root 权限。一般来说,我们可以这样说,如果有可能安装恢复模式,那么也有可能安装成为 root 所需的所有必要软件。我们只需要将正确的文件复制到默认情况下以只读方式挂载的系统分区,这样我们就可以通过创建一个临时的系统分区使用源文件来访问它,或者——在我们没有 Android 源代码的情况下——通过使用我们之前描述的某个自定义恢复模式将分区挂载为读写模式。
到目前为止,我们还没有讨论过修改设备上现有软件的法律问题。一般来说,将自定义 ROM 安装到我们的设备上并不违法,但可能会使设备保修失效。至于 Nexus 设备,没有任何问题;它们是为了软件开发目的而销售的,因此产品保修不是与软件相关,而是与硬件相关。
Chef 工具包
本书的主要目标之一是帮助您实现自己版本的 ROM 定制。在修改者的词典中,为了制作自己的 ROM 定制而修改 Android 版本的行为通常用动词“to cook”和名词“kitchen”来描述。
“制作自己的 ROM”意味着修改设备上安装的原始 Android 版本,目的是创建一个新的版本。
因此,所有可能帮助简化 ROM 定制开发的工具都被称为“Chef toolkit”。
如前几章所述,从源代码创建自己的 ROM 版本确实是可能的,但并非总是如此,因为一些设备制造商没有发布他们的源代码。在所有这些情况下,我们需要在系统分区上操作,通常直接在构建内部核心的二进制文件上操作,包括应用程序框架和文件系统实用工具。
在接下来的段落中,我们将学习如何从二进制镜像开始制作 ROM,从环境分析开始,了解将帮助我们完成第一个 ROM 定制的开发工具。
准备环境
在我们开始开发 ROM 之前,我们肯定需要在电脑上准备一个合适的环境。Android 基本上可以与所有最新的操作系统一起使用,从 Windows 到 Linux,再到 OS X。
我们总是提到 Ubuntu,就像我们在前几章中处理从源代码编译 Android 时做的那样。因此,您只需要一台装有最新版 Ubuntu 的电脑即可开始。除此之外,我们还建议安装一个好的开发者文本编辑器——它可以是命令行的 VIM,或者图形编辑器,如 ATOM、SublimeText 等。我们将主要在控制台工作,使用不同的脚本和工具来完成我们的第一个定制 ROM。
Android kitchen
烹饪大师最重要的工具无疑是“Kitchen”。虽然我们借鉴了烹饪世界的类比,但实际上我们关注的是我们第一个 Android 定制的准备工作——第一步是获取系统二进制镜像。
我们将通常使用的工具集称为“Android Kitchen”,例如在 shell 中使用的脚本,这些工具帮助开发者执行自动化任务,如解压缩和编辑构建 ROM 的系统镜像,反编译 APK 包,有时向 ROM 添加 root 权限等。
当然,网上存在许多不同的厨房,每个都有其独特的特点。我们将研究其中的一些,并尝试执行简单的操作,以便将我们的第一个定制 ROM 准备好,以便将其刷入我们的设备。
最受欢迎的Android 厨房之一是dsixda。该项目正式“退役”,但它已被许多用户分叉,并且开发仍在继续。它是开源的,你可以从github.com/dsixda/Android-Kitchen下载它或分叉它并为项目做出贡献。
dsixda 的厨房基于一系列Bash脚本和工具,提供了一种简单的方法来执行最常见的烹饪操作:
-
添加 Busybox
-
添加 root 权限
-
自定义启动屏幕
这些只是其控制台菜单中可用的可能操作中的一些。这个厨房与 Windows、Linux 和 OS X 兼容。我们将使用我们信任的 Ubuntu。一旦你下载了厨房(github.com/dsixda/Android-Kitchen/archive/0.224.zip),将其解压缩到一个文件夹中,进入该文件夹,并运行以下命令:
$: ./menu
此命令将启动主菜单,如下面的截图所示:

dsixda 厨房操作两个特定的分区——系统和 boot,分别压缩在system.img和boot.img文件中。在以下章节中,我们将深入了解提取这些分区并对其进行自定义。
其他开发者的工具
当然,对于开发者来说,许多其他不同的工具可能会很有用,这完全取决于个人的具体需求。一个十六进制编辑器对于二进制图像的分析肯定非常有用,而简单的图形编辑软件在修改图标或其他图形方面以及为编译 Linux 内核和可能添加到 ROM 的 Android 应用程序准备整个环境时也会有所帮助。
我们通常准备环境,就像我们需要从源代码编译 Android 和 Linux 内核一样,这样我们肯定有所有必要的工具来构建我们的自定义 ROM。
使用 APKTool 操作 DEX 文件
在与 Android 系统一起工作时,需要操作 DEX 文件是很常见的。DEX 代表Dalvik 可执行文件,这些文件由 Android 虚拟机使用。为了轻松操作这些文件,你可以使用 Ryszard Wiśniewski 和 Connor Tumbleson 的 APKTool。这些工具是开源的,你可以在ibotpeaches.github.io/Apktool/下载它们。
APKTool 是用 Java 编写的,因此你需要一个 JVM 来使用它。一旦你有了 APKTool 的jar文件,打开一个终端并运行以下命令:
$: java –jar apktool_2.0.3.jar
如果需要,将版本替换为你的版本。以下截图显示了工具的初始帮助菜单:

APKTool 基于另外两个工具——smali和baksmali,用于文件的汇编和反汇编。它需要一个初始设置来正常工作:framework-res.apk位置。你必须指定 APKTool 必须查找以获取此文件的位置。framework-res.apk是 Android 系统的一部分,可以从正在运行的 Android 设备中提取,使用我们信任的 ADB:
~$ adb pull /system/framework/framework-res.apk .
之前的命令将 APK 从 Android 设备复制到当前文件夹。一旦我们有了文件,我们就可以告诉 APKTool 在哪里找到它:
~$ apktool if {path to framework-res.apk}
现在一切配置就绪,我们可以尝试使用以下命令反编译和定制一个 APK:
~$ apktool d myapk.apk path_destination_decompilation
APK 内容将被放置在我们指定的目标文件夹中,我们可以编辑我们想要的任何文件。在我们所有的修改完成后,我们可以使用以下命令将文件夹重新压缩成一个 APK 文件:
~$ apktool b path_decompiled_files new_apk_mod.apk
一旦新的 APK 准备好,我们可以使用文件传输应用或使用我们在前几章中看到的ADB push将其复制到设备上。
烹饪我们的第一个 ROM
到目前为止,我们已经看到了从二进制系统镜像创建自定义 ROM 所需工具套件的概述。其中最重要的是“厨房”,它需要system.img和boot.img分区文件来正确地完成其工作。
如果你针对的是谷歌设备,这是一个简单的游戏。谷歌为其设备提供系统源代码,因此我们可以始终从源代码构建.img文件,就像我们在前几章中学到的那样。我们还可以从谷歌为 Android 系统每个新版本提供的官方系统安装包中获取.img文件。
如果你针对的不是 Nexus 设备,事情就会变得更加冒险。大多数情况下,你不会有系统源代码;通常甚至没有可下载的系统镜像。正如你将在下一节中看到的那样,总有办法获取拼图中最后一块,以创建我们的自定义 ROM。
收集原料
列表相当简短。你所需要的只是:
-
内核源代码,如果你想在核心级别定制系统
-
system.img -
boot.img
这两个.img文件可能由制造商提供,就像谷歌那样,或者可以从运行中的设备系统内存中手动转储。第一种情况是幸运的;第二种情况更高级,需要一点创造力。这是我们将要深入探讨的情况,因为,如果你足够幸运,有制造商的系统恢复文件,你只需要将其解压缩到一个文件夹中,你就可以得到你想要的.img文件。
转储系统分区
要创建系统内存的转储,你需要以 root 权限访问系统。正如我们已经知道的,有几种方法可以获得 root 权限——设备特定的 rooting、安装自定义恢复等。选择你喜欢的技术。
一旦你有了 root 权限,打开一个终端,使用以下命令连接到你的设备 shell:
~$: adb shell
系统将以 # 符号欢迎我们。我们现在可以继续分区转储。要获取分区结构的概览,你可以使用以下命令:
~ # cat /proc/partitions
下面的截图显示了标准 Google Nexus 6 设备的输出:

分区的数量几乎令人难以置信,但我们只需要关注系统分区和引导分区。我们知道我们感兴趣的分区就在所有列出的分区中。现在,我们必须找出这些分区中哪个实际上是 system/,哪个是 boot/。
使用以下命令显示物理分区与其在 Android 架构中角色的关系:
~ # ls /dev/block/platform/msm_sdcc.1/by-name
之前的命令将显示类似以下内容:
~ # . . .
~ # … recovery -> /dev/block/mmcblk0p35
~ # … system -> /dev/block/mmcblk0p41
~ # … boot -> /dev/block/mmcblk0p37
~ # … userdata -> /dev/block/mmcblk0p42
~ # . . .
如你所见,它显示了每个相关的分区及其角色。我们可以很容易地确定物理 mccblk0p41 将成为我们的 system.img,而 mmcblk0p37 将成为我们的 boot.img 文件。
我们将利用 /sdcard 分区来存储转储,并使用 dd 工具创建转储:
~ # dd if=/dev/block/mmcblk0p41 of=/sdcard/system.img
使用之前的命令,你正在将整个系统分区复制到 SD 卡上的一个单独的文件中。这个过程可能需要一些时间——请耐心等待。一旦你有了 system.img 文件,你就可以继续创建 boot.img 文件,使用以下命令:
~ # dd if=/dev/block/mmcblk0p37 of=/sdcard/boot.img
你现在有了创建自定义 ROM 的两个最重要的文件。让我们开始定制它们。
修改 Android 系统二进制镜像
按照以下步骤修改 Android 系统二进制镜像:
-
让我们从
system.img开始。首先,你需要将它复制到你的主机计算机上:~$ adb pull /sdcard/system.img . -
然后,你需要创建一个挂载点来挂载镜像到其中:
~$ mkdir system_mount_point -
现在,你可以将其挂载为一个常见的镜像文件:
~$ mount –o loop system.img system_mount_point
注意
在旧设备上,system.img 使用的文件系统是 yaffs。多年来,Android 系统迁移到了 ext4 文件系统,这在许多 Linux 系统中也非常常见。你很可能会现在使用 ext4 文件系统。
使用 cd 进入挂载点,并用 ls 列出文件,你将看到一个类似于下一张图片中的文件夹结构:

现在,你可以导航文件夹树并研究结构,移除或添加你想要的文件。一个值得研究的有意思的文件是 build.prop。这个文件包含有关系统和其配置的详细信息。它是一个非常硬件特定的文件,由于定制 Android 系统的无限可能性,但大多数变体都共享一些共同细节,例如内存堆大小、显示密度、设备代码名称、制造商名称、Android 框架 SDK 版本、Android 系统版本等等。甚至还有关于系统构建时间和通知及电话的默认铃声的信息。有许多小定制可以让你玩耍和实验。对于更重的修改,请继续阅读并准备好下一章即将到来的内容。
修改 Android 二进制引导镜像
如您从前面的章节中学到的,引导镜像与系统镜像略有不同。首先,它不包含我们可以在主机系统上挂载的文件系统:引导镜像必须被解压缩。
要解压缩引导镜像,您将使用上一页中Android Kitchen的特定菜单项。引导镜像是自定义 ROM 的关键组件:那里是内核所在,也是init脚本所在。这是放置必须在 Android 系统启动前应用的系统定制化的完美位置,例如 CPU 管理器的设置。
要开始与引导镜像工作,只需将文件复制到Kitchen文件夹中,打开菜单,并从菜单中选择您想要的选项:
-
修改 ROM 名称可以是完美的第一步
-
添加 root 权限
-
对 APK 文件进行
zipalign以加快读取和加载速度 -
使用
deodexk对 APK 文件进行解密,以便于文件操作,但代价是加载速度较慢
一旦您对修改满意,使用kitchen生成更新文件。这是一个.zip文件,可以通过自定义恢复刷写到设备上,代表您的第一个自定义 ROM——恭喜!
刷写我们的自定义 ROM
您已经有了.zip文件和您定制的系统分区,并且您非常兴奋地将它们刷写到您的设备上。
要刷写系统分区,我们可以使用fastboot。首先,您必须使用以下命令卸载分区本身:
~$ umount system_mount_point
在我们开始对系统分区进行实验之前,总是明智的做法是进行系统备份:
"做好准备。你永远不知道。"
现在,您可以将设备置于 Fastboot 模式,根据您设备的特定序列。以我们的参考设备 Google Nexus 6 为例,序列如下:
-
关机
-
同时按下音量增加、音量减少和电源按钮
-
当出现
Fastboot菜单时释放
设备现在已准备好接收新的系统分区。使用以下命令进行刷写:
~ $ fastboot flash system system.img
您全新的系统分区已就位!如果您的修改非常极端且具有冒险性,您可能会遇到引导循环——系统持续重启且永远不会结束引导序列。制造商分发的库存系统镜像或您自己的备份在这种情况下非常有用。
注意
如果您正在使用三星设备并且您拥有 Windows 系统,您可以查看Samsung Odin,这是一个用于刷写 ROM 和获取设备 root 权限的图形界面工具。
最后一步是刷写您使用kitchen生成的.zip文件。该文件根据特定的文件结构生成,并准备好传递到您的自定义recovery。recovery将其视为“系统更新”,即使它是一个全新的、定制的系统。
首先,以恢复模式重启您的系统。您可以通过按键序列或使用 ADB,使用以下命令来完成:
$: adb reboot recovery
一旦设备进入恢复模式,使用音量按钮进行导航,并选择从 ADB 应用更新。这将使设备进入等待模式。返回您的终端,并导航到使用kitchen生成的.zip文件。最后,将文件加载到设备上:
$: adb sideload filename.zip
恭喜!您的第一个定制 ROM 已经在您的设备上激活了。现在,返回去进一步定制它吧!
摘要
本章向我们介绍了什么是定制 ROM。我们从对目前存在的、最相关的项目的描述开始,并深入探讨了细节。我们还审视了一些非常重要的组件,例如Android Recovery,包括原生的和经过修改的。最后,就像我们在前面的章节中所做的那样,我们采取了一种实用方法,学习如何为 Android 定制准备合适的环境。我们还研究了通常用于执行此任务的不同工具,最后,我们通过创建一个定制 ROM 的简单示例来应用我们刚刚学到的概念。在下一章中,我们将更深入地探讨 ROM 的每一个方面,使用实际示例来展示如何定制和提升 ROM 的性能。
第七章. 定制你的个人安卓系统
在上一章中,你学习了最流行的自定义安卓系统。我们开始深入分析系统在修改过程中的各个部分,以有效地了解如何进行定制以及如何操作,掌握安卓修改工具集。
在本章中,我们将更进一步,深入到 ROM 的每一个细节,通过实际案例展示如何定制和提升 ROM 的性能。
本章的主要内容包括:
-
黑客安卓框架
-
将新的安卓应用程序添加到构建系统中
-
使用安卓源代码添加新的 Linux 原生应用程序,或编辑现有的二进制 ROM 镜像
-
优化系统以更好地支持自定义硬件,重点关注应用层和内核层。
接收空中更新 – OTA
每个安卓设备按设计都能在一段时间内接收更新。这些可以是系统更新——当新的安卓版本发布时,或者安全更新——当某些关键漏洞被修复且谷歌正在分发补丁时。一旦收到更新,每个设备都能按照所需程序解压缩并应用此更新。
这类更新被称为 OTA,或空中更新,因为它们可以通过安卓设备本身下载和应用,无需主机 PC 的支持。这些更新通常用于修复操作系统功能,在所谓的只读部分工作。没有任何用户应用会受到这些更新的影响——通过谷歌应用商店安装的应用完全安全。
当新的 OTA 可用时,安卓会异步通知你。大多数情况下,如果你连接到 Wi-Fi 网络且电池电量高于 50%,你将收到通知,以确保可能的快速下载和安全的更新过程。当有更新可用时,状态栏通知区域将出现新的系统通知。一旦点击通知,安卓将显示有关更新的详细信息,如下面的图片所示:

OTA 更新可以分为以下三类:
-
完整系统更新
-
增量系统更新
-
单个更新包
更新整个系统
如你所猜,这一系列的更新将整个系统提升到新版本。它们包含整个系统镜像,包括system、boot和recovery分区。
要安装这些更新,系统需要能够正确引导恢复系统,并简单地读取和应用更新文件。
即使是完整的系统更新,用户分区也不会受到影响,且不会删除任何应用或用户数据。
增量更新系统
这些更新比完整的系统更新要小一些,它们的目的是应用特定系统组件的补丁。由于针对特定版本的操作系统和特定版本的文件进行定制,因此这些更新不能随机应用于可用的设备。
为了强制执行此约束,在安装此类更新文件之前,系统会检查正确的文件版本以及更新所需的任何其他可能的要求。如果某些要求未得到满足,Android 会通过错误图标通知用户,并中止更新过程。
应用单个组件更新
OTA 更新包是一个标准的.zip文件,包含一个META-INF/com/Google/Android/update-binary文件。一旦 Android 验证了 ZIP 文件的签名,它就会在/tmp中解压缩文件并执行它。一些参数传递给命令行。这些是:
-
更新二进制 API 版本号
-
命令行文件描述符,用于与命令行通信,向 UI 发送进度更新
-
文件名
在update-binary文件所在的同一文件夹中,还有一个有趣的文件——updater-binary。此文件包含执行更新的操作序列。所有这些操作都使用 Google 为这项任务创建的定制领域特定语言(DSL)Edify来表示。正如开源世界中的惯例,Google 记录了有关此语言的所有信息,您可以在/bootable/recovery/edify中找到文档。
事实是,Recovery 可以执行所有名为update-library的静态链接二进制文件。利用这个机会,许多开发者更愿意使用他们更熟悉的语言来执行所有必要的操作以应用更新。
在接下来的几页中,我们将看到使用 Google 的 Edify 或自定义解决方案的两种可能场景的示例。
创建空中更新
Google 提供了大量的开发者工具来生成不同类型的 OTA。如果您想生成一个完整更新 OTA,需要以下两个步骤:
-
生成包含完整更新文件的 ZIP 文件
-
生成包含更新所需所有工具集的 OTA 包
要生成包含所选目标文件的zip文件,请导航到 AOSP 源代码的root文件夹并运行以下命令:
. build/envsetup.sh && lunch aosp-shamu
mkdir dist_output
make dist DIST_DIR=dist_output
如果过程成功,我们应该在dist_output目录中有一个包含目标文件的zip文件。例如,让我们尝试使用以下命令列出文件夹内容:
ls -l dist_output/*target_files*
现在我们应该看到一个.zip文件,其名称也将包含我们正在编译的目标名称。
到目前为止,您只需要生成包含更新所需所有文件的 OTA 包。在可用的工具中,有一个实用程序可以帮助我们通过以下命令完成此操作:
./build/tools/releasetools/ota_from_target_files \
dist_output/aosp_shamu-target_files-eng.esteban.zip ota_update.zip
如此所示,您将找到包含生成的 OTA 包和命令输出的屏幕:

现在我们已经有了准备安装到 开发设备 上的 OTA 包,因为默认的 OTA 是用 测试密钥 签名的。如果你想为用户提供可安装的 OTA 包,你需要使用 OTA 生成工具提供的特定选项,用你自己的 私有密钥 签名 OTA。
为了生成 增量 OTA,过程几乎相同,只是你还需要指出包含上一个 OTA 版本的 ZIP 文件。命令可能如下所示:
./build/tools/releasetools/ota_from_target_files \
-i PREVIOUS-aosp-shamu-target_files.zip \
dist_output/aosp-shamu-target_files.zip incremental_ota_update.zip
就像我们之前的例子一样,你会得到一个包含增量备份的 ZIP 文件。
最后,没有预定义的工具用于组成 Update OTA 包,因为这取决于我们决定通过更新脚本安装/更新什么,我们将在稍后详细研究。
OTA 内部结构
如前节所预期,OTA 包在其文件夹树中包含一个二进制文件:
META-INF/com/google/android/update-binary
这个二进制文件是由 Android 的构建系统生成的,位于 bootable/recovery/updater 文件夹中,并用于正确执行更新。
二进制文件包含内部例程和 Edify 脚本语言的解释器。这种语言支持一组特定命令,以便在不影响系统本身完整性的情况下正确执行系统更新。你可以在你刚刚生成的 OTA ZIP 文件中的一个找到 Edify 脚本的示例,在以下位置:
META-INF/com/google/android/updater-script
这里展示了一个 Edify 脚本的示例截图:

通常,我们不需要手动编写任何 Edify 代码,因为在标准场景中,有自动化工具可以生成包含所有必要文件的正确 OTA 包,但在调试时手动修改它们可能很有用,或者在我们从二进制文件构建自定义 ROM 并需要定制闪存上的安装时。
让我们看看下一节中的 Edify 语法。
Edify 语法
首先要知道的是,Edify 将每个表达式评估为所有字符串类型值。在布尔上下文中,空字符串被视为 false,而任何其他值都视为 true。为了总结,Edify 支持以下所有表达式类型:
(expr )
expr + expr # string concatenation, not integer addition
expr == expr
expr != expr
expr && expr
expr || expr
! expr
if expr then expr endif
if expr then expr else expr endif
function_name(expr, expr,...)
expr; expr
包含以下类型字符的每个字符串,当然这些不是保留字,都被视为字符串字面量:
a-z, A-Z, 0-9, _, :, /, .
对于保留字,我们指的是像 if else 和 endif 这样的词。
可以使用 双引号 来编写常量字符串,以便创建包含空格或其他字符的字符串,这些字符在之前的示例中没有列出,例如以下内容:
\n, \t,
对于新行和制表符,也可以分别写成以下内容:
\", \\
作为转义字符,我们在用 双引号 编写的字符串中使用 " 和 \。
运算符是简单的短路运算,也就是说,如果逻辑结果由表达式的左侧确定,则甚至不会考虑右侧。语法可以非常简洁,如下面的代码片段所示;两行是等价的:
a1 && a2
if a1 then a2 endif
; 字符是一个序列点,意味着其左侧的内容先被考虑,而右侧的内容后被考虑。
让我们看看一个更丰富的例子:
show_progress(0.750000, 0);
ui_print("Android Shamu");
mount("ext4", "EMMC", "/dev/block/…/system", "system");
unmount("/system");
解释器包含完成正确更新所需的所有函数。除非另有说明,否则函数在成功时通常返回 true,在出错时返回 false。
语言提供了控制流程和管理边缘情况的有用方法。例如,如果我们想触发错误以阻止安装,我们可以使用以下函数:
abort();
assert();
如你所料,如果你想添加新功能,你可以通过修改源代码来实现,但在那之前,让我们看看一些已经可用且非常有用的函数:
-
abort([msg]): 此方法允许你中断当前运行的脚本。它还接受一个字符串参数msg,可以显示给用户作为中断的进一步信息。 -
assert(expr[, expr, ...]): 此方法接受一个表达式列表作为参数,并逐个评估它们。如果其中任何表达式失败或返回false,则整个脚本执行停止。系统还会显示一个“断言失败”消息和刚刚失败的断言文本。 -
apply_patch(src_file, tgt_file, tgt_sha1, tgt_size, patch1_sha1, patch1_blob, [...]): 此方法接受一个patch1_blob文件,并将其作为二进制补丁应用到源文件src_file上,以生成目标tgt_file。 -
delete_recursive([dirname, ...]): 此函数接受一个文件夹名称列表作为参数,并删除它们,同时删除它们包含的每个单个文件。 -
file_getprop(filename, key): 此方法可以被视为属性文件检查器。它接受一些参数,包括一个文件名和一个键,并像属性文件一样扫描文件,寻找提供的键。如果找到键,则返回其值。 -
format(fs_type, partition_type, location, fs_size, mount_point): 此方法提供了一种强大的方式来格式化分区。 -
ifelse(cond, e1[, e2]): 此方法表示常见的if-then-else计算机科学语句。 -
is_mounted(mount_point): 此方法有助于检测挂载的分区。 -
mount(fs_type, partition_type, name, mount_point): 此方法在mount_point处挂载fs_type文件系统。 -
rename(src_filename, tgt_filename): 此方法接受两个参数,用于将src_filename重命名为tgt_filename。 -
run_program(path[, arg, ...]): 此方法执行路径处的二进制文件,传递args,并返回程序的退出状态。 -
sleep(secs): 此方法接受一个整数secs作为参数,并暂停执行secs秒。 -
symlink(target[, source, ...]): 此方法接受一个target文件和一个sources列表,并将所有来源创建为指向目标的符号链接。 -
unmount(mount_point): 这是mount的对应方法。此方法用于卸载在mount_point上挂载的文件系统。 -
这只是所有可用命令的一个子集。如果你对整个列表感兴趣,可以查看官方 Google 文档
source.android.com/devices/tech/ota/inside_packages.html。
我们现在能够修改——或者从头开始创建——用于更新安装的Edify脚本。这种知识将证明在自定义 ROM 中非常有用,尤其是在源代码不可用的情况下,如果你想通过自定义恢复修改系统,在只读系统分区中安装特定文件。
自定义 ROM 的 OTA
如预期的那样,从 OTA 概念中,我们得到了一个方便的系统用于自定义 ROM 的安装。原因是大多数自定义 ROM 都是以更新 ZIP 包的形式分发,用于提供给自定义恢复,然后由恢复程序负责在系统中安装这些包。通过分析 OTA 结构——正如我们在上一节所做的那样——我们可以直观地理解如何组织一个特定的包来安装修改版的 Android。实际上,通过一个临时的 Edify 脚本,可以格式化和重新安装任何系统分区的所有文件,以便分发你自己的修改版 Android。
这个任务留给读者作为练习,因为它可以用到目前为止获得的知识来完成。
高级 ROM 定制
在前面的章节中,你在自定义 ROM 的世界中迈出了第一步;我们已经发现了在线上已有的内容,并详细分析了最典型的方面。在本章中,我们将深入探讨,学习如何修改 Android 框架的最内部部分。
自定义 ROM 通常与那些添加最意想不到功能并在线分享一切的"黑客"联系在一起,但并不总是如此。
如前几章所述,许多设备制造商提出了他们自己的修改版 Android,这实际上就是 Android 自定义 ROM。
这是一个非常重要的方面,因为这本书既面向前面提到的"黑客",也面向那些在日常工作中使用这些知识的人——一个"黑客"通常会处理二进制 ROM,而很少处理源代码,而专业人士肯定会有源代码,以及所有相关的工具,以便实现额外功能的发展。
在下一节中,我们将尝试以简单的方式解释两种不同的定制方法。这些是:从源代码和从二进制。
二进制 ROM 定制
从二进制文件开始修改 ROM,我们遗憾地只有很少的选择。由于我们没有源代码来生成不同的镜像,我们只能修改文件系统,添加实用工具和新应用程序,或者从框架二进制文件开始对颜色和图标进行美学上的更改。
我们可以使用上一章中看到的工具并应用所有必要的更改,然后,当我们完成时,我们可以使用正确的 Edify 脚本生成一个 update.zip 包,该包允许安装新功能。
此外,我们还可以添加新的应用程序,无论是 Java 还是 C,或者通过添加 BASH 环境来增强系统镜像,或者复制更新后的应用程序到 /system 分区,如 Gmail 或 Maps,这些可能会占用 /data 分区的空间。
即使在这种场景下可能性有限,从二进制镜像开始,我们也可以尝试一些优化和调整,正如我们将在接下来的章节中看到的。
从源代码定制 ROM
如果您有源代码,您几乎可以做什么,但如您所知,
"能力越大,责任越大"—本叔叔,蜘蛛侠。
第一步是确定我们想要修改的部分及其仓库。让我们以 Android 的 Settings 菜单为例,我们将将其作为一个主示例来修改我们的 ROM。Settings.apk 的源代码在以下路径:
packages/apps/Settings
一旦确定了源代码路径,也就是仓库,开始您定制的最佳方式是将仓库镜像到您的服务器上,然后您将对代码进行更改。
为了确保您的仓库是 Android 系统的一部分,您需要更新 manifest.xml,这样当您再次与 "repo" 同步时,您将克隆自己的 Settings 版本,而不是 Android 的。
之后,您需要创建另一个个人仓库,在那里您将保存您的清单,并修改以下行:
<project path="packages/apps/Settings" name="platform/packages/apps/Settings" groups="pdk-fs" />
这里您可以看到代码将本地下载的位置:
project path="packages/apps/Settings"
而在这里,它的远程位置:
name="platform/packages/apps/Settings"
您会注意到远程位置没有链接,因为我们将使用默认的,如下所示定义在顶部:
<remote name="aosp" fetch=".." />
<default revision="refs/tags/android-6.0.0_r6" remote="aosp" sync-j="4" />
如您所见,fetch 指的是父文件夹 ".." 而不是绝对路径。为了简化我们的工作,最好的做法是添加一个远程仓库,如下所示:
<remote name="my_repo-github" fetch="git://github.com/my_personal_repo/" />
以这种方式,我们已经定义了我们的远程仓库,我们只需要修复 Settings 行,如下所示:
<project path="packages/apps/Settings" name="my_repo_Settings" remote="my_repo-github" />
现在我们已经准备好所有必要的配置来继续开发:我们有一个单独的仓库,我们可以在其中开发代码,但最重要的是,由于清单中的修改,我们不必触摸由 Google 管理的剩余系统部分,这样其他 Google 组件的更新就变得简单且顺畅。
将新包添加到 Android 的构建系统中
第一步是将包添加到 Android 的构建系统中,这样,当我们执行构建时,它将自动编译并添加到 ROM 中,就像其他应用程序发生的那样。我们可以在两个层面上工作:将系统应用程序作为编译的二进制应用程序添加,用C编写,或将系统应用程序作为应用层添加,该层在 Android Dalvik 虚拟机上运行,并以 APK 的形式分发。
为了创建 Android 应用程序,首先要做的是为编写代码准备环境并生成将被 Android 内部虚拟机执行的 APK 文件。我们将使用 Java、Android Studio 和 Android SDK 开发一个标准的 Android 应用程序。
通过二进制添加包
在开发自定义 ROM 时,您可能需要添加二进制可执行文件或应用程序,您没有源代码。例如,您可能希望将特定应用程序作为特定任务的默认应用程序添加,这样当用户启动 ROM 时,该应用程序就已经安装到系统中。我们可以将Facebook应用程序作为此类示例。
要成功将新应用程序添加到您的系统镜像中,您只需获取APK文件并将其复制到正确的 ROM 目录中。您可以使用update.zip文件,添加正确的 Edify 脚本,这将安装新的 APK——我们将在稍后更详细地看到——或者,如前几章中已预料的,您可以通过 Android 的构建系统执行整个操作。
第一步是编写正确的Android.mk;让我们假设我们的 APK 文件位于以下路径:
<aosp-root>/package/app/myapkfolder/
一旦您的 APK 已经就位,您需要创建一个Android.mk文件并添加以下片段:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := < your app folder name >
LOCAL_SRC_FILES := < app apk filename >
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
include $(BUILD_PREBUILT)
分析代码片段,您将注意到几个占位符,您需要用实际值替换它们。之后,您需要在commons.mk文件中创建一个新的条目,该文件位于:
build/target/product
添加新的 APK 安装相关行,如下所示:
PRODUCT_PACKAGES += < what you have defined in LOCAL_MODULE >
到目前为止,您只需重新编译 AOSP,以在系统中找到新的 APK,它将与其他系统应用程序一起预装。
另一种非常常见且方便的方法是将预编译的应用程序添加到我们的 ROM 中,即通过 Android 更新系统的帮助。假设您已经安装了自定义恢复镜像——这将使所有操作都更容易——要向 Android 的/system/xbin目录添加新的二进制文件,您只需创建一个包含 Edify 脚本的update.zip文件以执行正确的操作。
在这里,您将看到一个 Edify 脚本,它在目标文件夹/system/xbin中执行预编译的应用程序安装。脚本包含在:
META-INF/com/google/android/
脚本包含以下代码:
ui_print("Edify Script for binary installation");
ui_print("Flashing a binary");
show_progress(0.700000, 0);
ui_print("mounting /system");
mount("ext4", "EMMC", "/dev/block/system", "/system");
ui_print("");
ui_print("Installing binary");
package_extract_dir("system", "/system");
ui_print("unmounting system");
unmount("/system");
ui_print("unmounted system");
ui_print("Operations completed!");
update.zip文件的内部结构将如下所示:
update.zip
---> META-INF/com/google/android/update-script
---> META-INF/com/google/android/updater-script
---> system/xbin/mybinary
一旦创建好更新包,您只需通过设备上安装的恢复自定义应用来应用它。如您所注意到的,相同的做法,“edify 脚本 + update.zip + 恢复”被反复使用,这显示了 Android 更新系统的稳固和灵活,并且对于大量任务和场景非常有用;但我们还可以更进一步。
还有另一种程序,我们可以将其定义为“脏”,它允许更复杂的安装。您仍然会使用更新包的程序,但不是使用 Edify 语法,这在许多情况下可能不方便,并且对于高级场景来说并不那么强大,您将重新定义 update-script 二进制文件的内容。
如您所知,这个二进制文件默认包含执行 Edify 脚本的解释器,由系统启动。这种“脏”技术包括用执行所需操作的 shell 脚本替换这个二进制文件。使用这种替代方法,您有了非常强大的 shell 脚本语言,并且假设某些恢复自定义包括 Bash——作为 shell——因此它将作为解释器工作。
以下是在 Android 系统中使用 ad hoc update.zip 和操作安装的 shell 脚本进行 busybox 安装的示例:
#!/sbin/sh
FD=$2
ui_print() {
echo -n -e "ui_print $1\n" > /proc/self/fd/$FD
echo -n -e "ui_print\n" > /proc/self/fd/$FD
}
set_perm() {
chown $1:$2 $4
chmod $3 $4
}
ui_print "- Mounting /system"
mount /system
ui_print "- Installing BusyBox"
unzip -o "$3" busybox -d /system/xbin
ui_print "- Setting right permissions -"
set_perm 0 2000 0755 /system/xbin/busybox
ui_print "- Symlinking BB applets"
for i in $(/system/xbin/busybox --list); do
busybox ln -sf busybox "/system/xbin/$i"
done
ui_print "- Unmounting /system"
umount /system
ui_print "- BusyBox Installation complete -"
此脚本将替换我们的 update-script 并执行二进制安装。结果,更新包将具有以下结构:
update.zip
---> META-INF/com/google/android/update-script
---> busybox
因此,我们可以执行最复杂的安装,并且这实际上是 Android ROM “modders”最常用的方法之一。
通过源代码添加软件包
在系统源代码中。在本节中,我们将通过使用 Android Studio 创建一个Hello World应用程序来做一个真正的例子,我们将将其导入并与整个 Android 系统一起编译。
首先,我们需要使用 Android Studio 创建一个基础应用程序。
对于安装说明,请阅读以下链接:developer.android.com/sdk/index.html。
当您的系统准备就绪时,启动 IDE 并创建一个新的项目:

上一张图片显示了如何指定应用程序名称、域名和 Android 项目的路径文件夹。一旦输入所有数据,您就可以点击下一步并转到 API 级别选择,如这里所示:

如前一张图片所示,默认情况下 Android Studio 将针对 API 16,以覆盖超过 95% 的市场。在这个场景中,这个值并不重要,因为此应用将仅安装在我们的定制 ROM 中,这可能是 Android 6。让我们转到下一个屏幕——活动选择器:

上一张图片展示了我们可以轻松添加到我们的应用中的众多可能的活动。在这个例子中,我们将只使用一个空活动,以保持事情简单:

上一张图片显示了如何重命名我们全新的活动——MainActivity将完美地完成这项工作。只需点击完成,Android Studio 就会带你进入编辑器屏幕,以便向你的Hello, World应用添加一些代码:

上一张图片显示了当我们的应用启动时如何显示Toast消息;没有太多花哨的东西,但足以让你了解如何通过适当的工具集和知识使事情变得简单。
当你的应用准备就绪时,只需点击运行按钮,开始构建你的 APK 文件。尽可能多地测试它,当你对结果满意时,将源代码复制到 AOSP 源代码文件夹:
<aosp>/package/apps/Myapp
根据你目前的知识,你可以更新清单文件,将此应用添加到安卓构建系统中。
最后一步是Android.mk文件。对于这个Hello, World示例,只需创建一个新文件,如下所示:
<aosp>/package/apps/Myapp/Android.mk
添加以下片段:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Build all java files in the java subdirectory
LOCAL_SRC_FILES := $(call all-subdir-java-files)
# Name of the APK to build
LOCAL_PACKAGE_NAME := LocalPackage
# Tell it to build an APK
include $(BUILD_PACKAGE)
使用安卓的构建系统,你现在能够构建和打包你自己的安卓应用到你的定制 ROM 中。
破解安卓框架
这些定制与用户界面相关。由于涉及个人品味因素,UI 定制是一个棘手的话题:许多用户喜欢“纯安卓”界面,许多其他用户喜欢“不同安卓”界面的想法,远离主流的 UI 体验。
在本节中,我们给你自由选择,在纯安卓和定制安卓之间进行选择。你将学习如何进行小范围的定制,比如对状态栏或颜色进行定制,或者进行大范围的定制,比如向设置菜单添加新项目,以正确设置你定制 ROM 的定制功能。
定制引导序列 UI
¾¦+引导序列的图形外观无疑是您最想做的定制之一,通常是用户会要求并喜爱的。
在引导过程中,标准的安卓设备将显示:
-
启动画面
-
引导动画
启动画面是系统在开机后前几秒显示的静态图像。在谷歌 Nexus 设备上,启动画面看起来如下所示:

图片显示了谷歌品牌和一个锁。正如我们之前所学的,锁代表引导加载程序的状态——锁定或解锁。启动画面与引导的初始阶段相关联——通常,系统从开机到引导加载程序和 Linux 内核设置序列完成期间显示启动画面。
定制¾¦+启动画面并不容易,因为即使从理论上讲它只是一个图像,或者一系列图像,存储在 NAND 内存中,每个制造商都使用定制的方案来完成这个目标,并且他们非常不愿意记录我们如何恢复他们的工作。对他们来说非常容易的事情,拥有大量的工具和关于他们系统的知识,对我们来说却变成了几个小时的反向工程,结果和效果对整个系统的稳定性都是不可预测的。
将注意力转向启动动画,我们可以看到启动动画是一系列图像,大多数情况下是动画的,任何 Android 设备在启动序列中都会显示这些图像,紧随启动画面之后,直到 Android 系统完成启动。许多制造商定制这个动画来强化他们的品牌,你也会用你的自己的品牌来做同样的事情。从技术角度来看,当你看到启动动画时,内核已经加载,分区已经挂载,Android 开始启动。
与启动画面相比,这个图像序列更容易定制。这是因为即使大多数设备都有定制的启动动画,每一个都严格遵守非常严格的已知要求——这意味着我们为此有文档!
就像许多 Android 组件一样,启动动画以标准的.zip文件形式提供,并放置在/system/media/文件夹或/data/local/中。我们只需要抓取它,按照我们的喜好编辑它,然后放回——小菜一碟!
要检索文件,我们可以使用我们信任的adb。打开你的终端并运行以下命令:
adb pull /system/media/bootanimation.zip .
当然,如果文件不存在,就尝试我们之前提到的第二个可能的位置。一旦你在你的主机计算机上有了这个文件,你就可以解压缩它,你将看到以下图像中显示的相同的文件夹结构:

所有那些part*文件夹包含创建动画的图像,而desc.txt文件包含正确执行动画的说明。
使用你喜欢的文本编辑器打开desc.txt文件,你将看到以下图像类似的内容:

第一行指定了动画将显示的分辨率和帧率。行2到6指定了如何显示动画的不同部分。
第一个字母,"c",代表“继续”,指示系统即使在启动序列完成后也要继续播放序列。
第一个数字指定了部分需要重复的次数。在示例中,只重复一次,或者无限时间(使用 0 作为值来表示无限循环)。第二个数字指定了在开始下一个部分之前将等待多少秒。行尾的最后标记指定了包含要显示的图像的文件夹。
现在你已经了解了bootanimation.zip文件的内部结构和如何设置序列,是时候发挥创意,替换所有那些无聊的图像,创建你自己的精彩动画了!
一旦你满意,就是时候创建一个新的bootanimation.zip文件了。打开你的终端并运行以下命令:
zip -r -0 bootanimation.zip part0 part1 partX desc.txt
仔细地将partx替换为你动画序列中正确的文件夹序列。要尝试你全新的启动动画,只需使用adb将zip文件上传到/data/local/文件夹。你甚至可以创建一个自定义的update.zip并将其通过 Recovery 刷入你的设备。这取决于你。
注意
FFMPEG 是一个方便的工具,可以从视频中提取图像以创建你的动画序列。打开终端并运行以下命令:
ffmpeg -i "path_file" -r 1 -s 1024x768 -f image2 "path_images-=.jpg"
之前的命令指定了一些有趣的参数:-r 1用于每秒捕获一帧,-s用于指定最终图像的分辨率,-f image2用于实际捕获一帧并将其保存为图像。一如既往,你可以通过-h来获取更多文档信息。
定制安卓设置菜单
安卓的一个显著特点是模块化:大部分的系统功能实际上都是独立的安卓应用,分别开发和维护。例如,安卓的设置菜单本身就是一个安卓应用,称为Settings.apk,作为 AOSP 的一部分,可以根据我们的需求自由定制。在下一页,你将学习如何对Settings.apk进行操作以添加自定义菜单项。
打开你的终端模拟器,从包含安卓源代码的WORKING_DIRECTORY开始,导航到:
WORKING_DIRECTORY/packages/apps/Settings
这个文件夹包含了原始设置菜单的源代码;这是你定制的起点。
这是一个关键示例,因为当你在一个自定义 ROM 上工作时,你正在改进系统,添加新功能或增强现有功能。你的新功能可能需要一定程度的设置,并将所有可能的配置选项放置在用户期望的位置,即设置菜单,这是良好用户体验的基本点。
以下图像显示了原始安卓设置菜单,这是我们定制的对象:

一旦你进入了设置菜单应用文件夹,packages/apps/Settings,你就可以开始编辑文件以添加你的新菜单项。让我们从添加一些字符串开始。使用你喜欢的编辑器——Android Studio、Atom、SublimeText 等——编辑res/values/strings.xml并添加以下行:

strings.xml文件包含了设置应用中使用的所有文本字符串列表;它是你定制的完美起点,并为你提供了关于命名约定和结构的想法。
一旦你对字符串文件满意,创建一个名为 CustomSettings.java 的新 .java 文件,并将其放置在 src/com/android/settings 文件夹中。这将包含我们需要的所有逻辑。以下图片显示了你可以创建的自定义 PreferenceFragment 的一个片段:

这个 Fragment 将加载你需要创建的特定布局文件。让我们称它为 custom_settings.xml,并按照下一张图片所示进行填充:

现在,你需要向 AndroidManifest.xml 文件中添加几行。导航到 root 文件夹并按照以下方式编辑 AndroidManifest.xml 文件:

导航到主 src/ 文件夹并打开 Settings.java 文件。这个文件包含了 Settings 菜单中可用的所有 Activity。在这里,你可以添加自己的 Activity,如下一张图片所示:

src/ 目录下包含一个 SettingsActivity.java 文件。在这文件的开始部分,你会找到一个名为 ENTRY_FRAGMENTS 的 String 数组。这些都是可以被 Settings 菜单中的 Activity 文件加载的 Fragment。这个列表相当令人印象深刻,在 Android Marshmallow 系统中,它包含大约 70 个 Fragment;在你的 Android 版本中,它将包含一个额外的条目:你的。按照以下截图所示,将你的 CustomSettings 类添加到数组中:

我们几乎完成了。接下来我们需要做的是使用以下命令编译新的包:
:$ mm
一旦我们创建了新的包,我们就可以创建一个新的更新文件,并使用 Recovery 来刷写它。在下次启动时,我们将在 设置 屏幕中看到我们全新的菜单项,如下一张截图所示:

提升系统性能
你可以在网上找到的许多自定义 ROM 都带来了性能提升、电池寿命延长以及许多小调整。这些增强中的大多数都可以通过 build.prop 文件的手术式调整来实现。
自定义系统属性文件
Android 的 build.prop 文件包含在启动序列中应用于系统的各种系统设置的详细信息。在深入其定制之前,我们需要对其内部结构有一个概述。
打开终端并使用以下命令连接到你的设备:
:$ adb shell
导航到 /system 文件夹并打开 build.prop 文件。内容将类似于以下片段:
ro.product.model=Nexus 6
ro.product.brand=google
ro.product.name=shamu
ro.product.device=shamu
ro.product.board=shamu
[…]
如你所猜,这些说明中的部分是针对每个设备特定的,但其中一些相当常见。我们确实有设备型号名称、品牌、产品、设备和板子的代号等等。
这些常见值可以轻松编辑,以在我们的系统中获得有趣的行为变化。例如,你可能已经注意到了在接收到电话时,智能手机开始响铃前的那一小段,但可以感知到的延迟。通过仅编辑build.prop文件中的几行,就可以移除这个延迟。扫描文件并查找以下两行:
ro.telephony.call_ring.delay=0
ring.delay=0
简单地将分配给它们的任何值替换为漂亮的0(零),然后你可以告别延迟。
你是否曾经想过为什么当手机显示锁屏或应用启动器时,你无法旋转屏幕?不再需要疑惑。查找这两行,并将现有的属性替换为新的属性:
log.tag.launcher_force_rotate=VERBOSE
lockscreen.rot_override=true
你是否想要将设备旋转超过 180 度?通过以下行启用 270 度旋转:
windowsmgr.support_rotation_270=true
我们可以通过单行编辑实现另一个 UI 技巧,即更改 LCD 密度值。搜索以下行:
ro.sf.lcd_density=XXX
将XXX替换为你想要尝试的值。更改此值将产生系统图标的大小调整和屏幕空间的增加:你设置的值越小,你得到的空闲空间就越多。不幸的是,这里没有精确的科学方法,一点点的试错是不可避免的,所以尝试用几个值进行实验,直到找到你喜欢的设置。
每天 Android 设备都在变得更强大,但,在那些日子里,可用的 CPU 功率非常有限。为了保证令人满意的性能和用户体验,Android 使用了智能调整,如下所示:
ro.media.enc.jpeg.quality=xxx
之前的值改变了 JPEG 文件的渲染质量。即使它在过去很有用,我们也可以认为在上一代智能手机上它是多余的,我们可以安全地将它设置为100并享受 100%原始质量的图片。
如果你的智能手机有物理导航按钮,你可以通过以下方式设置下一个属性来增加屏幕空间,移除屏幕底部的导航软键:
qemu.hw.mainkeys=1
如果你的设备没有物理按键,你仍然可以移除软键并使用手势导航;在 Google Play Store 中查找手势应用,如All in one Gestures。继续“屏幕空间”主题,你可以通过以下属性移除系统通知栏中的debug mode图标:
persist.adb.notify=0
这最后两个调整指的是网络设置。第一个如下:
wifi.supplicant_scan_interval=300
这行配置了每次自动 Wi-Fi 扫描之间的秒数。Android 默认执行自动 Wi-Fi 扫描,寻找可连接的开放网络或仅为了提高导航系统的精度。你可以增加或减少这些扫描的频率,试图在导航的更高精度和更长的电池寿命之间找到完美的平衡。第二个网络调整给你提供了设置默认 DNS 服务器的机会:
net.dns1=8.8.8.8
net.dns2=8.8.4.4
这在政府根据 IP 地址过滤互联网网站的国家中非常有用。使用前面片段中显示的 DNS IP,Google 的 DNS 服务器,你将能够绕过这种审查。
添加自定义初始化序列
Linux 的传统在 Android 架构的几个关键方面仍然很强。其中最有趣的一个是在初始化期间执行自定义脚本的可能性。如果你熟悉 Linux 系统,你了解 /etc/init.d 文件夹。这个系统文件夹包含了一组在系统启动时可以执行的脚本。为了在 Android 上实现相同的行为,我们可以使用 busybox 和它的 run-parts 工具。这个工具接受一个文件夹作为参数,并执行该文件夹中包含的每个脚本。例如,以下命令将执行 /system/etc/init.d 文件夹中包含的每个脚本:
run-parts /system/etc/init.d
为了正确复制 Linux init.d 的行为,我们希望能够以严格的顺序执行脚本。你可以通过巧妙的文件命名来实现这一点。只需重命名你的脚本,并在前面加上一个数字,如下例所示:
01settings
02optimizations
在前面的例子中,01settings 脚本将在 02optimizations 脚本之前执行,依此类推。现在你已经有了一组有序的脚本,并且知道如何逐个执行它们,你需要编辑我们在前几章中看到的 install-recovery.sh 文件,并添加以下行:
run-parts /system/etc/init.d
高级 Linux 内核修改
当你想到定制 Android 系统的核心时,你立刻会想到定制 Linux 内核。它管理 CPU、传感器、无线电和显示,并且是每个系统定制的起点。正如我们之前看到的,修改内核并不是一件容易的事情,但有了正确的思维方式、知识和工具集,它可以是一次令人满意的体验。
每个嵌入式系统都有其自定义的可能性,当涉及到 Android 时,大部分努力都集中在以下方面的定制:
-
管理器
-
I/O 调度器
-
CPU 超频/降频
深入 CPU 频率管理
在工作中,以及如何根据不同的场景选择不同的管理器。在本节中,你将学习如何自定义现有的管理器以及如何将新的管理器添加到你的系统中。
注意
一个 管理器,或 CPU 频率管理器,描述了 CPU 根据特定的环境因素如何表现。
一个典型的通用管理器会在系统负载低时减少活跃核心的数量和它们的工作频率,当系统需要高性能时,将 CPU 推向全功率和全速。
标准的 Linux 内核提供了以下管理器:
-
按需: 这是市场上大多数内核的默认调速器。它被认为是一个平衡的调速器,因为它可以保证系统具有反应性,在需要时快速增加 CPU 频率。事实是,由于如此渴望增加 CPU 频率,这个调速器并没有对实际所需的 CPU 功率进行任何实际评估。按需调速器不考虑实际系统负载;相反,当它被触发时,它会将 CPU 频率增加到最大值,如果不需要,它会缓慢降低。正如你所看到的,这不适合“省电”场景:每次系统认为需要更多电力时,它都会推到最高速度,而不进行更深入的分析。这种方法肯定会保证设备具有反应性,但肯定会迅速耗尽电池。
-
省电: 这无疑是节省电池寿命最有效的方法之一。这个调速器将 CPU 的最高频率设置为最低可能值。电池的续航时间肯定会“无限”,但设备将无法使用:一个 2 GHz 四核 CPU 可以轻松降低到 200 MHz,如果它始终保持在那里,那就毫无意义。
-
性能: 这个调速器与省电调速器的行为正好相反:它将 CPU 的最小频率设置为最大可能值以实现最佳性能。从电池的角度来看,这会迅速耗尽电池:一个始终以全功率运行的 2 GHz 四核 CPU 当然表现良好,但智能手机的续航时间不会很长。
-
交互式: 这是按需调速器的更智能版本。其目标是提供一种反应性 CPU 缩放,而不会陷入按需陷阱。按需调速器根据预设值更改 CPU 频率,而不进行任何具体分析。相反,交互式调速器会持续评估系统负载,并根据需要调整 CPU 频率,具有更线性的 CPU 缩放曲线:这绝对是一个优点。整个 CPU 缩放分析不是基于原始工作量,而是根据请求的时间进行。这种方法保证了系统流畅性和在多媒体场景中的更好性能,因为 CPU 不会在频率上上下跳动,而是在整个必要的时间内保持稳定,在需要时提供恒定的帧率。
-
保守: 这个调速器是按需调速器的平滑版本。与按需调速器不同,保守调速器不会每次都将 CPU 推到最高频率,而是会根据 CPU 负载逐步调整 CPU 频率。
-
用户空间: 这是最可定制的也是最“非自动”的调速器。它为用户提供手动选择所需频率的可能性。
添加自定义 CPU 频率调速器
如果你需要特定的 CPU 行为,或者你只是想更深入地了解内核定制,你可以创建自己的 CPU 调速器。
对于这个任务,您需要内核源代码并导航到:
<root-source>/drivers/cpufreq
此文件夹包含我们在上一节中看到的每个调度器以及您将要添加或设备制造商已经添加的每个可能的自定义调度器。
让我们在该文件夹中创建一个新的调度器,例如创建一个.c文件:
<root-source>/drivers/cpufreq/cpufreq_mygovernor.c
将文件放置好之后,您需要将其添加到以下提到的文件中:
<root-source>/drivers/cpufreq/Kconfig
我们按照以下片段所示进行更改:
config CPU_FREQ_GOV_MYGOVERNOR
tristate "'mygovernor' cpufreq governor"
depends on CPU_FREQ
help
'mygovernor' - my optimized governor!
config CPU_FREQ_DEFAULT_GOV_ MYGOVERNOR
bool "mygovernor"
select CPU_FREQ_GOV_MYGOVERNOR
help
Use the CPUFreq governor 'mygovernor' as default.
完成对Kconfig的编辑后,编辑Makefile并添加以下行:
obj-$(CONFIG_CPU_FREQ_GOV_ MYGOVERNOR) += cpufreq_mygovernor.o
作为最后一步,编辑以下文件:
<root-source>/include/linux/cpufreq.h
大约在第 400 行,有一个当前可用的调度器列表,如下所示:

按照相同的模式,让我们添加您的新调度器引用,使用以下片段:
#elif defined(CONFIG_CPU_FREQ_DEFAULT_GOV_MYGOVERNOR)
extern struct cpufreq_governor cpufreq_gov_mygovernor;
#define CPUFREQ_DEFAULT_GOVERNOR (&cpufreq_gov_mygovernor)
任务完成:您的新调度器现在可用并准备好集成到您的下一个内核构建中。尝试运行menuconfig并导航到调度器屏幕;您将能够启用它并将其设置为默认调度器。
探索 I/O 调度器
I/O 调度器指定了 I/O 密集型操作必须如何执行以及在 CPU 核心之间如何平衡。Android 自带一组默认的 I/O 调度器:
-
Noop: 这几乎可以被认为是一个调度器。实际上,它对任务列表没有影响:它只是按顺序排队。
-
SIO: 这是第一个真正的调度器。即使它不进行任务重排序,它也能保证从任务入队到执行的最小延迟。
-
CFQ: 此调度器根据特定类别将任务排序到独立的队列中,并为每个队列分配一个执行时间窗口。窗口大小取决于分配给相关任务的优先级。
-
BFQ: 此调度器与 CFQ 调度器类似,但它使用磁盘带宽窗口而不是时间窗口来分组和调度任务。
-
Anticipatory: 此调度器使用预测技术来分组和调度任务,暂停执行一段时间并等待可能的新任务被添加到特定队列。
-
ROW: 此调度器基于“读优先于写”规则:每个读取任务都比写入任务具有更高的优先级。
-
Deadline: 此调度器保证入队任务的终止,试图避免“饥饿”场景。饥饿是计算机科学中的一个知名概念,适用于资源管理。想象一下,N个进程想要使用相同的共享资源。共享资源一次只能由一个进程使用,进程根据它们的优先级交替使用。如果一个低优先级进程请求资源,但由于其他高优先级进程正在使用它,资源永远不会变得可用,会发生什么?低优先级进程将永远等待资源,永远不会得到使用。在计算机科学的术语中,它将饿死。
每个可用的调度器都存储在以下文件夹中:
<root-source>/block
创建 I/O 调度器可能具有挑战性,这超出了本书的目的。我们能做的是为你指明正确的方向,并激发你对这个主题的兴趣。
展望未来
在撰写这本书的过程中,我们很幸运地提前看到了即将到来的 Android N。新版本可能于 2016 年底作为稳定版发布,在几个月的公开开发者预览之后。
Android N 引入了一些有趣的功能,如下一张图片所示的多窗口模式:

在谷歌,他们非常注重用户反馈,并在三星测试了几个月后决定将此功能引入官方版本。我们大多数人都会从三星已经提供的 Android ROM 中认出多窗口模式。在 Android N 中,这项功能将面向所有人,适用于所有 Android 设备,并完全支持两种方向,即纵向和横向,甚至可以通过拖动“分隔线”来调整分割窗口的大小。
根据许多博客文章,Google Play Store 应用中最受欢迎的类别之一是来电显示过滤器。在 Android N 中,这个功能将作为系统功能提供,就像新的“移动数据节省”功能一样,旨在减少特定应用的背景数据消耗。
Android N 带来的新 UI 增强之一是可以在滚动下拉的快速设置菜单中添加和删除操作图标,如下一张图片所示:

此外,下拉的通知菜单还带来了新的通知设计,它允许更丰富的交互,更快地访问常用操作,如下一张图片所示:

设置部分也得到了一些关注,新增了就地通知,如下一张图片所示,这让你有机会在不导航到特定位置的情况下禁用或启用设置:

下一个图片还展示了新添加到设置部分的导航抽屉,以便更快地导航到更深的菜单层级:

新版本中将提供大量的小修复,许多改进旨在提高性能和电池寿命,如最受欢迎的移动中的省电模式,它承诺将为 Android 设备带来变革。
摘要
在本章中,你学习了如何通过实际案例在不同的级别上有效地自定义 Android,你现在知道如何从源代码中程序性地创建一个自定义 ROM,准备一个带有每个部件就绪的自定义文件夹结构,以便由 Android 的构建系统组装。你还知道如果你有一个已经组装的系统镜像,如何处理自定义任务,以及如何自定义和重新组装二进制镜像。
下一章将带你走出纯智能手机体验,并展示 Android 如何有效地成为我们生活中的无处不在:物联网、Android Auto 和 Android Wear、智能家居和娱乐只是我们目前能找到的绿色机器人的几个场景。
第八章. 超越智能手机
在第七章,定制您的个人 Android 系统中,你学习了如何给你的定制 Android 系统添加最后的个人风格。你定制了应用层和系统层:新的菜单、新的应用和新的守护进程。
在本章中,我们将更进一步:我们将超出智能手机,连接到外部微控制器、传感器和不同的设备。我们将看到整个世界如何与 Android 连接和互动。
你将了解 Android ADK 和 Arduino,以及谷歌如何用 Android 导向的设备丰富我们的生活:从 Chromecast 设备到 Android Auto,从智能手表到物联网。
认识 Arduino
十多年前,在一个小意大利酒吧里,一群学生和研究人员创造了一个低成本微控制器,它将彻底改变 DIY(Do It Yourself)的世界——Arduino,如下一张图片所示:

Arduino(或 Genuino,非美国市场)的最新版本被称为 Arduino UNO。Uno在意大利语中意味着一个,这个代号是为了庆祝板子自带的 IDE(集成开发环境)的第一个稳定版本。这个板子基于 Atmel 的 ATmega328P,提供了一套可控制的输入/输出引脚。一旦被正确编程,它可以作为一个独立的微控制器工作,并且可以通过其 USB 连接使用。
Arduino 最伟大的特点是它的开放性:从硬件原理图到开发 IDE,一切自第一天起就都是开源的。这种开放性和板子的可扩展设计允许制造商和高级用户创建无限数量的所谓扩展板:

Arduino 扩展板是一个可以附加到 Arduino 上以增强其功能和添加新功能的独立组件。上一张图片展示了如何堆叠 Arduino 扩展板以创建一个完全新定制的设备。Arduino 扩展板的常见例子包括:
-
Ethernet Shield,它使 Arduino 能够通过互联网连接与外界通信。
-
Proto Shield,它可以用来制作你用面包板创建的原型的永久版本。
-
Relay Shield,它使 Arduino 能够控制高压电路。这在家庭自动化中至关重要,当你需要开关灯或电器时。
-
Display Shield,它为 Arduino 提供了一种与外界进行视觉通信的方式。
自从 Arduino 问世以来,它获得了越来越多的粉丝和热情的开发者,这得益于其易于使用的界面和极低的学习曲线。今天,没有硬件或电子知识的软件开发者也可以创建超出他们电脑的项目,并能与外部世界互动。为了利用这些可能性,2012 年谷歌创建了 Android ADK。
Android ADK
Android Accessory Development Kit 是 Android Open Accessory 设备的参考实现。在 2012 年的 Google I/O 大会上,谷歌向开发者提供了 Android Accessory Development Kits,并为制造商提供了创建他们自己的套件、为 Android 创建外部配件设备的明确规范。这些认证设备之一就是 Arduino 本身,但多亏了整个项目的开放性,你自己也可以构建一个兼容设备。
不幸的是,Android ADK 在开发者中从未真正火爆。当然,你可以在网上找到许多关于将 Android 智能手机连接到 Arduino 的有趣项目,比如 TCRobotics 在blog.bricogeek.com/noticias/arduino/el-adk-de-google-en-un-arduino-uno上的项目。这肯定是我们最喜欢的之一;它展示了巨大的潜力,但也揭示了保持 Android 智能手机有线连接到整个过程的巨大牺牲:

幸运的是,对于 Android ADK 的使用,有更多酷炫的方式来与传感器和电子设备互动。
使用 UDOO 作为一体化的 ADK 设备
如你所知,UDOO 可以运行 Android。你可能不知道的是,它上面还内置了 Arduino。是的,Android 和 Arduino 都在同一块板上!当你想到你可以将触摸屏,甚至鼠标和键盘连接到 UDOO 时,你很快就会开始幻想你那些极客项目的实现。
准备工作
要开始使用 Arduino,你只需要设置 UDOO 并将 Android 部分连接到 SAM3X(Arduino 兼容)部分。以下图片展示了从上方看到的 UDOO,左侧高亮显示的是跳线 18。这个跳线必须拔掉才能启用 SAM3X。右侧也高亮显示了你要连接的 USB 端口:

一旦板子准备就绪,你就可以进入软件部分。
刷写 Arduino 板
在这个快速示例中,我们将控制连接到 UDOO 的 LED。LED 将连接到 UDOO 板的输入 13。每个 LED 都有两个引脚;较长的那个是阳极,必须连接到输入 13,较短的则是阴极,必须连接到地,即输入 13 左侧的无编号输入:

电子设备设置已经就绪。让我们从www.udoo.org/other-resources/下载 Arduino IDE。
第一次运行 Arduino IDE 时,你会看到一个空的项目文件:

这个空的 Arduino 草图为你提供了我们的 Arduino 程序的骨架结构:
-
一个只运行一次并设置第二方法所需一切的
setup方法 -
一个不断重复运行的
loop方法,直到板子被关闭
为了正确连接和编程我们的 Arduino,我们需要选择板型和端口。从 Arduino IDE 工具菜单中选择板 | Arduino Due (编程端口):

上一张图片显示了目前市场上可用的不同 Arduino 板数量。UDOO 与 Arduino Due 兼容,因此我们选择了该板型。一旦我们选定了合适的板型,我们需要选择用于连接到 UDOO 的端口:

如你在图中所见,端口名在不同的电脑上可能会有所不同。上一张图片显示了苹果 MacBook Pro 的常见配置。
一旦 IDE 配置正确,我们就可以开始编写源代码,如下所示:
#include "variant.h"
#include <stdio.h>
#include <adk.h>
#define LED_PIN 13
// Accessory descriptor. It's how Arduino identifies itself to Android.
char descriptionName[] = "ArduinoADK";
char modelName[] = "UDOO_ADK"; // Arduino Accessory name (Need to be the same defined in the Android App)
char manufacturerName[] = "Packt"; // Manufacturer (Need to be the same defined in the Android App)
char versionNumber[] = "1.0"; // version (Need to be the same defined in the Android App)
char serialNumber[] = "1";
char url[] = "http://www.packtpub.com"; // If there isn't any compatible app installed, Android suggest to visit this url
USBHost Usb;
ADK adk(&Usb, manufacturerName, modelName, descriptionName, versionNumber, url, serialNumber);
#define RCVSIZE 128
uint8_t buf[RCVSIZE];
uint32_t bytesRead = 0;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
delay(500);
Serial.println("Starting...");
}
void loop() {
Usb.Task();
if (adk.isReady()) {
adk.read(&bytesRead, RCVSIZE, buf);// read data into buf variable
if (bytesRead > 0) {
if (parseCommand(buf[0]) == 1) {// compare received data
// Received "1" - turn on LED
digitalWrite(LED_PIN, HIGH);
} else if (parseCommand(buf[0]) == 0) {
// Received "0" - turn off LED
digitalWrite(LED_PIN, LOW);
}
}
} else {
digitalWrite(LED_PIN , LOW); // turn off light
}
delay(10);
}
// the characters sent to Arduino are interpreted as ASCII, we decrease 48 to return to ASCII range.
uint8_t parseCommand(uint8_t received) {
return received - 48;
}
我们可以快速分析源代码,并找出:
-
我们指定了引脚号 13
-
我们指定了模型名称、制造商名称和版本号,这些信息将识别我们连接到 Android 的板型。
-
我们正在配置串行连接
-
我们正在监听串行连接上的传入数据,并相应地做出反应:
-
如果我们收到 1,则打开 LED
-
如果我们收到 0,则关闭 LED
-
一旦源代码就绪,你可以使用 IDE 的文件 | 上传菜单将其烧录到 Arduino 上。
创建 Android 应用
Android 应用将非常简单:一个切换按钮来打开和关闭 LED。你可以使用 Android Studio 向导创建初始应用,创建一个空的Activity以开始。一旦有了框架,你需要在build.gradle中添加一个新的依赖项:
dependencies {
compile 'me.palazzetti:adktoolkit:0.3.0'
}
Emanuele Palazzetti,Packt Publishing 出版的《入门 UDOO》一书的作者,发布了一个实用的 Android 库,ADK Toolkit (github.com/palazzem/adk-toolkit),用于简化你的 Android 应用和 Android ADK 设备之间的通信,我们将充分利用这个库。
你需要在你的 AndroidManifest 中添加一些特定的配置。在你的<activity>标签中,添加以下行:
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"/>
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="@xml/accessory_filter"/>
如你所注意到的,<meta-data>标签引用了一个名为accessory_filter的 XML 资源。目前它缺失。让我们在src/res/xml/文件夹中创建一个accessory_filter.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory
manufacturer="Packt"
model="UDOO_ADK"
version="1.0"/>
</resources>
这是我们在 Arduino 草图中所添加的确切信息,将允许 Android 正确识别我们的板型。
设置完成。让我们继续到我们应用的 UI。遵循向导后,你现在有一个带有自己布局的单个Activity;很可能是它的名字是main.xml,位于src/res/layout。一旦你找到了布局,我们可以添加我们的按钮:
<ToggleButton
android:id="@+id/toggleButtonLED"
android:textOn="Turn OFF"
android:textOff="Turn ON"
android:layout_width="500dp"
android:layout_height="200dp"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:textSize="50sp"
android:onClick="blinkLED"/>
这非常直接:一个 ID,几个标签,以及一个当按钮被点击时触发的onClick方法。
由onClick引用的方法将被放置在我们的Activity中:
public void blinkLED(View v) {
if (buttonLED.isChecked()) {
adkManager.write("1");
} else {
adkManager.write("0");
}
}
当按钮被点击时,如果是开启状态,我们发送1;如果是关闭状态,我们发送0。这很公平,但我们把数据发送到哪里?那个adkManager是什么?
adkManager模块随 ADK Toolkit 提供。我们在Activity中创建它并设置它。这是最终的结果:
public class UDOOBlinkLEDActivity extends Activity {
private ToggleButton buttonLED;
private AdkManager adkManager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
buttonLED = (ToggleButton) findViewById(R.id.toggleButtonLED);
adkManager = new AdkManager(this);
registerReceiver(adkManager.getUsbReceiver(), adkManager.getDetachedFilter());
}
@Override
public void onResume() {
super.onResume();
adkManager.open();
}
@Override
public void onPause() {
super.onPause();
adkManager.close();
}
public void blinkLED(View v) {
if (buttonLED.isChecked()) {
adkManager.write("1");
} else {
adkManager.write("0");
}
}
}
最后,我们的应用完成了。只需将其上传到我们的 UDOO,您将有一个巨大的按钮来打开和关闭 LED:

探索物联网的可能性
知道您最喜欢的操作系统可以在数千种设备上运行,在数百种不同的定制中,并且可以与任何类型的设备通信,无论是有线还是无线,这开辟了令人难以置信的可能性。
Android Auto
2014 年,谷歌推出了 Android Auto,这是一个旨在使用我们汽车中已有的控制命令 Android 系统的创新项目:

2015 年,Android Auto 的第一个版本发布,开发者社区开始真正关注它。2016 年,数十家汽车制造商将推出支持集成 Android Auto 的车型。
Android Auto 背后的理念是支持驾驶安全,并在驾驶时为用户提供一种访问其设备的替代方式。为了实现这一目标,谷歌工程师与汽车制造商合作,在 Android 设备和汽车仪表盘之间建立了一座桥梁。
汽车仪表盘和控制代表了我们驾驶时可能拥有的用户体验和交互的顶端。一切都被放置得恰到好处,以便于访问,一切都被设计得易于使用,一切都被创造得既有效又强大,但又不分散注意力。
这些限制迫使谷歌重新思考他们为这个新挑战而流行的应用。当您将 Android 智能手机连接到 Android Auto 兼容的汽车时,您可以享受一个针对特定场景定制的不同操作系统用户界面。下一张图片显示了 Android Auto 的 Google Maps 界面:

下一张图片显示了连接我们的设备到 Android Auto 兼容汽车后,Google Play Music 的用户界面:

如 Google Maps 或 Google Play Music 等流行应用演变成更有效的设计,充分利用了仪表盘更大的屏幕和方向盘控制。
从开发者的角度来看,Android Auto 带来一个明显的问题:
我需要一辆车来开发和测试我的应用吗?
幸运的是,谷歌为那些想要接触 Android Auto 的人提供了测试工具:桌面车载单元(DHU)。它随 Android SDK 一起提供,在您的计算机上运行,并允许您的计算机充当汽车仪表盘。以下图片显示了智能手机如何切换到 Android Auto 模式,以及用户界面如何切换到 DHU:

上一张图片展示了将智能手机连接到汽车后,智能手机显示屏的外观——它变黑并显示了 Android Auto 标志。下一张图片展示了将智能手机连接后,汽车仪表盘如何变得活跃。汽车仪表盘变成了 Android Auto 用户界面,在这个例子中,显示了几个 Google Now 卡片,包括交通和天气信息:

Android Wear
当我们等待 Android Auto 功能的车款进入我们的生活时,我们可以将注意力转向 Android Wear。
2014 年,谷歌宣布了一种特定的 Android 版本,专门为智能手表设计和开发。Android Wear 最初是基于 Android 5.0 Lollipop 的定制版本,目前基于 Android 6.0.1 Marshmallow。
Android Wear 旨在增强用户每天与世界互动的方式。一款 Android Wear 智能手表连接到 Android 智能手机,并提供更快地访问通知、消息以及所有可能的、在不与智能手机本身交互的情况下以更好的方式享受的内容:

像上一张图片中的那样的智能手表提供了与数十个服务的集成,例如 Google Fit、Endomondo 和 IFTTT。它们具有蓝牙和 Wi-Fi 连接性、GPS 和加速度计。这一巨大的可能性推动 Android 社区进行实验,并为数十种场景创造解决方案。
在 Android SDK 和 Android 社区的支持下,在过去两年里,我们看到为智能手表量身定制的应用程序数量不断增加——我们可以用我们的手表打开我们的飞利浦 Hue 灯,我们可以用我们的手表关闭我们的 Google Nest,我们可以通过 Parrot Flower Power 了解我们植物的状态。
沿着这条道路走下去,将直接带我们进入下一节。
智能家居
我们生活在一个许多设备、家电和“事物”原本是未连接的世界,现在却成为了不断增长的互联设备生态系统中的一部分。我们来自一个过去,计算只能在我们的电脑中发生——我们现在生活在这样一个当下,计算发生在我们的口袋里,通过我们的智能手机。我们正朝着这样一个未来迈进,计算将在任何地方发生:手表、汽车、无人机、房屋、花园等等。
我们曾经有需要手动控制的恒温器,现在我们有智能恒温器,例如 Google Nest,它们会从我们的习惯中学习并相应地做出反应,以创造更好的用户体验:

我们以前需要用墙上的开关来打开和关闭灯光,现在我们有智能灯泡,比如飞利浦 Hue,可以通过智能手机甚至智能手表来控制。这些灯泡在我们接近家的时候可以自动打开,利用地理围栏等概念。我们有可以与其他设备互联的灯光,比如智能门铃,可以为听力受损的用户创建视觉触发器:

我们有植物传感器,比如 Parrot Flower Power,可以在我们的智能手机上显示通知,告诉我们我们的植物需要浇水。知道了这一点,即使我们在数千公里外的某个偏远海滩上,享受着假期,我们也可以远程命令 Belkin WeMo 开关打开我们的灌溉系统并给植物浇水。
我们有智能冰箱,比如三星 Family Hub,它们连接到互联网,并允许你实际上看到冰箱内部,检查明天早餐的橙汁是否缺少。它们变得越来越互联,韩国版本将知道你最喜欢的产品的可能折扣,并建议你检查哪个特定的超市以节省一些钱。
我们有智能镜子,比如汉娜·米特尔施泰特(Hannah Mittelstaedt)制作的那个(github.com/HannahMitt/HomeMirror),可以用你的一部旧安卓设备轻松创建。给你的旧平板电脑赋予新的生命和新的用途。它可以提供天气预报、最新新闻、植物状态、交通信息,或者在你早上刷牙时你想拥有的任何有用信息。
我们有智能咖啡机,比如Nespresso Prodigio,它可以告诉我们水位、剩余咖啡胶囊和必要的维护情况。咖啡机可以远程控制,从沙发上操作,而且在我们历史上,这个经典的笑话不再是笑话了;我们的安卓手机实际上可以为我们煮咖啡!
一个绿色的机器人能让你娱乐吗?
一旦人类满足了所有基本需求,就开始与无聊作斗争了!
好吧,可能有点夸张了,但我们正在进入娱乐部分,所以让我们来谈谈一些有趣的事情吧!
多媒体
娱乐是一个巨大的市场,谷歌很快就跳了进来,推出了它的 Nexus Player 和 Chromecast 设备:

上一张图片展示了谷歌 Chromecast 的最新型号。当谷歌进入这个市场时,他们决定为用户提供一个尽可能容易设置的设备。谷歌 Chromecast 有一个 HDMI 接口和一个 USB 电源线;仅此而已。你将 HDMI 连接到电视上,连接电源,现在你的电视就可以连接到你的智能手机了。
你的智能手机变成了遥控器,只需几点击,你就可以开始流式传输你想要的任何多媒体内容,直接到你的电视:你偏好的 YouTube 频道,你从 Google Play Store 偏好的电影,你从 Google Play Music 的音乐,以及数百个第三方应用都可以成为内容来源。
如果你不喜欢看电视,而你是一个音乐迷,谷歌的 Chromecast Audio 可以满足你的需求:

至于 Chromecast,Chromecast Audio 连接到你的 Hi-Fi 系统非常简单,并且可以通过你可以在 Android 智能手机上安装的 Chromecast 应用轻松设置。
其中一个关键特性是它可以通过家庭 Wi-Fi 系统建立独立的 Wi-Fi 连接,因此它可以被指令播放你的音乐,而不是要求你的手机将音乐流式传输到 Chromecast Audio。你可以使用智能手机控制它,但不会耗电,因为你的手机和 Chromecast Audio 之间没有持久的 Wi-Fi 或蓝牙连接。
玩具
现在,当我们想到由 Android 供电的玩具时,我们只能想到无人机!

最受欢迎的,如图中所示的 Parrot ARDrone,设定了标准并推动了市场的发展。随着时间的推移,出现了许多商业替代品,但就像智能镜子一样,遥控玩具社区也完全转向了 DIY。
在 2015 年都灵 Droidcon 黑客马拉松期间,我们展示了如何在 24 小时内使用 Android 设备通过 Wi-Fi 控制由 UDOO 供电的遥控车……www.hackster.io/team-dronix-alter-ego/dronixcar-37b81a?f=1#:

这款遥控车配备了视频摄像头,实时流媒体传输到智能手机。智能手机充当视频消费者和遥控器。
整个项目以开源的形式发布,遵循经典的 Android/Linux 传统。
摘要
我们的旅程结束了!这是一段相当刺激的过山车之旅,从操作系统的历史到它如何让设备准备好与外部世界通信。你学习了如何获取设备的源代码,如何导航源文件夹树,以及如何创建完美的设置来正确构建纯净的 Android 系统。
你开始升级系统,添加自定义功能,以丰富用户体验,提高性能,并添加对您自己的硬件的支持。你深入到引导序列的内部结构,以进一步自定义系统。然后你回到表面,自定义架构金字塔的最高部分,即用户界面,为用户提供终极定制的 Android 系统。
最后,你看到了如何轻松地摆脱 Android 设备本身,发现一个由 Android 平台驱动的设备世界,这些设备正等待通信和交互。
我们的旅程已经结束,但你的旅程才刚刚开始!掌握你所学的知识,进行实验,尝试你的想法,失败,学习更多,再次尝试,最终成功!
Android 是一个强大的工具;你可以用它将你疯狂的想法变成现实!




浙公网安备 33010602011771号