從CM刷機過程和原理分析Android系統結構

轉: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中,那麼打開一個終端,執行如下命令便可:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  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
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ cd $CM  

        3. 執行如下命令讓OPPO Find 5重啓,而且進入Fastboot模式。

 

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ adb reboot bootloader  

        4. 能夠看到OPPO Find 5停留在Fastboot界面上,執行如下命令確保fastboot工具可以鏈接到OPPO Find 5。

 

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ fastboot devices  

       若是可以鏈接,那麼上述命令將會輸出一串標識OPPO Find 5的ID。

 

       5. 刷入咱們剛纔下載的Recovery。

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ fastboot flash recovery recovery-clockwork-6.0.4.6-find5.img  

       6. 提示刷入成功後,執行如下命令正常重啓手機。

 

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ fastboot reboot  

       若是一切正常,手機將進入到原來的系統中。

 

       繼續在上述打開的終端上,刷CM-10.1.3 ROM,步驟以下所示:

       1. 將下載好的cm-10.1.3.find5.zip上傳至OPPO Find 5的sdcard上

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  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
 
 在CODE上查看代碼片派生到個人代碼片
  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
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ cd $CMSOURCE  

        2. 將當前目錄初始爲CM-10.1分支源碼的Git倉庫。

 

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ repo init -u git://github.com/CyanogenMod/android.git -b cm-10.1  

        3. 下載CM-10.1分支源碼。

 

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ repo sync  

        以上兩步都是關於Android源碼倉庫的知識,能夠參考Android源代碼倉庫及其管理工具Repo分析一文,這裏再也不詳述。

 

        4. 進入到$CMSOURCE目錄下的vendor/cm子目錄中,而且執行裏面的get-prebuilts腳本,用來得到一些預編譯的APP。

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ ./get-prebuilts  

        打開$CMSOURCE/vendor/cm/get-prebuilts文件,它的內容以下所示:

 

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  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
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ source build/envsetup.sh  

        參考Android編譯系統環境初始化過程分析一文,envsetup.sh腳本加載到當前終端後,咱們就能夠得到一系列與Android編譯系統相關的命令,例如lunch/m/mm/mmm,以及下一步要執行的breakfast命令。

 

        6. 爲OPPO Find 5下載相關的源碼。

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ breakfast find5  

        在Android編譯系統簡要介紹和學習計劃這個系列的文章中,咱們提到,在編譯Android的官方源碼以前,咱們須要執行一個lunch命令來爲咱們的目標設備初始化編譯環境。這個lunch命令是由Android官方源碼的envsetup.sh腳本提供的。CM修改envsetup.sh腳本,額外提供了一個breakfast命令,用來從網上尋找指定的設備相關的源碼,以便咱們能夠爲該設備編譯出能運行的ROM來。

 

        打開envsetup.sh文件,查看breakfast的實現:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  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
 
 在CODE上查看代碼片派生到個人代碼片
  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
 
 在CODE上查看代碼片派生到個人代碼片
  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
 
 在CODE上查看代碼片派生到個人代碼片
  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
 
 在CODE上查看代碼片派生到個人代碼片
  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
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ ./extract-files.sh  

       腳本extract-files.sh的內容以下所示:

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  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部分的內容以下所示:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. /bin/btnvtool  
  2. /bin/ds_fmc_appd  
  3. /bin/efsks  
  4. /bin/hci_qcomm_init  
  5. /bin/ks  
  6. /bin/mm-qcamera-daemon  
  7. /bin/mpdecision  
  8. /bin/netmgrd  
  9. /bin/nv_tee  
  10. /bin/qcks  
  11. /bin/qmuxd  
  12. ......  

       這裏列出的文件路徑都是相對於設備上的/system目錄的,而且都是設備特定的、不公開源碼的,所以,咱們須要從設備上獲取出來。

 

       再來看腳本setup-makefiles.sh的內容:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. #!/bin/sh  
  2.   
  3. VENDOR=oppo  
  4. DEVICE=find5  
  5. OUTDIR=vendor/$VENDOR/$DEVICE  
  6. MAKEFILE=../../../$OUTDIR/$DEVICE-vendor-blobs.mk  
  7.   
  8. (cat << EOF) > $MAKEFILE  
  9. # Copyright (C) 2013 The CyanogenMod Project  
  10. #  
  11. # Licensed under the Apache License, Version 2.0 (the "License");  
  12. # you may not use this file except in compliance with the License.  
  13. # You may obtain a copy of the License at  
  14. #  
  15. #      http://www.apache.org/licenses/LICENSE-2.0  
  16. #  
  17. # Unless required by applicable law or agreed to in writing, software  
  18. # distributed under the License is distributed on an "AS IS" BASIS,  
  19. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  20. # See the License for the specific language governing permissions and  
  21. # limitations under the License.  
  22.   
  23. # This file is generated by device/$VENDOR/$DEVICE/setup-makefiles.sh  
  24.   
  25. PRODUCT_COPY_FILES += \\  
  26. EOF  
  27.   
  28. LINEEND=" \\"  
  29. COUNT=`cat proprietary-blobs.txt | grep -v ^# | grep -v ^$ | wc -l | awk {'print $1'}`  
  30. for FILE in `cat proprietary-blobs.txt | grep -v ^# | grep -v ^$ | sed -e 's#^/system##g' -e 's#^/##g'`; do  
  31.     COUNT=`expr $COUNT - 1`  
  32.     if [ $COUNT = "0" ]; then  
  33.         LINEEND=""  
  34.     fi  
  35.     echo "    $OUTDIR/proprietary/$FILE:system/$FILE$LINEEND" >> $MAKEFILE  
  36. done  
  37.   
  38. (cat << EOF) > ../../../$OUTDIR/$DEVICE-vendor.mk  
  39. # Copyright (C) 2013 The CyanogenMod Project  
  40. #  
  41. # Licensed under the Apache License, Version 2.0 (the "License");  
  42. # you may not use this file except in compliance with the License.  
  43. # You may obtain a copy of the License at  
  44. #  
  45. #      http://www.apache.org/licenses/LICENSE-2.0  
  46. #  
  47. # Unless required by applicable law or agreed to in writing, software  
  48. # distributed under the License is distributed on an "AS IS" BASIS,  
  49. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  50. # See the License for the specific language governing permissions and  
  51. # limitations under the License.  
  52.   
  53. # This file is generated by device/$VENDOR/$DEVICE/setup-makefiles.sh  
  54.   
  55. # Pick up overlay for features that depend on non-open-source files  
  56. DEVICE_PACKAGE_OVERLAYS := vendor/$VENDOR/$DEVICE/overlay  
  57.   
  58. \$(call inherit-product, vendor/$VENDOR/$DEVICE/$DEVICE-vendor-blobs.mk)  
  59. EOF  
  60.   
  61. (cat << EOF) > ../../../$OUTDIR/BoardConfigVendor.mk  
  62. # Copyright (C) 2013 The CyanogenMod Project  
  63. #  
  64. # Licensed under the Apache License, Version 2.0 (the "License");  
  65. # you may not use this file except in compliance with the License.  
  66. # You may obtain a copy of the License at  
  67. #  
  68. #      http://www.apache.org/licenses/LICENSE-2.0  
  69. #  
  70. # Unless required by applicable law or agreed to in writing, software  
  71. # distributed under the License is distributed on an "AS IS" BASIS,  
  72. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  73. # See the License for the specific language governing permissions and  
  74. # limitations under the License.  
  75.   
  76. # This file is generated by device/$VENDOR/$DEVICE/setup-makefiles.sh  
  77.   
  78. USE_CAMERA_STUB := false  
  79. EOF  

       這個腳本主要就是用來在vendor/oppo/find5目錄下生成兩個文件:find5-vendor-blobs.mk和BoardConfigVendor.mk文件。這兩個文件都是接下來爲OPPO Find 5編譯ROM時要用到的。

 

       生成的find5-vendor-blobs.mk的部份內容以下所示:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. PRODUCT_COPY_FILES += \  
  2.     vendor/oppo/find5/proprietary/bin/btnvtool:system/bin/btnvtool \  
  3.     vendor/oppo/find5/proprietary/bin/ds_fmc_appd:system/bin/ds_fmc_appd \  
  4.     vendor/oppo/find5/proprietary/bin/efsks:system/bin/efsks \  
  5.     vendor/oppo/find5/proprietary/bin/hci_qcomm_init:system/bin/hci_qcomm_init \  
  6.     vendor/oppo/find5/proprietary/bin/ks:system/bin/ks \  
  7. ......  

       實際上就是經過PRODUCT_COPY_FILES變量告訴編譯系統,要將剛纔從設備上獲取回來的文件打包到編譯出來的ROM的system分區裏面去。

 

       生成的文件BoardConfigVendor.mk只有1行,以下所示:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. USE_CAMERA_STUB := false  

       在編譯攝像頭相關的庫文件時,就會到這裏定義的USE_CAMERA_STUB變量。

 

       到目前爲止,咱們就分別從CM源碼服務器和目標設備上獲取到了編譯OPPO Find 5的ROM所須要的設備相關的文件了,接下來就能夠開始編譯CM源碼了。

 

       8. 回到$CMSOUCE目錄中,爲OPPO Find 5編譯ROM。

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. $ brunch find5  

       命令brunch也是由build/envsetup.sh腳本提供的,它的實現以下所示:

 

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. function brunch()  
  2. {  
  3.     breakfast $*  
  4.     if [ $? -eq 0 ]; then  
  5.         mka bacon  
  6.     else  
  7.         echo "No such item in brunch menu. Try 'breakfast'"  
  8.         return 1  
  9.     fi  
  10.     return $?  
  11. }  

       函數brunch作了兩件事情:執行breakfast命令和執行mka bacon命令。

 

       前面咱們不是已經執行過breakfast命令了嗎?這裏爲何又要再執行一次呢?從前面的分析能夠知道,函數breakfast在執行的過程當中,會調用函數lunch,而函數lunch又會調用函數check_product來檢查目標設備是否存在。由於在前一步中,咱們已經將目標設備相關的源碼下載回來了,所以這時候目標設備是確定存在的。當目標設備存在的時候,函數lunch會經過build/tools/roomservice.py腳本檢查目標設備相關的源碼是否依賴有其它工程,而且這些工程是否已經下載回來了。若是有依賴的工程,而且這些工程尚未下載回來,那麼就須要將它們下載回來。

       當目標設備存在的時候,函數lunch執行build/tools/roomservice.py腳本的形式爲:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. build/tools/roomservice.py $product true  

       傳遞給build/tools/roomservice.py第二件參數爲true,表示要處理的是目標設備$product依賴的工程。

 

       腳本build/tools/roomservice.py是如何知道目標設備有沒有依賴的工程的呢?原來,在下載回來的設備源碼中,有一個cm.dependencies文件,裏面描述了本身所依賴的工程。例如,device/oppo/find5/cm.dependencies的內容以下所示:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. [  
  2.  {  
  3.    "repository": "android_kernel_oppo_find5",  
  4.    "target_path": "kernel/oppo/find5",  
  5.    "branch": "cm-10.1"  
  6.  }  
  7. ]  

       上面描述的是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文件中發現如下兩行內容:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  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.   <project name="CyanogenMod/android_kernel_oppo_find5" path="kernel/oppo/find5" remote="github" revision="cm-10.1" />  
  6.   ......  
  7. </manifest>  

      回到函數brunch中,它接下來執行的命令mka也是由build/envsetup.sh腳本提供的,以下所示:

 

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. function mka() {  
  2.     case `uname -s` in  
  3.         Darwin)  
  4.             make -j `sysctl hw.ncpu|cut -d" " -f2` "$@"  
  5.             ;;  
  6.         *)  
  7.             schedtool -B -n 1 -e ionice -n 1 make -j$(cat /proc/cpuinfo | grep "^processor" | wc -l) "$@"  
  8.             ;;  
  9.     esac  
  10. }  

       它其實是經過一個叫作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文件的加載過程當中,有如下的一段邏輯:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. ifneq ($(strip $(TARGET_BUILD_APPS)),)  
  2. # An unbundled app build needs only the core product makefiles.  
  3. all_product_configs := $(call get-product-makefiles,\  
  4.     $(SRC_TARGET_DIR)/product/AndroidProducts.mk)  
  5. else  
  6.   ifneq ($(CM_BUILD),)  
  7.     all_product_configs := $(shell ls device/*/$(CM_BUILD)/cm.mk)  
  8.   else  
  9.     # Read in all of the product definitions specified by the AndroidProducts.mk  
  10.     # files in the tree.  
  11.     all_product_configs := $(get-all-product-makefiles)  
  12.   endif # CM_BUILD  
  13. 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函數實現以下所示:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. function check_product()  
  2. {  
  3.     T=$(gettop)  
  4.     if [ ! "$T" ]; then  
  5.         echo "Couldn't locate the top of the tree.  Try setting TOP." >&2  
  6.         return  
  7.     fi  
  8.   
  9.     if (echo -n $1 | grep -q -e "^cm_") ; then  
  10.        CM_BUILD=$(echo -n $1 | sed -e 's/^cm_//g')  
  11.        if [ `uname` == "Darwin" ]; then  
  12.            export BUILD_NUMBER=$((date +%s%N ; echo $CM_BUILD; hostname) | openssl sha1 | cut -c1-10)  
  13.        else  
  14.            export BUILD_NUMBER=$((date +%s%N ; echo $CM_BUILD; hostname) | sha1sum | cut -c1-10)  
  15.        fi  
  16.     else  
  17.        CM_BUILD=  
  18.     fi  
  19.     export CM_BUILD  
  20.   
  21.     CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \  
  22.         TARGET_PRODUCT=$1 \  
  23.         TARGET_BUILD_VARIANT= \  
  24.         TARGET_BUILD_TYPE= \  
  25.         TARGET_BUILD_APPS= \  
  26.         get_build_var TARGET_DEVICE > /dev/null  
  27.     # hide successful answers, but allow the errors to show  
  28. }  

        從這裏就能夠看出,環境變量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爲例,它的部份內容以下所示:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. ......  
  2. # Inherit device configuration  
  3. $(call inherit-product, device/oppo/find5/full_find5.mk)  
  4.   
  5. ## Device identifier. This must come after all inclusions  
  6. PRODUCT_DEVICE := find5  
  7. PRODUCT_NAME := cm_find5  
  8. PRODUCT_BRAND := Oppo  
  9. PRODUCT_MODEL := Find 5  
  10. PRODUCT_MANUFACTURER := Oppo  
  11. ......  

        除定了一些設備相關的變量以外,它還會加載另一個文件device/oppo/find5/full_find5.mk,後者的部份內容以下所示:

 

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. # Inherit from hardware-specific part of the product configuration  
  2. $(call inherit-product, device/oppo/find5/device.mk)  
  3. $(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的定義:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. PRODUCT_PACKAGES := \  
  2.     lights.msm8960  
  3.   
  4. PRODUCT_PACKAGES += \  
  5.     charger_res_images \  
  6.     charger  
  7.   
  8. # Vold and Storage  
  9. PRODUCT_COPY_FILES += \  
  10.         device/oppo/find5/configs/vold.fstab:system/etc/vold.fstab  
  11.   
  12. # Live Wallpapers  
  13. PRODUCT_PACKAGES += \  
  14.         LiveWallpapers \  
  15.         LiveWallpapersPicker \  
  16.         VisualizationWallpapers \  
  17.         librs_jni  
  18. ......  

       而文件vendor/oppo/find5/find5-vendor.mk會加載另一個文件vendor/oppo/find5/find5-vendor-blobs.mk,以下所示:

 

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. ......  
  2. $(call inherit-product, vendor/oppo/find5/find5-vendor-blobs.mk)  
  3. ......  

        回憶前面的第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文件,以下所示:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. ......  
  2. board_config_mk := \  
  3.     $(strip $(wildcard \  
  4.         $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \  
  5.         device/*/$(TARGET_DEVICE)/BoardConfig.mk \  
  6.         vendor/*/$(TARGET_DEVICE)/BoardConfig.mk \  
  7.     ))  
  8. ......  

        上述Makefile腳本片斷會在build/target/board、device/*、vendor/*目錄中尋找一個名稱爲$(TARGET_DEVICE)的子目錄,而且在找到的子目錄裏面加載一個名稱爲BoardConfig.mk文件,來得到Device、Board和Architecture級別的配置信息。

 

        變量TARGET_DEVICE指向的即是目標設備的名稱。在咱們這個場景中,它的值就等於find5,所以,最終得到的Device、Board和Architecture級別的配置信息就是來自於device/oppo/find5/BoardConfig.mk文件,它的部份內容以下所示:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. TARGET_CPU_ABI := armeabi-v7a  
  2. TARGET_CPU_ABI2 := armeabi  
  3. ......  
  4. TARGET_ARCH := arm  
  5. ......  
  6.   
  7. # Bluetooth  
  8. BOARD_HAVE_BLUETOOTH := true  
  9. BOARD_HAVE_BLUETOOTH_QCOM := true  
  10. BLUETOOTH_HCI_USE_MCT := true  
  11. ......  
  12.   
  13. TARGET_BOARD_PLATFORM := msm8960  
  14. ......  
  15.   
  16. # Wifi  
  17. BOARD_HAS_QCOM_WLAN              := true  
  18. BOARD_WLAN_DEVICE                := qcwcn  
  19. ......  
  20.   
  21. # Display  
  22. TARGET_QCOM_DISPLAY_VARIANT := caf  
  23. USE_OPENGL_RENDERER := true  
  24. ......  
  25.   
  26. # GPS  
  27. BOARD_HAVE_NEW_QC_GPS := true  
  28. ......  
  29.   
  30. # Camera  
  31. COMMON_GLOBAL_CFLAGS += -DDISABLE_HW_ID_MATCH_CHECK -DQCOM_BSP_CAMERA_ABI_HACK -DNEW_ION_API  
  32. ......  
  33.   
  34. # Audio  
  35. BOARD_USES_ALSA_AUDIO:= true  
  36. TARGET_QCOM_AUDIO_VARIANT := caf  
  37. TARGET_USES_QCOM_MM_AUDIO := true  
  38. TARGET_USES_QCOM_COMPRESSED_AUDIO := true  
  39. BOARD_AUDIO_CAF_LEGACY_INPUT_BUFFERSIZE := true  
  40. ......  

        從這裏就能夠看到各類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文件有以下的內容:

 

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. ......  
  2. LOCAL_CFLAGS += -DCAMERA_ION_HEAP_ID=ION_CP_MM_HEAP_ID # 8660=SMI, Rest=EBI  
  3. LOCAL_CFLAGS += -DCAMERA_ZSL_ION_HEAP_ID=ION_CP_MM_HEAP_ID  
  4.   
  5. LOCAL_CFLAGS+= -DHW_ENCODE  
  6. LOCAL_CFLAGS+= -DUSE_NEON_CONVERSION  
  7.   
  8. ifeq ($(TARGET_BOARD_PLATFORM),msm8960)  
  9.         LOCAL_CFLAGS += -DCAMERA_GRALLOC_HEAP_ID=GRALLOC_USAGE_PRIVATE_MM_HEAP  
  10.         LOCAL_CFLAGS += -DCAMERA_GRALLOC_FALLBACK_HEAP_ID=GRALLOC_USAGE_PRIVATE_IOMMU_HEAP  
  11.         LOCAL_CFLAGS += -DCAMERA_ION_FALLBACK_HEAP_ID=ION_IOMMU_HEAP_ID  
  12.         LOCAL_CFLAGS += -DCAMERA_ZSL_ION_FALLBACK_HEAP_ID=ION_IOMMU_HEAP_ID  
  13.         LOCAL_CFLAGS += -DCAMERA_GRALLOC_CACHING_ID=0  
  14. else ifeq ($(TARGET_BOARD_PLATFORM),msm8660)  
  15.         LOCAL_CFLAGS += -DCAMERA_GRALLOC_HEAP_ID=GRALLOC_USAGE_PRIVATE_CAMERA_HEAP  
  16.         LOCAL_CFLAGS += -DCAMERA_GRALLOC_FALLBACK_HEAP_ID=GRALLOC_USAGE_PRIVATE_CAMERA_HEAP # Don't Care  
  17.         LOCAL_CFLAGS += -DCAMERA_ION_FALLBACK_HEAP_ID=ION_CAMERA_HEAP_ID # EBI  
  18.         LOCAL_CFLAGS += -DCAMERA_ZSL_ION_FALLBACK_HEAP_ID=ION_CAMERA_HEAP_ID  
  19.         LOCAL_CFLAGS += -DCAMERA_GRALLOC_CACHING_ID=0  
  20. else  
  21.         LOCAL_CFLAGS += -DCAMERA_GRALLOC_HEAP_ID=GRALLOC_USAGE_PRIVATE_ADSP_HEAP  
  22.         LOCAL_CFLAGS += -DCAMERA_GRALLOC_FALLBACK_HEAP_ID=GRALLOC_USAGE_PRIVATE_ADSP_HEAP # Don't Care  
  23.         LOCAL_CFLAGS += -DCAMERA_GRALLOC_CACHING_ID=GRALLOC_USAGE_PRIVATE_UNCACHED #uncached  
  24. endif  
  25. ......  

       上述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

相關文章
相關標籤/搜索