转:http://blog.csdn.net/luoshengyang/article/details/29688041html
前面101篇文章都是分析Android系统源码,彷佛不够接地气。若是能让Android系统源码在真实设备上跑跑看效果,那该多好。这不就是传说中的刷ROM吗?刷ROM这个话题是老罗之前一直避免谈的,由于以为没有全面了解Android系统前就谈ROM是不完整的。写完了101篇文章后,老罗以为第102篇文章该谈谈这个话题了,而且选择CM这个有表明性的ROM来谈,目标是加深你们对Android系统的了解。android
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!git
提及刷ROM的动机,除了上面说的用来看Android系统源码在真实设备上的运行效果,还有不少值得说的。回想起PC时代,咱们对咱们本身拥有的设备(电脑),基本上能作的就是在上面重装系统。这个系统是厂商作好给咱们的,里面有什么咱们就用什么,不能为所欲为地定制。固然,若是你用的是Linux系统,你是能够为所欲为地对它进行定制的。不过惋惜的是,咱们的女神用的都是Windows系统。你和女神说你想要什么样的Linux系统,我给你定制一个,她会不知道你说的是什么——她须要的是一个不会中毒的又跑得快的Windows系统而已。现现在虽然不少女神用的是仍然是咱们不能为所欲为定制的iOS系统,可是在移动设备上,iOS系统毕竟不能作到Windows在PC那样的一家独大——咱们还有很多女神是用Android系统的。因此,若是你如今和女神说,我能够帮你刷一个专属的精简Android系统,里面没有一堆你不须要的预装软件,会让你的手机跑得很快,那女神得有多崇拜你啊。github
固然,刷ROM的动机不能只是为了让女神崇拜,做为一个程序猿,咱们的首要任务是维护宇宙和平。怎么维护呢?至少程序有BUG不能不改吧。你不改的话,老板是不会放过你的。可是,碰到那些很棘手的BUG,怎么办呢?例如,你是一个Android应用开发者,调用一个API接口的时候,老是抛出一个异常,而这个异常不跟到API内部实现去看看实在是不知道什么缘由形成的。这时候,若是你手头上有这个手机的系统源代码,找这个API内部实现的地方,加一两行调试代码,再编译回手机上去跑,是否是就很容易定位问题了呢?算法
因此说,会刷ROM,不仅是能够得到女神崇拜,还能够维护世界和平,做为一个Android开发者,你还有什么理由不去学习刷ROM呢?既然你决定学习刷ROM了,那你就得先搞清楚两个问题:1. 什么是刷ROM;2. 怎么学习刷ROM。shell
在回答第一个问题以前,咱们先来看看Android设备从硬件到系统的结构,如图1所示:express
图1 Android系统架构apache
最底层的是各类硬件设备,往上一层是Bootloader。Bootloader是什么概念呢?咱们都知道,PC主板上有一小段程序叫作BIOS,主板加电时它是第一个跑起来的程序,负责初始化硬件,以及将OS启动起来。在嵌入式世界里(手机也是属于嵌入式设备),也有一小段相似BIOS的程序,不过它不叫BIOS,而是叫Bootloader。使用最普遍的Bootloader是一个叫uboot的程序,它支持很是多的体系结构。通过编译后,uboot会生成一个uboot.bin镜像,将这个镜像烧到设备上的一个特定分区去,就能够做为Bootloader使用了。服务器
Bootloader支持交互式启动,也就是咱们可让Bootloader初始化完成硬件以后,不是立刻去启动OS,而是停留在当前状态,等待用户输入命令告诉它接下来该干什么。这种启动模块就称为Fastboot模式。对于Android设备来讲,咱们能够经过adb reboot bootloader命令来让它从新启动而且进入到Fastboot模式中去。架构
在讨论Fastboot模式以前,咱们先了解一下嵌入式设备的ROM结构(NAND flash之类的芯片)。一般,一个可以正常启动的嵌入式设备的ROM包含有如下四个分区:
1. Bootloader分区,也就是存放uboot.bin的分区
2. Bootloader用来保存环境变量的分区
3. Kernel分区,也就是存放OS内核的分区
4. Rootfs分区,也就是存入系统第一个进程init对应的程序的分区
当设备处于Fastboot模式时,咱们能够经过另一个工具fastboot来让设备执行指定的命令。对搞机者来讲,最经常使用的命令就是刷入各类镜像文件了,例如,往Kernel分区和Rootfs分区刷入指定的镜像。
对于Android设备来讲,当它处于Fastboot模式时,咱们能够将一个包含有Kernel和Rootfs的Recovery.img镜像经过fastboot工具刷入到一个称为设备上一个称为Recovery的分区去。这个过程就是刷Recovery了,它也是属于刷ROM的一种。因为Recovery分区包含有Kernel和Rootfs,所以将Recovery.img刷入到设备后,咱们就可让设备正常地启动起来了。这种启动方式就称为Recovery模式。 对于Android设备来讲,咱们能够经过adb reboot recovery命令来让它进入到Recovery模式中去。
当设备处于Recovery模式时,咱们能够作些什么呢?答案是取决于刷入的Recovery.img所包含的Rootfs所包含的程序。更确切地说,是取决于Rootfs镜像里面的init程序都作了些什么事情。不过顾名思义,Recovery就是用来恢复系统的意思,也包含有更新系统的意思。这里所说的系统,是用户正常使用的系统,里面包含有Android运行时框架,使得咱们能够在上面安装和使用各类APP。
用户正常使用Android设备时的系统,主要是包含有两个分区:System分区和Boot分区。System分区包含有Android运行时框架、系统APP以及预装的第三方APP等,而Boot分区包含有Kernel和Rootfs。刷入到System分区和Boot分区的两个镜像称为system.img和boot.img,咱们一般将它们打包和压缩为一个zip文件,例如update.zip,而且将它上传到Android设备上的sdcard上去。这样当咱们进入到Recovery模式时,就能够在Recovery界面上用咱们以前上传到sdcard的zip包来更新用户正常使用Android设备时所用的系统了。这个过程就是一般所说的刷ROM了。
不知道你们看明白了没有?广义上的刷ROM,实际上包含更新Recovery和更新用户正常使用的系统两个意思;而狭义上的刷ROM,只是更新用户正常使用的那个系统。更新Recovery须要进入到Fastboot模式中,而更新用户正常使用的那个系统须要进入到Recovery模式中。Android设备在启动的过程当中,在默认状况下,一旦Bootloader启动完成,就会直接启动用户正常使用的那个系统,而不会进入到Recovery模式,或者停留在Bootloader中,也就是停留在Fastboot模式中。只有经过特定的命令,例如adb reboot recovery和adb reboot bootloader,或者特定的按键,例如在设备启动过程当中同时按住音量减少键和电源开关键,才能让设备进入到Recovery模式或者Fastboot模式中。
所以,一个完整的刷ROM过程,包含如下两个步骤:
1. 让设备进入到Fastboot模式,刷入一个recovery.img镜像
2. 让设备进入到Recovery模式,刷入一个包含system.img镜像和boot.img镜像的zip包
不过须要注意的是,system.img镜像和boot.img镜像不必定是只有在Recovery模式才能刷入,在Fastboot模式下也是能够刷入的,就像在Fastboot模式中刷入recovery.img镜像同样,只不过在Recovery模式下刷入它们更友好一些。说到这里,就不得不说另一个概念,就是所谓的Bootloader锁。在锁定Bootloader的状况下,咱们是没法刷入非官方的recovery.img、system.img和boot.img镜像的。这是跟厂商实现的Bootloader相关的,它们能够经过必定的算法(例如签名)来验证要刷入的镜像是不是官方发布的。在这种状况下,必需要对Bootloader进行解锁,咱们才能够刷入非官方的镜像。
好了,以上就回答了什么是刷ROM这个问题,接下来咱们要回答的是如常学习刷ROM这个问题。
前面咱们提到了刷ROM的两个步骤,实际上咱们还少了一个重要的步骤,那就是先要制做recovery.img、system.img和boot.img。有人可能会说,网上不是不少现成的刷机包吗?直接拿过来用不就是行了吗?可是别忘了前面咱们所说的刷ROM动机:为所欲为地定制本身的系统。去拿别人制好的刷机包就失去了为所欲为定制的能力。那就只能本身去编译AOSP源码生成刷机包了。
然而,从零开始从AOSP源码中编译出能在本身使用的手机上运行的系统,可不是一件容易的事情。不过,好在有不少现成的基于AOSP的第三方开源项目,能够编译出来在目前市场上大部分的手机上运行。其中,最著名的就是CyanogenMod了,简称CM。国内大部分的第三方Android系统,都是基于CM来开发的,包括MIUI和锤子。
选择CM来说解刷ROM的过程是本文的主题。不过单就刷ROM这个过程来讲,CM官网上已经有很详细的过程,包括从CM源码编译出适用特定机型的刷机包,以及将编译出来的刷机包刷到手机里面去的过程。所以,本文不是单纯地讲解刷ROM过程,而是要结合原理来说解刷ROM过程的关键步骤,来达到帮助你们更进一步地理解Android的目的。
要真正作到理解CM ROM的刷机原理,除了要对Android系统自己有必定的认识以外,还熟练掌握Android的源码管理系统和编译系统。所以,在继续阅读下面的内容以前,但愿能够先阅读前面Android源代码仓库及其管理工具Repo分析和Android编译系统简要介绍和学习计划这两个系列的文章。接下来咱们就开始讲解CM ROM的刷机过程和原理。
咱们首先是要准备好环境以及手机。这是本次操做所用的环境以及手机:
1. Ubuntu 13.04
2. CM-10.1
3. OPPO Find 5
也就是说,咱们将在Ubuntu 13.04上为OPPO Find 5制做CM-10.1的Recovery和ROM。
不过先别急着制做本身的ROM。为了保证接下来的步骤能够顺利执行,咱们首先尝试刷一下CM官方对应版本的Recovery和ROM到咱们的OPPO Find 5手机上。若是一切正常,就说明咱们使用CM的源码来制做的Recovery和ROM也是能够运行在OPPO Find 5上的。
适合OPPO Find 5的CM官方Recovery下载地址:http://download2.clockworkmod.com/recoveries/recovery-clockwork-6.0.4.6-find5.img。假设咱们下载好以后,保存在本地的路径为$CM/recovery-clockwork-6.0.4.6-find5.img。
适合OPPO Find 5的CM官方10.1.3版本ROM下载地址:http://download.cyanogenmod.org/get/jenkins/42498/cm-10.1.3-find5.zip。假设咱们下载好以后,保存在本地的路径为$CM/cm-10.1.3.find5.zip。
注意,因为咱们计划用CM-10.1源码来制做本身的ROM,因此咱们在下载CM官方ROM,也要下载对应10.1版本的。
在刷Recovery和ROM的过程当中,咱们须要借助于Android SDK里面的fastboot和adb工具,所以,为了方便执行这些命令,咱们先将这些工具的目录加入到PATH环境变量去。假设咱们下载的Android SDK保存在目录$ASDK中,那么打开一个终端,执行如下命令便可:
先刷Recovery,步骤以下所示:
1. 保持OPPO Find 5在正常开机状态,而且通USB链接到将有Ubuntu 13.04的电脑上。
2. 仍是在刚才打开的终端上,而且进入到保存recovery-clockwork-6.0.4.6-find5.img的目录$CM。
3. 执行如下命令让OPPO Find 5重启,而且进入Fastboot模式。
4. 能够看到OPPO Find 5停留在Fastboot界面上,执行如下命令确保fastboot工具可以链接到OPPO Find 5。
若是可以链接,那么上述命令将会输出一串标识OPPO Find 5的ID。
5. 刷入咱们刚才下载的Recovery。
6. 提示刷入成功后,执行如下命令正常重启手机。
若是一切正常,手机将进入到原来的系统中。
继续在上述打开的终端上,刷CM-10.1.3 ROM,步骤以下所示:
1. 将下载好的cm-10.1.3.find5.zip上传至OPPO Find 5的sdcard上
2. 执行如下命令让OPPO Find 5重启,而且进入Recovery模式。
进入到Recovery模式后,咱们将看到显示的Recovery版本号为6.0.4.6,这代表咱们如今进入的就是刚才咱们刷入的Recovery。
3. 在刷入新的ROM前,咱们先备份一下当前的ROM,以防万一刷机失败,能够进行恢复。在Recovery界面中,经过音量增大/减少键,选中“backup and restore”选项,按下电源键,进入下一个界面,一样是经过音量增大/减少键,选中“backup”,按下电源键,就能够对当前系统进行备份了。
4. 备份完成以后,咱们还要清除手机上的数据,恢复至出厂设置。回到Recovery界面中,经过音量增大/减少键,选中"wipe data/factory reset",按下电源键,确认后便可进行清除数据,而且恢复至出厂设置。
5. 清除数据完成以后,再回到Recovery界面上,经过音量增大/减少键,选中“install zip”选项,按下电源键,进入下一个界面,一样是经过音量增大/减少键,选中“choose zip from sdcard”,按下电源键,找到前面咱们上传至sdcard的cm-10.1.3.find5.zip,确认以后就能够进行刷机了。
6. 刷机完成后,再回到Recovery界面上,经过音量增大/减少键,选中“reboot system now”选项,按下电源键,正常启动系统。
若是一切正常,手机将进入到刚才刷入的CM-10.1.3系统中。
如今咱们就能够肯定OPPO Find 5能够正常运行CM-10.1.3的系统了。接下来激动人心的时刻就要开始了,咱们将要本身下载和编译CM-10.1源码,而且将编译出来的Recovery和ROM刷入到OPPO Find 5去。同时,在接下来的步骤中,咱们会将相关的原理讲清楚,以便咱们能够更好地理解Android系统的结构,这也是本文的重点之一。如下假设咱们将CM-10.1源码保存在目录$CMSOURCE中,而且已经按照Android官网文档的要求初始化好Android的源码编译环境,即在咱们的Ubuntu机器上安装了要求的软件,详情请参考:http://source.android.com/source/initializing.html。
1. 进入到$CMSOURCE目录中。
2. 将当前目录初始为CM-10.1分支源码的Git仓库。
3. 下载CM-10.1分支源码。
以上两步都是关于Android源码仓库的知识,能够参考Android源代码仓库及其管理工具Repo分析一文,这里再也不详述。
4. 进入到$CMSOURCE目录下的vendor/cm子目录中,而且执行里面的get-prebuilts脚本,用来得到一些预编译的APP。
打开$CMSOURCE/vendor/cm/get-prebuilts文件,它的内容以下所示:
咱们能够发现,实际上这里只是去下载一个叫作Android Terminal Emulator的APP,地址是http://jackpal.github.com/Android-Terminal-Emulator/downloads/Term.apk。这个APP最终会包含在咱们本身编译出来的ROM。它用来Android手机上模拟出一个终端来,而后咱们就能够像在Linux主机上同样执行一些经常使用的Linux命令。是否是很酷呢?原来Android手机不单止能够运行咱们常见的APP,还能够运运咱们经常使用的Linux命令。关于这个Android Terminal Emulator的安装和介绍,参能够这里:https://github.com/jackpal/Android-Terminal-Emulator。此外,这个Android Terminal Emulator还能够配合另一个封装了busybox的kbox工具,用来在Android手机上得到更多的Linux经常使用命令,kbox的安装和介绍,能够参考这里:http://kevinboone.net/kbox2_install.html。
5. 回到$CMSOURCE目录中,将build子目录下的envsetup.sh脚本加载到当前终端来。
参考Android编译系统环境初始化过程分析一文,envsetup.sh脚本加载到当前终端后,咱们就能够得到一系列与Android编译系统相关的命令,例如lunch/m/mm/mmm,以及下一步要执行的breakfast命令。
6. 为OPPO Find 5下载相关的源码。
在Android编译系统简要介绍和学习计划这个系列的文章中,咱们提到,在编译Android的官方源码以前,咱们须要执行一个lunch命令来为咱们的目标设备初始化编译环境。这个lunch命令是由Android官方源码的envsetup.sh脚本提供的。CM修改envsetup.sh脚本,额外提供了一个breakfast命令,用来从网上寻找指定的设备相关的源码,以便咱们能够为该设备编译出能运行的ROM来。
打开envsetup.sh文件,查看breakfast的实现:
函数breakfast主要是作了如下两件事情。
第一件事情是检查vendor/cm目录下是否存在一个vendorsetup.sh文件。若是存在的话,就将它加载到当前终端来。注意,这里是经过ls命令来检查文件endor/cm/vendorsetup.sh是否存在的。若是不存在的话,标准输出就为空,而错误信息会重定向至/dev/null。若是存在的话,字符串“endor/cm/vendorsetup.sh”就会输出到标准输出来,也就是变量f的值会等于“endor/cm/vendorsetup.sh”。
接下来咱们就看看文件endor/cm/vendorsetup.sh的内容:
它所作的工做就是将https://raw.github.com/CyanogenMod/hudson/master/cm-build-targets的内容下载回来,而且去掉其中的空行,最后将含有"cm-10.1"的行的第1列取出来,而且经过add_lunch_combo命令将其加入到Android编译系统的lunch菜单去。
从https://raw.github.com/CyanogenMod/hudson/master/cm-build-targets下载回来的是官方CM所支持的机型列表,格式为cm_<product>-<variant> <version>,如下列出的是部份内容:
因而可知,执行脚本endor/cm/vendorsetup.sh以后,cm-10.1所支持的机型就会增长到lunch菜单中去。
回到函数breakfast中,它所作的第二件事情是检查执行函数是否带有参数,即变量target的值是否等于空。若是变量target的值不等于空,而且它的值是<product>-<variant>的形式,那么就直接以它为参数,调用lunch函数。不然的话,就以cm_$target-userdebug为参数,调用lunch函数。
在这一步中,咱们调用breakfast函数时,传进来的参数为find5,不是<product>-<variant>的形式,所以,函数breakfast最后会以cm_find5-userdebug为参数,调用lunch函数。
函数lunch的实现咱们在前面一篇文章Android编译系统环境初始化过程分析已经分析过了,不过当时分析的是AOSP官方版本的实现,CM对其进行了一些修改,增长了一些CM自有的逻辑,下面咱们就看看这些修改:
这里的变量selection的值就等于咱们传进来的参数"cm_find5-userdebug",经过sed命令将"cm_find5"提取出来,而且赋值给变量product。接下来调用check_product函数来检查当前的CM源码中是否支持find5这个设备。
若是不支持的话,那么它的返回值就不等于0,即#?不等于0,那么接下来就会经过build/tools/roomservice.py到CM源码服务器去检查是否支持find5这个设备。若是CM源码服务器支持find5这个设备的话,那么build/tools/roomservice.py就会将与find5相关的源码下载回来。这时候咱们就会发现本地CM源码目录中多了一个device/oppo/find5目录。里面存放的都是编译find5的ROM时所要用的文件。
另外一方面,若是当前的CM源码中已经支持find5这个设备,那么函数lunch也会调用build/tools/roomservice.py去CM源码服务器检查当前CM源码目录中find5设备依赖的其它源码是否有更新,或者是否有新的依赖。若是有的话,就将这些依赖更新下载回来。
脚本build/tools/roomservice.py的详细内容这里就不分析了,下面主要是解释一下与Android的源码仓库管理工具Repo相关的逻辑。关于Android的源码仓库管理工具Repo的详细分析,能够参考Android源代码仓库及其管理工具Repo分析一文。
CM源码服务器放在github上,地址为http://github.com/CyanogenMod,上面保存的是CM修改过的AOSP工程、CM支持的设备相关源码工程(下载回来放在device/<manufacturer>/<device>目录中),以及CM支持的设备对应的内核源码工程(下载回来放在kernel/<manufacturer>/<device>目录中)。
脚本build/tools/roomservice.py会根据传进来的第一个参数,到CM源码服务上检查是否存在相应的工程。在咱们这个场景中,传给build/tools/roomservice.py的第一个参数为cm_find5。这时候前面的cm_会被去掉,而后到CM源码服务上检查是否存在一个android_device_<manufacturer>_find5的工程。若是存在的话,那么就会将它下载回来,保存在device/<manufacturer>/find5目录中。这里的<manufacturer>对应的就是oppo了。
下载回来的设备相关源码其实是做为是一个Git仓库来管理的,所以,脚本build/tools/roomservice.py还须要将该Git仓库归入到Repo仓库去管理,以便之后执行repo sync命令时,能够同时对这些设备相关的源码进行更新。
从Android源代码仓库及其管理工具Repo分析一文能够知道,Repo仓库保存在.repo目录中,而它所管理的Git仓库由.repo/manifest.xml文件描述。文件.repo/manifest.xml实际上只是一个符号连接,它连接至.repo/manifests/default.xml文件。目录.repo/manifests实际上也是一个Git仓库,用来描述当前的CM源码目录都是由哪些工程构成的,而且这些工程是来自于哪些Git远程仓库的。
若是按照标准的Repo仓库管理方法,从CM源码服务器上下载回来设备相关源码以后,应该往.repo/manifests/default.xml文件增长相应的描述,之后repo工具能够对这些设备相关的源码进行管理。可是,因为Repo仓库是由官方维护的,当咱们在本地往.repo/manifests/default.xml增长了新的内容以后,下次执行repo sync命令时,.repo/manifests/default.xml的内容又会被恢复至修改前的样子,所以,修改.repo/manifests/default.xml文件是不适合的。CM采用另一个办法,那就是在.repo目录下另外建立一个local_manifests目录,在里面能够随意增长任意命名的xml文件,只要这些xml文件的规范与.repo/manifests/default.xml文件的规范一致便可。执行repo sync命令时,它就会同时从.repo/manifests/default.xml和.repo/local_manifests目录下的xml文件中读取当前都有哪些源码工程须要更新。
实际上,在.repo/local_manifests目录下的xml文件,除了能够描述新增的工程以外,还能够描述要删除的工程。例如,若是咱们不想将某一个系统功能或者系统APP编译到咱们本身制做的ROM去,那么就能够在.repo/local_manifests目录下增长一个xml文件,里面描述咱们须要删除对应的工程。这样,当咱们从服务器下载回来相应的工程以后,它们就会在本地中被删除。这样就作到了很好的定制化编译,并且又不会与官方的源码结构产生冲突。关于CM的Local Manifests机制,能够参考官方文档:http://wiki.cyanogenmod.org/w/Doc:_Using_manifests。
脚本build/tools/roomservice.py将下载回来的设备相关源码归入到Repo仓库管理的办法就是在.repo/local_manifests目录下建立一个roomservice.xml文件。例如,当咱们从CM源码服务器下载回来find5相关的设备源码以后,就能够看到在roomservice.xml文件中看到相应的一行内容:
这代表本地的device/oppo/find5目录是来自于远程仓库github的,而且相对路径为CyanogenMod/android_device_oppo_find5。
好了,如今咱们终于将OPPO Find 5相关的设备源码下载回来了,可是在编译以前。须要从OPPO Find 5上提取一些设备相关的私有文件。
7. 保持OPPO Find 5开机状态,而且经过USB链接到Ubuntu 13.04上,进行到$CMSOURCE/device/oppo/find5目录中,执行如下命令提取设备私有文件。
脚本extract-files.sh的内容以下所示:
首先是建立一个vendor/oppo/find5/proprietary目录,接着是读取文件proprietary-blobs.txt中的每一行,而且将每一行所描述的文件从设备上的/system目录中获取出来,保存在vendor/oppo/find5/proprietary对应的子目录下面,最后再执行另一个脚本setup-makefiles.sh。
文件device/oppo/find5/proprietary-blobs.txt部分的内容以下所示:
这里列出的文件路径都是相对于设备上的/system目录的,而且都是设备特定的、不公开源码的,所以,咱们须要从设备上获取出来。
再来看脚本setup-makefiles.sh的内容:
这个脚本主要就是用来在vendor/oppo/find5目录下生成两个文件:find5-vendor-blobs.mk和BoardConfigVendor.mk文件。这两个文件都是接下来为OPPO Find 5编译ROM时要用到的。
生成的find5-vendor-blobs.mk的部份内容以下所示:
实际上就是经过PRODUCT_COPY_FILES变量告诉编译系统,要将刚才从设备上获取回来的文件打包到编译出来的ROM的system分区里面去。
生成的文件BoardConfigVendor.mk只有1行,以下所示:
在编译摄像头相关的库文件时,就会到这里定义的USE_CAMERA_STUB变量。
到目前为止,咱们就分别从CM源码服务器和目标设备上获取到了编译OPPO Find 5的ROM所须要的设备相关的文件了,接下来就能够开始编译CM源码了。
8. 回到$CMSOUCE目录中,为OPPO Find 5编译ROM。
命令brunch也是由build/envsetup.sh脚本提供的,它的实现以下所示:
函数brunch作了两件事情:执行breakfast命令和执行mka bacon命令。
前面咱们不是已经执行过breakfast命令了吗?这里为何又要再执行一次呢?从前面的分析能够知道,函数breakfast在执行的过程当中,会调用函数lunch,而函数lunch又会调用函数check_product来检查目标设备是否存在。由于在前一步中,咱们已经将目标设备相关的源码下载回来了,所以这时候目标设备是确定存在的。当目标设备存在的时候,函数lunch会经过build/tools/roomservice.py脚本检查目标设备相关的源码是否依赖有其它工程,而且这些工程是否已经下载回来了。若是有依赖的工程,而且这些工程尚未下载回来,那么就须要将它们下载回来。
当目标设备存在的时候,函数lunch执行build/tools/roomservice.py脚本的形式为:
传递给build/tools/roomservice.py第二件参数为true,表示要处理的是目标设备$product依赖的工程。
脚本build/tools/roomservice.py是如何知道目标设备有没有依赖的工程的呢?原来,在下载回来的设备源码中,有一个cm.dependencies文件,里面描述了本身所依赖的工程。例如,device/oppo/find5/cm.dependencies的内容以下所示:
上面描述的是OPPO Find 5所使用的内核源码,它来自于CM源码服务器上的android_kernel_oppo_find5仓库的cm-10.1分支,下载回来后保存在kernel/oppo/find5目录中。这意味着咱们经过CM源码编译出来的ROM所使用的内核也是由咱们本身编译出来的。
为了之后执行repo sync命令同步本地源码时,也能够将设备源码依赖的工程也同时同步回来,咱们须要将这些依赖工程也归入到Repo仓库中去,所以,当再次执行过breakfast使命以后。咱们就能够在.repo/local_manifests/roomservice.xml文件中发现如下两行内容:
回到函数brunch中,它接下来执行的命令mka也是由build/envsetup.sh脚本提供的,以下所示:
它其实是经过一个叫作schedtool的工具来调用make工具对源码进行编译。咱们知道,编码Android源码是一个漫长的过程,而如今的机器都是多核的,为了加快这个编译过程,须要将机器的全部核以都充分利用起来。工具schedtool所作的事情就是充分地利机器的多核特性来执行make命令,使得咱们能够尽快结束编译过程。
关于Android源码的编译详细过程,能够参考Android编译系统简要介绍和学习计划这个系列的文章,这里只进行简要的说明。
从Android编译系统简要介绍和学习计划这个系列一文能够知道,Android的编译系统是由不少的mk文件组成的,每个mk文件都是一个Makefile脚本片断。在编译开始以前,这些mk文件会组合在一块儿,造成一个很大的Makefile文件,而后再根据这个很大的Makefile文件的指令对源码进行编译。
由这些mk文件组成的Makefile文件内容能够抽象为四个层次,如图2所示:
图2 Android编译系统层次
最下面一层描述的设备CPU的体系架构(Architecture),Android设备支持arm、x86和mips三种CPU体系架构。再往上一层描述的是设备所使用的芯片(Board),例如用的是高通的芯片,仍是三星的芯片,等等,与这些芯片相关的源文件存放在hardware目录下。接下来再往上的一层是设备(Device),描述的是具体的硬件设备。最上面的一层是产品(Product),描述的是在硬件设备上运行的软件模块。
在这一步中,咱们经过brunch命令编译CM源码时,指定的惟一参数是find5,那么Android编译系统是如何根据这个参数来找包含上述四个层次的mk文件为OPPO Find 5编译出能正常运行的ROM的呢?
在咱们执行breakfast或者brunch命令的过程当中,会调用另一个函数check_product。根据Android编译系统环境初始化过程分析一文,函数check_product会经过另一个函数get_build_var加载build/core/config.mk文件。文件build/core/config.mk又会继续加载另一个文件build/core/envsetup.mk。最后,文件build/core/envsetup.mk又会加载另一个文件build/core/product_config.mk。
在build/core/product_config.mk文件的加载过程当中,有如下的一段逻辑:
当咱们编译的是整个Android源码时,变量TARGET_BUILD_APPS的值等于空。这时候就会判断是否设置了一个名称为CM_BUILD的环境变量。若是设置了的话,那么接下来就会加载device/*/$(CM_BUILD)目录下的cm.mk文件来得到目标产品配置文件。不然,首先会经过调用另一个函数get-all-product-makefiles来得到/device/*/*目录下的全部名称为AndroidProducts.mk的文件,接着再在这些AndroidProducts.mk文件中定义的PRODUCT_MAKEFILES变量来得到目标产品配置文件。
环境变量CM_BUILD的值是在函数check_product中设置的,而且只有在CM源码中编译时才会设置。在AOSP源码编译时,是不会设置环境变量CM_BUILD的。CM版本的check_product函数实现以下所示:
从这里就能够看出,环境变量CM_BUILD的值实际上就是目标设备的名称。例如,前面咱们执行breakfast命令时,经过函数lunch调用check_product函数时,传进来的参数为为cm_find5,函数check_product会将find5提取出来,而且赋值给环境变量CM_BUILD。这样在加载build/core/product_config.mk文件时,找到的产品配置文件就为device/*/find5/cm.mk文件。在device目录中,只有oppo子目录包含有find5这个目录,所以最终加载的其实是device/oppo/find5/cm.mk文件。
不管是经过环境变量CM_BUILD来直接得到的目标产品配置文件,仍是经过AndroidProducts.mk文件间接得到的目标产品配置文件,得到的目标产品配置文件都会直接或者间接地经过变量PRODUCT_COPY_FILES和PRODUCT_PACKAGES来设备要包含的软件模块。
以device/oppo/find5/cm.mk为例,它的部份内容以下所示:
除定了一些设备相关的变量以外,它还会加载另一个文件device/oppo/find5/full_find5.mk,后者的部份内容以下所示:
文件device/oppo/find5/full_find5.mk又会继续加载另外两个文件device/oppo/find5/device.mk和vendor/oppo/find5/find5-vendor.mk。
在文件device/oppo/find5/device.mk中,咱们就能够看到PRODUCT_COPY_FILES和PRODUCT_PACKAGES的定义:
而文件vendor/oppo/find5/find5-vendor.mk会加载另一个文件vendor/oppo/find5/find5-vendor-blobs.mk,以下所示:
回忆前面的第7步,文件文件vendor/oppo/find5/find5-vendor-blobs.mk是咱们执行extract-files.sh脚本的时候生成的,里面经过变量PRODUCT_COPY_FILES告诉编译系统将从设备上得到的私有文件打包到要制做的ROM去。
以上就是Product级别的配置信息的获取过程,接下来咱们再看Device、Board和Architecture级别的配置信息的获取过程。
函数check_product会经过另一个函数get_build_var来加载build/core/config.mk文件的过程当中,会在目标产品对应的设备目录中找到一个名称为BoardConfig.mk文件,以下所示:
上述Makefile脚本片断会在build/target/board、device/*、vendor/*目录中寻找一个名称为$(TARGET_DEVICE)的子目录,而且在找到的子目录里面加载一个名称为BoardConfig.mk文件,来得到Device、Board和Architecture级别的配置信息。
变量TARGET_DEVICE指向的即是目标设备的名称。在咱们这个场景中,它的值就等于find5,所以,最终得到的Device、Board和Architecture级别的配置信息就是来自于device/oppo/find5/BoardConfig.mk文件,它的部份内容以下所示:
从这里就能够看到各类Device、Board和Architecture级别的配置信息。例如,CPU体系架构由TARGET_ARCH、TARGET_CPU_ABI和TARGET_CPU_ABI2来描述。芯片类型由TARGET_BOARD_PLATFORM来描述,而设备信息的描述则包括Bluetooth、Wifi、Display、GPS、Camera和Audio等。
在BoardConfig.mk文件中配置的编译信息是在何时用到的呢?如下咱们就以TARGET_BOARD_PLATFORM为例,说明这些配置信息在编译的过程当中是如何使用的。在Android源码中,hardware目录包含各类芯片相关的模块源码。在这些模块的编译脚本中,就会根据TARGET_BOARD_PLATFORM的值来为指定的芯片编译出正确的模块来。
例如,假设咱们使用的是高通芯片,在它的Camera HAL2模块源码目录hardware/qcom/camera/QCamera/HAL2/core中定义的Android.mk文件有以下的内容:
上述Makefile脚本片断会根据TARGET_BOARD_PLATFORM的值不一样而设置不一样的LOCAL_CFLAGS值,以即可觉得目标设备编译出正确的HAL模块来。
这一步执行完成以后,也就是编译完成以后,咱们就能够在out/target/product/find5目录中看到两个文件:recovery.img和cm-10.1-xxxxxxxx-UNOFFICIAL-find5.zip。其中,recovery.img是Recovery文件,而cm-10.1-xxxxxxxx-UNOFFICIAL-find5.zip是ROM文件。有了这两个文件以后,咱们就能够参照前面的介绍,将它们刷入咱们的OPPO Find 5手机上去了。当刷入完成,而且能够正常运行以后,如今咱们的手机上面运行的Recovery和ROM都是咱们本身亲手打造的了!
至此,咱们就介绍完成CM的刷机过程和原理了,是否是以为本身亲手去编译一个ROM是一件很容易的事呢?其实否则。咱们上面编译的ROM之因此这么轻松,是由于CM已经为咱们作了不少的工做,那就是已经在CM源码服务器上准备好了全部设备相关的源码,也就是咱们下载回来以后存放在device目录下的源码。若是咱们手头上有一部CM官方不支持的手机,那么CM源码服务器上是没有对应的设备源码存在的,这时候咱们就须要本身去开发对应的设备源码了。这是一个艰苦的过程,须要不停的调试、调试、再调试,也许要花上几周,甚至一个月来完成这个工做。固然,这个过程也是有一些经验和技巧能够参考的,具体能够参考CM文档:http://wiki.cyanogenmod.org/w/Doc:_porting_intro。这里就再也不详述。
最后,本文之因此选择CM这个第三方ROM和源码来说解,是由于CM官方提供了很全面的资料供咱们去学习如何基于AOSP源码来制做ROM,这样可使咱们少走不少弯路!另外,上面所述的ROM过程都是参考了CM官方文档的。更多的CM刷ROM教程,能够参考:http://wiki.cyanogenmod.org/w/Development。更多信息也能够关注老罗的新浪微博:http://weibo.com/shengyangluo。