首页 > 代码库 > 从CM刷机过程和原理分析Android系统结构
从CM刷机过程和原理分析Android系统结构
前面101篇文章都是分析Android系统源代码,似乎不够接地气。
假设能让Android系统源代码在真实设备上跑跑看效果,那该多好。这不就是传说中的刷ROM吗?刷ROM这个话题是老罗曾经一直避免谈的,由于认为没有全面了解Android系统前就谈ROM是不完整的。写完了101篇文章后。老罗认为第102篇文章该谈谈这个话题了,而且选择CM这个有代表性的ROM来谈。目标是加深大家对Android系统的了解。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入。
说起刷ROM的动机,除了上面说的用来看Android系统源代码在真实设备上的运行效果,还有非常多值得说的。
回忆起PC时代,我们对我们自己拥有的设备(电脑)。基本上能做的就是在上面重装系统。
这个系统是厂商做好给我们的。里面有什么我们就用什么。不能随心所欲地定制。当然,假设你用的是Linux系统。你是能够随心所欲地对它进行定制的。只是可惜的是,我们的女神用的都是Windows系统。你和女神说你想要什么样的Linux系统,我给你定制一个,她会不知道你说的是什么——她须要的是一个不会中毒的又跑得快的Windows系统而已。现如今尽管非常多女神用的是仍然是我们不能随心所欲定制的iOS系统,可是在移动设备上,iOS系统毕竟不能做到Windows在PC那样的一家独大——我们还有不少女神是用Android系统的。
所以,假设你如今和女神说,我能够帮你刷一个专属的精简Android系统。里面没有一堆你不须要的预装软件,会让你的手机跑得非常快,那女神得有多崇拜你啊。
当然。刷ROM的动机不能仅仅是为了让女神崇拜,作为一个程序员,我们的首要任务是维护宇宙和平。
怎么维护呢?至少程序有BUG不能不改吧。你不改的话,老板是不会放过你的。
可是。碰到那些非常棘手的BUG,怎么办呢?比如,你是一个Android应用开发人员,调用一个API接口的时候,总是抛出一个异常,而这个异常不跟到API内部实现去看看实在是不知道什么原因造成的。这时候,假设你手头上有这个手机的系统源代码。找这个API内部实现的地方。加一两行调试代码。再编译回手机上去跑,是不是就非常easy定位问题了呢?
所以说,会刷ROM,不仅仅是能够获得女神崇拜,还能够维护世界和平,作为一个Android开发人员,你还有什么理由不去学习刷ROM呢?既然你决定学习刷ROM了。那你就得先搞清楚两个问题:1. 什么是刷ROM;2. 怎么学习刷ROM。
在回答第一个问题之前,我们先来看看Android设备从硬件到系统的结构。如图1所看到的:
图1 Android系统架构
最底层的是各种硬件设备。往上一层是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源代码中编译出能在自己使用的手机上运行的系统。可不是一件easy的事情。只是,好在有非常多现成的基于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中,那么打开一个终端,运行下面命令就可以:
$ export PATH=$ASDK/platform-tools:$PATH先刷Recovery,过程例如以下所看到的:
1. 保持OPPO Find 5在正常开机状态,而且通USB连接到将有Ubuntu 13.04的电脑上。
2. 还是在刚才打开的终端上,而且进入到保存recovery-clockwork-6.0.4.6-find5.img的文件夹$CM。
$ cd $CM3. 运行下面命令让OPPO Find 5重新启动,而且进入Fastboot模式。
$ adb reboot bootloader4. 能够看到OPPO Find 5停留在Fastboot界面上,运行下面命令确保fastboot工具能够连接到OPPO Find 5。
$ fastboot devices假设能够连接,那么上述命令将会输出一串标识OPPO Find 5的ID。
5. 刷入我们刚才下载的Recovery。
$ fastboot flash recovery recovery-clockwork-6.0.4.6-find5.img6. 提示刷入成功后,运行下面命令正常重新启动手机。
$ fastboot reboot假设一切正常,手机将进入到原来的系统中。
继续在上述打开的终端上,刷CM-10.1.3 ROM,过程例如以下所看到的:
1. 将下载好的cm-10.1.3.find5.zip上传至OPPO Find 5的sdcard上
$ adb push cm-10.1.3.find5.zip /sdcard/cm-10.1.3.find5.zip2. 运行下面命令让OPPO Find 5重新启动,而且进入Recovery模式。
$ adb reboot 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文件夹中。
$ cd $CMSOURCE2. 将当前文件夹初始为CM-10.1分支源代码的Git仓库。
$ repo init -u git://github.com/CyanogenMod/android.git -b cm-10.13. 下载CM-10.1分支源代码。
$ repo sync以上两步都是关于Android源代码仓库的知识,能够參考Android源代码仓库及其管理工具Repo分析一文。这里不再详述。
4. 进入到$CMSOURCE文件夹下的vendor/cm子文件夹中,而且运行里面的get-prebuilts脚本,用来获得一些预编译的APP。
$ ./get-prebuilts打开$CMSOURCE/vendor/cm/get-prebuilts文件,它的内容例如以下所看到的:
BASEDIR=`dirname $0` mkdir -p $BASEDIR/proprietary # Get Android Terminal Emulator (we use a prebuilt so it can update from the Market) curl -L -o $BASEDIR/proprietary/Term.apk -O -L http://jackpal.github.com/Android-Terminal-Emulator/downloads/Term.apk unzip -o -d $BASEDIR/proprietary $BASEDIR/proprietary/Term.apk lib/*我们能够发现,实际上这里仅仅是去下载一个叫做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脚本载入到当前终端来。
$ source build/envsetup.sh參考Android编译系统环境初始化过程分析一文,envsetup.sh脚本载入到当前终端后,我们就能够获得一系列与Android编译系统相关的命令,比如lunch/m/mm/mmm,以及下一步要运行的breakfast命令。
6. 为OPPO Find 5下载相关的源代码。
$ breakfast find5在Android编译系统简要介绍和学习计划这个系列的文章中,我们提到。在编译Android的官方源代码之前,我们须要运行一个lunch命令来为我们的目标设备初始化编译环境。这个lunch命令是由Android官方源代码的envsetup.sh脚本提供的。CM改动envsetup.sh脚本。额外提供了一个breakfast命令,用来从网上寻找指定的设备相关的源代码,以便我们能够为该设备编译出能运行的ROM来。
打开envsetup.sh文件。查看breakfast的实现:
function breakfast() { target=$1 CM_DEVICES_ONLY="true" unset LUNCH_MENU_CHOICES add_lunch_combo full-eng for f in `/bin/ls vendor/cm/vendorsetup.sh 2> /dev/null` do echo "including $f" . $f done unset f if [ $# -eq 0 ]; then # No arguments, so let‘s have the full menu lunch else echo "z$target" | grep -q "-" if [ $?-eq 0 ]; then # A buildtype was specified, assume a full device name lunch $target else # This is probably just the CM model name lunch cm_$target-userdebug fi fi return $? }
函数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的内容:
for combo in $(curl -s https://raw.github.com/CyanogenMod/hudson/master/cm-build-targets | sed -e ‘s/#.*$//‘ | grep cm-10.1 | awk {‘print $1‘}) do add_lunch_combo $combo done它所做的工作就是将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>,下面列出的是部分内容:
# CM build target list # <lunchcombo> <branch> [period: "D"aily, "W"eekly or "M"onthly] # Absence of a period indicates Daily (the default) cm_a700-userdebug cm-11.0 cm_acclaim-userdebug cm-11.0 cm_amami-userdebug cm-11.0 cm_anzu-userdebug jellybean M cm_apexqtmo-userdebug cm-11.0 cm_aries-userdebug cm-11.0 cm_captivatemtd-userdebug cm-11.0 ......由此可见。运行脚本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自有的逻辑,下面我们就看看这些改动:
function lunch() { ...... local product=$(echo -n $selection | sed -e "s/-.*$//") check_product $product if [ $?这里的变量selection的值就等于我们传进来的參数"cm_find5-userdebug",通过sed命令将"cm_find5"提取出来,而且赋值给变量product。-ne 0 ] then # if we can‘t find a product, try to grab it off the CM github T=$(gettop) pushd $T > /dev/null build/tools/roomservice.py $product popd > /dev/null check_product $product else build/tools/roomservice.py $product true fi ...... }
接下来调用check_product函数来检查当前的CM源代码中是否支持find5这个设备。
假设不支持的话,那么它的返回值就不等于0。即#?不等于0。那么接下来就会通过build/tools/roomservice.py到CM源代码server去检查是否支持find5这个设备。假设CM源代码server支持find5这个设备的话,那么build/tools/roomservice.py就会将与find5相关的源代码下载回来。这时候我们就会发现本地CM源代码文件夹中多了一个device/oppo/find5文件夹。里面存放的都是编译find5的ROM时所要用的文件。
还有一方面,假设当前的CM源代码中已经支持find5这个设备。那么函数lunch也会调用build/tools/roomservice.py去CM源代码server检查当前CM源代码文件夹中find5设备依赖的其它源代码是否有更新。或者是否有新的依赖。假设有的话,就将这些依赖更新下载回来。
脚本build/tools/roomservice.py的具体内容这里就不分析了,下面主要是解释一下与Android的源代码仓库管理工具Repo相关的逻辑。关于Android的源代码仓库管理工具Repo的具体分析,能够參考Android源代码仓库及其管理工具Repo分析一文。
CM源代码server放在github上,地址为http://github.com/CyanogenMod。上面保存的是CM改动过的AOSPproject、CM支持的设备相关源代码project(下载回来放在device/<manufacturer>/<device>文件夹中)。以及CM支持的设备相应的内核源代码project(下载回来放在kernel/<manufacturer>/<device>文件夹中)。
脚本build/tools/roomservice.py会依据传进来的第一个參数,到CM源代码服务上检查是否存在相应的project。在我们这个场景中,传给build/tools/roomservice.py的第一个參数为cm_find5。这时候前面的cm_会被去掉。然后到CM源代码服务上检查是否存在一个android_device_<manufacturer>_find5的project。假设存在的话,那么就会将它下载回来。保存在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源代码文件夹都是由哪些project构成的,而且这些project是来自于哪些Git远程仓库的。
假设依照标准的Repo仓库管理方法,从CM源代码server上下载回来设备相关源代码之后,应该往.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文件里读取当前都有哪些源代码project须要更新。
实际上,在.repo/local_manifests文件夹下的xml文件,除了能够描写叙述新增的project之外,还能够描写叙述要删除的project。
比如,假设我们不想将某一个系统功能或者系统APP编译到我们自己制作的ROM去,那么就能够在.repo/local_manifests文件夹下添加一个xml文件。里面描写叙述我们须要删除相应的project。这样,当我们从server下载回来相应的project之后,它们就会在本地中被删除。这样就做到了非常好的定制化编译。而且又不会与官方的源代码结构产生冲突。
关于CM的Local Manifests机制,能够參考官方文档:http://wiki.cyanogenmod.org/w/Doc:_Using_manifests。
脚本build/tools/roomservice.py将下载回来的设备相关源代码纳入到Repo仓库管理的办法就是在.repo/local_manifests文件夹下创建一个roomservice.xml文件。比如。当我们从CM源代码server下载回来find5相关的设备源代码之后,就能够看到在roomservice.xml文件里看到相应的一行内容:
<?xml version="1.0" encoding="UTF-8"?> <manifest> ...... <project name="CyanogenMod/android_device_oppo_find5" path="device/oppo/find5" remote="github" /> ...... </manifest>这表明本地的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脚本extract-files.sh的内容例如以下所看到的:
#!/bin/sh VENDOR=oppo DEVICE=find5 BASE=../../../vendor/$VENDOR/$DEVICE/proprietary rm -rf $BASE/* for FILE in `cat proprietary-blobs.txt | grep -v ^# | grep -v ^$ | sed -e ‘s#^/system/##g‘`; do DIR=`dirname $FILE` if [ ! -d $BASE/$DIR ]; then mkdir -p $BASE/$DIR fi adb pull /system/$FILE $BASE/$FILE done ./setup-makefiles.sh
首先是创建一个vendor/oppo/find5/proprietary文件夹。接着是读取文件proprietary-blobs.txt中的每一行,而且将每一行所描写叙述的文件从设备上的/system文件夹中获取出来,保存在vendor/oppo/find5/proprietary相应的子文件夹下面,最后再运行另外一个脚本setup-makefiles.sh。
文件device/oppo/find5/proprietary-blobs.txt部分的内容例如以下所看到的:
/bin/btnvtool /bin/ds_fmc_appd /bin/efsks /bin/hci_qcomm_init /bin/ks /bin/mm-qcamera-daemon /bin/mpdecision /bin/netmgrd /bin/nv_tee /bin/qcks /bin/qmuxd ......这里列出的文件路径都是相对于设备上的/system文件夹的。而且都是设备特定的、不公开源代码的。因此,我们须要从设备上获取出来。
再来看脚本setup-makefiles.sh的内容:
#!/bin/sh VENDOR=oppo DEVICE=find5 OUTDIR=vendor/$VENDOR/$DEVICE MAKEFILE=../../../$OUTDIR/$DEVICE-vendor-blobs.mk (cat << EOF) > $MAKEFILE # Copyright (C) 2013 The CyanogenMod Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This file is generated by device/$VENDOR/$DEVICE/setup-makefiles.sh PRODUCT_COPY_FILES += \EOF LINEEND=" \\" COUNT=`cat proprietary-blobs.txt | grep -v ^# | grep -v ^$ | wc -l | awk {‘print $1‘}` for FILE in `cat proprietary-blobs.txt | grep -v ^# | grep -v ^$ | sed -e ‘s#^/system##g‘ -e ‘s#^/##g‘`; do COUNT=`expr $COUNT - 1` if [ $COUNT = "0" ]; then LINEEND="" fi echo " $OUTDIR/proprietary/$FILE:system/$FILE$LINEEND" >> $MAKEFILE done (cat << EOF) > ../../../$OUTDIR/$DEVICE-vendor.mk # Copyright (C) 2013 The CyanogenMod Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This file is generated by device/$VENDOR/$DEVICE/setup-makefiles.sh # Pick up overlay for features that depend on non-open-source files DEVICE_PACKAGE_OVERLAYS := vendor/$VENDOR/$DEVICE/overlay \$(call inherit-product, vendor/$VENDOR/$DEVICE/$DEVICE-vendor-blobs.mk) EOF (cat << EOF) > ../../../$OUTDIR/BoardConfigVendor.mk # Copyright (C) 2013 The CyanogenMod Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This file is generated by device/$VENDOR/$DEVICE/setup-makefiles.sh USE_CAMERA_STUB := false EOF这个脚本主要就是用来在vendor/oppo/find5文件夹下生成两个文件:find5-vendor-blobs.mk和BoardConfigVendor.mk文件。这两个文件都是接下来为OPPO Find 5编译ROM时要用到的。
生成的find5-vendor-blobs.mk的部分内容例如以下所看到的:
PRODUCT_COPY_FILES += vendor/oppo/find5/proprietary/bin/btnvtool:system/bin/btnvtool vendor/oppo/find5/proprietary/bin/ds_fmc_appd:system/bin/ds_fmc_appd vendor/oppo/find5/proprietary/bin/efsks:system/bin/efsks vendor/oppo/find5/proprietary/bin/hci_qcomm_init:system/bin/hci_qcomm_init vendor/oppo/find5/proprietary/bin/ks:system/bin/ks ......实际上就是通过PRODUCT_COPY_FILES变量告诉编译系统,要将刚才从设备上获取回来的文件打包到编译出来的ROM的system分区里面去。
生成的文件BoardConfigVendor.mk仅仅有1行,例如以下所看到的:
USE_CAMERA_STUB := false在编译摄像头相关的库文件时,就会到这里定义的USE_CAMERA_STUB变量。
到眼下为止。我们就分别从CM源代码server和目标设备上获取到了编译OPPO Find 5的ROM所须要的设备相关的文件了,接下来就能够開始编译CM源代码了。
8. 回到$CMSOUCE文件夹中,为OPPO Find 5编译ROM。
$ brunch find5命令brunch也是由build/envsetup.sh脚本提供的,它的实现例如以下所看到的:
function brunch() { breakfast $* if [ $? -eq 0 ]; then mka bacon else echo "No such item in brunch menu. Try ‘breakfast‘" return 1 fi return $? }函数brunch做了两件事情:运行breakfast命令和运行mka bacon命令。
前面我们不是已经运行过breakfast命令了吗?这里为什么又要再运行一次呢?从前面的分析能够知道,函数breakfast在运行的过程中,会调用函数lunch。而函数lunch又会调用函数check_product来检查目标设备是否存在。由于在前一步中。我们已经将目标设备相关的源代码下载回来了。因此这时候目标设备是肯定存在的。
当目标设备存在的时候,函数lunch会通过build/tools/roomservice.py脚本检查目标设备相关的源代码是否依赖有其它project,而且这些project是否已经下载回来了。假设有依赖的project,而且这些project还没有下载回来。那么就须要将它们下载回来。
当目标设备存在的时候,函数lunch运行build/tools/roomservice.py脚本的形式为:
build/tools/roomservice.py $product true传递给build/tools/roomservice.py第二件參数为true。表示要处理的是目标设备$product依赖的project。
脚本build/tools/roomservice.py是怎样知道目标设备有没有依赖的project的呢?原来,在下载回来的设备源代码中,有一个cm.dependencies文件,里面描写叙述了自己所依赖的project。
比如,device/oppo/find5/cm.dependencies的内容例如以下所看到的:
[ { "repository": "android_kernel_oppo_find5", "target_path": "kernel/oppo/find5", "branch": "cm-10.1" } ]上面描写叙述的是OPPO Find 5所使用的内核源代码。它来自于CM源代码server上的android_kernel_oppo_find5仓库的cm-10.1分支,下载回来后保存在kernel/oppo/find5文件夹中。这意味着我们通过CM源代码编译出来的ROM所使用的内核也是由我们自己编译出来的。
为了以后运行repo sync命令同步本地源代码时。也能够将设备源代码依赖的project也同一时候同步回来,我们须要将这些依赖project也纳入到Repo仓库中去,因此,当再次运行过breakfast使命之后。
我们就能够在.repo/local_manifests/roomservice.xml文件里发现下面两行内容:
<?回到函数brunch中,它接下来运行的命令mka也是由build/envsetup.sh脚本提供的,例如以下所看到的:xml version="1.0" encoding="UTF-8"?> <manifest> ...... <project name="CyanogenMod/android_device_oppo_find5" path="device/oppo/find5" remote="github" /> <project name="CyanogenMod/android_kernel_oppo_find5" path="kernel/oppo/find5" remote="github" revision="cm-10.1" /> ...... </manifest>
function mka() { case `uname -s` in Darwin) make -j `sysctl hw.ncpu|cut -d" " -f2` "$@" ;; *) schedtool -B -n 1 -e ionice -n 1 make -j$(cat /proc/cpuinfo | grep "^processor" | wc -l) "$@" ;; esac }它实际上是通过一个叫做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文件的载入过程中。有下面的一段逻辑:
ifneq ($(strip $(TARGET_BUILD_APPS)),) # An unbundled app build needs only the core product makefiles. all_product_configs := $(call get-product-makefiles, $(SRC_TARGET_DIR)/product/AndroidProducts.mk) else ifneq ($(CM_BUILD),) all_product_configs := $(shell ls device/*/$(CM_BUILD)/cm.mk) else # Read in all of the product definitions specified by the AndroidProducts.mk # files in the tree. all_product_configs := $(get-all-product-makefiles) endif # CM_BUILD endif当我们编译的是整个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函数实现例如以下所看到的:
function check_product() { T=$(gettop) if [ ! "$T" ]; then echo "Couldn‘t locate the top of the tree. Try setting TOP." >&2 return fi if (echo -n $1 | grep -q -e "^cm_") ; then CM_BUILD=$(echo -n $1 | sed -e ‘s/^cm_//g‘) if [ `uname` == "Darwin" ]; then export BUILD_NUMBER=$((date +%s%N ; echo $CM_BUILD; hostname) | openssl sha1 | cut -c1-10) else export BUILD_NUMBER=$((date +%s%N ; echo $CM_BUILD; hostname) | sha1sum | cut -c1-10) fi else CM_BUILD= fi export CM_BUILD CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core TARGET_PRODUCT=$1 TARGET_BUILD_VARIANT= TARGET_BUILD_TYPE= TARGET_BUILD_APPS= get_build_var TARGET_DEVICE > /dev/null # hide successful answers, but allow the errors to show }从这里就能够看出,环境变量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为例,它的部分内容例如以下所看到的:
...... # Inherit device configuration $(call inherit-product, device/oppo/find5/full_find5.mk) ## Device identifier. This must come after all inclusions PRODUCT_DEVICE := find5 PRODUCT_NAME := cm_find5 PRODUCT_BRAND := Oppo PRODUCT_MODEL := Find 5 PRODUCT_MANUFACTURER := Oppo ......除定了一些设备相关的变量之外。它还会载入另外一个文件device/oppo/find5/full_find5.mk,后者的部分内容例如以下所看到的:
# Inherit from hardware-specific part of the product configuration $(call inherit-product, device/oppo/find5/device.mk) $(call inherit-product-if-exists, vendor/oppo/find5/find5-vendor.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的定义:
PRODUCT_PACKAGES := lights.msm8960 PRODUCT_PACKAGES += charger_res_images charger # Vold and Storage PRODUCT_COPY_FILES += device/oppo/find5/configs/vold.fstab:system/etc/vold.fstab # Live Wallpapers PRODUCT_PACKAGES += LiveWallpapers LiveWallpapersPicker VisualizationWallpapers librs_jni ......而文件vendor/oppo/find5/find5-vendor.mk会载入另外一个文件vendor/oppo/find5/find5-vendor-blobs.mk。例如以下所看到的:
...... $(call inherit-product, 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文件。例如以下所看到的:
...... board_config_mk := $(strip $(wildcard $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk device/*/$(TARGET_DEVICE)/BoardConfig.mk vendor/*/$(TARGET_DEVICE)/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文件。它的部分内容例如以下所看到的:
TARGET_CPU_ABI := armeabi-v7a TARGET_CPU_ABI2 := armeabi ...... TARGET_ARCH := arm ...... # Bluetooth BOARD_HAVE_BLUETOOTH := true BOARD_HAVE_BLUETOOTH_QCOM := true BLUETOOTH_HCI_USE_MCT := true ...... TARGET_BOARD_PLATFORM := msm8960 ...... # Wifi BOARD_HAS_QCOM_WLAN := true BOARD_WLAN_DEVICE := qcwcn ...... # Display TARGET_QCOM_DISPLAY_VARIANT := caf USE_OPENGL_RENDERER := true ...... # GPS BOARD_HAVE_NEW_QC_GPS := true ...... # Camera COMMON_GLOBAL_CFLAGS += -DDISABLE_HW_ID_MATCH_CHECK -DQCOM_BSP_CAMERA_ABI_HACK -DNEW_ION_API ...... # Audio BOARD_USES_ALSA_AUDIO:= true TARGET_QCOM_AUDIO_VARIANT := caf TARGET_USES_QCOM_MM_AUDIO := true TARGET_USES_QCOM_COMPRESSED_AUDIO := true BOARD_AUDIO_CAF_LEGACY_INPUT_BUFFERSIZE := true ......从这里就能够看到各种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文件有例如以下的内容:
...... LOCAL_CFLAGS += -DCAMERA_ION_HEAP_ID=ION_CP_MM_HEAP_ID # 8660=SMI, Rest=EBI LOCAL_CFLAGS += -DCAMERA_ZSL_ION_HEAP_ID=ION_CP_MM_HEAP_ID LOCAL_CFLAGS+= -DHW_ENCODE LOCAL_CFLAGS+= -DUSE_NEON_CONVERSION ifeq ($(TARGET_BOARD_PLATFORM),msm8960) LOCAL_CFLAGS += -DCAMERA_GRALLOC_HEAP_ID=GRALLOC_USAGE_PRIVATE_MM_HEAP LOCAL_CFLAGS += -DCAMERA_GRALLOC_FALLBACK_HEAP_ID=GRALLOC_USAGE_PRIVATE_IOMMU_HEAP LOCAL_CFLAGS += -DCAMERA_ION_FALLBACK_HEAP_ID=ION_IOMMU_HEAP_ID LOCAL_CFLAGS += -DCAMERA_ZSL_ION_FALLBACK_HEAP_ID=ION_IOMMU_HEAP_ID LOCAL_CFLAGS += -DCAMERA_GRALLOC_CACHING_ID=0 else ifeq ($(TARGET_BOARD_PLATFORM),msm8660) LOCAL_CFLAGS += -DCAMERA_GRALLOC_HEAP_ID=GRALLOC_USAGE_PRIVATE_CAMERA_HEAP LOCAL_CFLAGS += -DCAMERA_GRALLOC_FALLBACK_HEAP_ID=GRALLOC_USAGE_PRIVATE_CAMERA_HEAP # Don‘t Care LOCAL_CFLAGS += -DCAMERA_ION_FALLBACK_HEAP_ID=ION_CAMERA_HEAP_ID # EBI LOCAL_CFLAGS += -DCAMERA_ZSL_ION_FALLBACK_HEAP_ID=ION_CAMERA_HEAP_ID LOCAL_CFLAGS += -DCAMERA_GRALLOC_CACHING_ID=0 else LOCAL_CFLAGS += -DCAMERA_GRALLOC_HEAP_ID=GRALLOC_USAGE_PRIVATE_ADSP_HEAP LOCAL_CFLAGS += -DCAMERA_GRALLOC_FALLBACK_HEAP_ID=GRALLOC_USAGE_PRIVATE_ADSP_HEAP # Don‘t Care LOCAL_CFLAGS += -DCAMERA_GRALLOC_CACHING_ID=GRALLOC_USAGE_PRIVATE_UNCACHED #uncached endif ......上述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是一件非常easy的事呢?事实上不然。我们上面编译的ROM之所以这么轻松,是由于CM已经为我们做了非常多的工作,那就是已经在CM源代码server上准备好了全部设备相关的源代码,也就是我们下载回来之后存放在device文件夹下的源代码。
假设我们手头上有一部CM官方不支持的手机。那么CM源代码server上是没有相应的设备源代码存在的,这时候我们就须要自己去开发相应的设备源代码了。这是一个艰苦的过程,须要不停的调试、调试、再调试,或许要花上几周,甚至一个月来完毕这个工作。
当然,这个过程也是有一些经验和技巧能够參考的,具体能够參考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。
从CM刷机过程和原理分析Android系统结构