轉: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。