从CM刷机过程和原理分析Android系统结构

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从CM刷机过程和原理分析Android系统结构相关的知识,希望对你有一定的参考价值。

转:http://blog.csdn.net/luoshengyang/article/details/29688041

 

 前面101篇文章都是分析android系统源码,似乎不够接地气。如果能让Android系统源码在真实设备上跑跑看效果,那该多好。这不就是传说中的刷ROM吗?刷ROM这个话题是老罗以前一直避免谈的,因为觉得没有全面了解Android系统前就谈ROM是不完整的。写完了101篇文章后,老罗觉得第102篇文章该谈谈这个话题了,并且选择CM这个有代表性的ROM来谈,目标是加深大家对Android系统的了解。

老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

       说起刷ROM的动机,除了上面说的用来看Android系统源码在真实设备上的运行效果,还有很多值得说的。回想起PC时代,我们对我们自己拥有的设备(电脑),基本上能做的就是在上面重装系统。这个系统是厂商做好给我们的,里面有什么我们就用什么,不能随心所欲地定制。当然,如果你用的是Linux系统,你是可以随心所欲地对它进行定制的。不过可惜的是,我们的女神用的都是Windows系统。你和女神说你想要什么样的Linux系统,我给你定制一个,她会不知道你说的是什么——她需要的是一个不会中毒的又跑得快的Windows系统而已。现如今虽然很多女神用的是仍然是我们不能随心所欲定制的ios系统,但是在移动设备上,iOS系统毕竟不能做到Windows在PC那样的一家独大——我们还有不少女神是用Android系统的。所以,如果你现在和女神说,我可以帮你刷一个专属的精简Android系统,里面没有一堆你不需要的预装软件,会让你的手机跑得很快,那女神得有多崇拜你啊。

       当然,刷ROM的动机不能只是为了让女神崇拜,作为一个程序猿,我们的首要任务是维护宇宙和平。怎么维护呢?至少程序有BUG不能不改吧。你不改的话,老板是不会放过你的。但是,碰到那些很棘手的BUG,怎么办呢?例如,你是一个Android应用开发者,调用一个API接口的时候,总是抛出一个异常,而这个异常不跟到API内部实现去看看实在是不知道什么原因造成的。这时候,如果你手头上有这个手机的系统源代码,找这个API内部实现的地方,加一两行调试代码,再编译回手机上去跑,是不是就很容易定位问题了呢?

        所以说,会刷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源码中编译出能在自己使用的手机上运行的系统,可不是一件容易的事情。不过,好在有很多现成的基于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中,那么打开一个终端,执行以下命令即可:

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ 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。

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ cd $CM  

        3. 执行以下命令让OPPO Find 5重启,并且进入Fastboot模式。

 

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ adb reboot bootloader  

        4. 可以看到OPPO Find 5停留在Fastboot界面上,执行以下命令确保fastboot工具能够连接到OPPO Find 5。

 

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ fastboot devices  

       如果能够连接,那么上述命令将会输出一串标识OPPO Find 5的ID。

 

       5. 刷入我们刚才下载的Recovery。

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ fastboot flash recovery recovery-clockwork-6.0.4.6-find5.img  

       6. 提示刷入成功后,执行以下命令正常重启手机。

 

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ fastboot reboot  

       如果一切正常,手机将进入到原来的系统中。

 

       继续在上述打开的终端上,刷CM-10.1.3 ROM,步骤如下所示:

       1. 将下载好的cm-10.1.3.find5.zip上传至OPPO Find 5的sdcard上

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ adb push cm-10.1.3.find5.zip /sdcard/cm-10.1.3.find5.zip  

       2. 执行以下命令让OPPO Find 5重启,并且进入Recovery模式。

 

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ 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目录中。

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ cd $CMSOURCE  

        2. 将当前目录初始为CM-10.1分支源码的Git仓库。

 

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ repo init -u git://github.com/CyanogenMod/android.git -b cm-10.1  

        3. 下载CM-10.1分支源码。

 

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ repo sync  

        以上两步都是关于Android源码仓库的知识,可以参考Android源代码仓库及其管理工具Repo分析一文,这里不再详述。

 

        4. 进入到$CMSOURCE目录下的vendor/cm子目录中,并且执行里面的get-prebuilts脚本,用来获得一些预编译的APP。

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ ./get-prebuilts  

        打开$CMSOURCE/vendor/cm/get-prebuilts文件,它的内容如下所示:

 

 

[plain] view plain copy
 
 技术分享技术分享
  1. BASEDIR=`dirname $0`  
  2.   
  3. mkdir -p $BASEDIR/proprietary  
  4.   
  5. # Get Android Terminal Emulator (we use a prebuilt so it can update from the Market)  
  6. curl -L -o $BASEDIR/proprietary/Term.apk -O -L http://jackpal.github.com/Android-Terminal-Emulator/downloads/Term.apk  
  7. 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脚本加载到当前终端来。

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ source build/envsetup.sh  

        参考Android编译系统环境初始化过程分析一文,envsetup.sh脚本加载到当前终端后,我们就可以获得一系列与Android编译系统相关的命令,例如lunch/m/mm/mmm,以及下一步要执行的breakfast命令。

 

        6. 为OPPO Find 5下载相关的源码。

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ breakfast find5  

        在Android编译系统简要介绍和学习计划这个系列的文章中,我们提到,在编译Android的官方源码之前,我们需要执行一个lunch命令来为我们的目标设备初始化编译环境。这个lunch命令是由Android官方源码的envsetup.sh脚本提供的。CM修改envsetup.sh脚本,额外提供了一个breakfast命令,用来从网上寻找指定的设备相关的源码,以便我们可以为该设备编译出能运行的ROM来。

 

        打开envsetup.sh文件,查看breakfast的实现:

 

[plain] view plain copy
 
 技术分享技术分享
  1. function breakfast()  
  2. {  
  3.     target=$1  
  4.     CM_DEVICES_ONLY="true"  
  5.     unset LUNCH_MENU_CHOICES  
  6.     add_lunch_combo full-eng  
  7.     for f in `/bin/ls vendor/cm/vendorsetup.sh 2> /dev/null`  
  8.         do  
  9.             echo "including $f"  
  10.             . $f  
  11.         done  
  12.     unset f  
  13.   
  14.     if [ $# -eq 0 ]; then  
  15.         # No arguments, so let‘s have the full menu  
  16.         lunch  
  17.     else  
  18.         echo "z$target" | grep -q "-"  
  19.         if [ $? -eq 0 ]; then  
  20.             # A buildtype was specified, assume a full device name  
  21.             lunch $target  
  22.         else  
  23.             # This is probably just the CM model name  
  24.             lunch cm_$target-userdebug  
  25.         fi  
  26.     fi  
  27.     return $?  
  28. }  

        函数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的内容:

 

[plain] view plain copy
 
 技术分享技术分享
  1. 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‘})  
  2. do  
  3.     add_lunch_combo $combo  
  4. 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>,以下列出的是部分内容:

 

[plain] view plain copy
 
 技术分享技术分享
  1. # CM build target list  
  2. # <lunchcombo> <branch> [period: "D"aily, "W"eekly or "M"onthly]  
  3. # Absence of a period indicates Daily (the default)  
  4.   
  5. cm_a700-userdebug cm-11.0  
  6. cm_acclaim-userdebug cm-11.0  
  7. cm_amami-userdebug cm-11.0  
  8. cm_anzu-userdebug jellybean M  
  9. cm_apexqtmo-userdebug cm-11.0  
  10. cm_aries-userdebug cm-11.0  
  11. cm_captivatemtd-userdebug cm-11.0  
  12. ......  

        由此可见,执行脚本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自有的逻辑,下面我们就看看这些修改:

 

[plain] view plain copy
 
 技术分享技术分享
  1. function lunch()  
  2. {  
  3.     ......  
  4.   
  5.     local product=$(echo -n $selection | sed -e "s/-.*$//")  
  6.     check_product $product  
  7.     if [ $? -ne 0 ]  
  8.     then  
  9.         # if we can‘t find a product, try to grab it off the CM github  
  10.         T=$(gettop)  
  11.         pushd $T > /dev/null  
  12.         build/tools/roomservice.py $product  
  13.         popd > /dev/null  
  14.         check_product $product  
  15.     else  
  16.         build/tools/roomservice.py $product true  
  17.     fi  
  18.       
  19.     ......  
  20. }  

       这里的变量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文件中看到相应的一行内容:

 

[html] view plain copy
 
 技术分享技术分享
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <manifest>  
  3.   ......  
  4.   <project name="CyanogenMod/android_device_oppo_find5" path="device/oppo/find5" remote="github" />  
  5.   ......  
  6. </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目录中,执行以下命令提取设备私有文件。

 

[plain] view plain copy
 
 技术分享技术分享
  1. $ ./extract-files.sh  

       脚本extract-files.sh的内容如下所示:

[plain] view plain copy
 
 技术分享技术分享
  1. #!/bin/sh  
  2.   
  3. VENDOR=oppo  
  4. DEVICE=find5  
  5.   
  6. BASE=../../../vendor/$VENDOR/$DEVICE/proprietary  
  7. rm -rf $BASE/*  
  8.   
  9. for FILE in `cat proprietary-blobs.txt | grep -v ^# | grep -v ^$ | sed -e ‘s#^/system/##g‘`; do  
  10.     DIR=`dirname $FILE`  
  11.     if [ ! -d $BASE/$DIR ]; then  
  12.         mkdir -p $BASE/$DIR  
  13.     fi  
  14.     adb pull /system/$FILE $BASE/$FILE  
  15. done  
  16.   
  17. ./setup-makefiles.sh  

       首先是创建一个vendor/oppo/find5/proprietary目录,接着是读取文件proprietary-blobs.txt中的每一行,并且将每一行所描述的文件从设备上的/system目录中获取出来,保存在vendor/oppo/find5/proprietary对应的子目录下面,最后再执行另外一个脚本setup-makefiles.sh。

       文件device/oppo/find5/proprietary-blobs.txt部分的内容如下所示: