時代在進步,第三套少兒廣播體操!不好意思,搞錯頻道了,重來!時代在進步,Android的版本也是快速的進行着迭代着,從我們以前最常見的Android 4.4一直發展到了今天的Android 11版本(即Android K到Android R),Android版本的快速迭代對於消費者來說是一件普天同慶的大好事情,但是對於我們開發者來說各種適配各種改造有時候吃翔的心情都有了。而對於Android版本的適配和各種改造的第一步就是從編譯Android源碼開始,可是不幸的是隨着Android版本的迭代連編譯Android源碼的相關流程都發生了翻天覆地的變化,正所謂工欲利其事必先利器,所以我們今天的這篇博客將帶領讀者一起來捯飭捯飭Android各個版本的源碼編譯發展和編譯具體操作步驟!
這里我們需要注意一下Android各個版本的對應關系如下:
- Android 5.x (Lollipop)簡稱Android L版本
- Android 6.0 (MarshMallow) 簡稱Android M版本
- Android 7.x (Nougat)簡稱Android N版本
- Android 8.x (Oreo)簡稱Android O版本
- Android 9.0 (Pie)簡稱Android P版本
- Android 10.0 (Q)簡稱Android Q版本
- Android 11.0 (R)簡稱Android R版本
並且這里還有一點需要特別注意,本文演示的Android R版本是以高通平台為基准進行的。
一.Android編譯環境的構建以及常見命令
俗話說天時地利人和缺一不可,而這其中的地利翻譯過來說的就是環境因素了,人的成長離不開環境因素,而我們的Android編譯也離不開編譯環境的構建!雖然我們本篇博客的主題是Android源碼編譯指南,但是我們還是有必要抽出一個章節來簡單說明下Android編譯環境的構建和初始化過程,以及初始化完畢后常見的命令。
1.1 Android編譯環境的構建
本章節重點偏向的是Android編譯環境的構建,而不是編譯環境構建的原理分析(如果是原理分析,那內容就多了)。
雖然Android的版本一直在迭代着,但是Android編譯環境的構建步驟還是比較良心的依然沒有多大的變化(注意這里的措辭,只是步驟),對於有過Android源碼開發經驗的讀者來說是再為熟悉不過的了,通常是如下二部曲:
$ source build/envsetup.sh $ lunch aosp-eng
雖然各位對上述的命令應該已經爛熟於心了,但是這里我還是簡單說明一下:
-
第一行命令”source build/envsetup.sh”引入了build/envsetup.sh 腳本。該腳本的作用是初始化編譯環境,並引入一些輔助的Shell函數,這其中就包括第二步使用 lunch 函數
-
第二行命令”lunch aosp-eng”是調用 lunch 函數,並指定參數為”aosp-eng”。lunch 函數的參數用來指定此次編譯的目標設備以及編譯類型。在這里,這兩個值分別是”aosp”和”eng”。”aosp”是 Android 源碼中已經定義好的一種產品,是為模擬器而設置的。而編譯類型會影響最終系統中包含的模塊。如果在調用lunch函數的時候沒有指定參數,那么該函數將輸出列表以供選擇,列表內容不同Android版本,不同廠家的基線源碼會有所不同,如下:
這里補充一點對Android的源碼編譯類型簡單說明一下,它可以分為如下三種功能,每種類型的特點如下:
1.2 Android編譯各種常見命令
在編譯環境初始化完成后,我們就可以使用各種各種編譯環境提供的指令和make編譯命令族來開啟Android的構建之旅了,這里我簡單的總結了下,我們在Android編譯中可能會用到的編譯環境提供的指令和make編譯命令族,如下:
1.2.1 常見的Android命令指令
指令 | 說明 |
---|---|
croot | 切到Android源碼樹的根目錄(當你深入Android源碼樹的子目錄,想回到根目錄的時候此命令就非常實用了) |
m | 相當於在源碼樹的根目錄執行make,並且該命令不一定要在根目錄下執行 |
mm | 編譯當前目錄路徑下的所有模塊(包括include進來的,但是不包括存在依賴關系模塊) |
mma | 編譯當前目錄路徑下的所有模塊(包括include進來的,且包括存在依賴關系模塊) |
mmm[module_path] | 編譯指定目錄路徑下的所有模塊(包括include進來的,但是不包括存在存在依賴關系模塊) |
mmma[module_path] | 編譯指定目錄路徑下的所有模塊(包括include進來的,包括存在存在依賴關系模塊) |
cgrep | 對C/C++文件執行grep(即grep的時候只搜尋C/C++文件類型,注意這里也包括.h文件類型) |
jgrep | 對Java文件執行grep(即grep的時候只搜尋Java文件類型) |
resgrep | 在所有res/.xml文件上執行 grep即grep的時候只搜尋res/.xml文件類型) |
printconfig | 顯示當前Android編譯的相關配置信息 |
add_lunch_combo | 在lunch命令的的菜單中添加一個條目 |
這里我們對上述表格中的不包括存在依賴關系模塊::
1.依賴關系模塊這個要怎么說呢,這里我們舉個栗子!譬如模塊A的編譯需要依賴模塊B,此時的B是一個so庫。
2.假如我們通過mm或者mmm編譯模塊A的時候,此時B模塊還沒有編譯那么此時就會報錯
3.假如我們使用的是mma或者mmma編譯模塊A,假如依賴的模塊B還沒有編譯,那么會先將模塊B編譯OK,然后編譯模塊A(當然這里只是舉栗子,可能A還依賴C,D同理也會先編譯)
1.2.2 make編譯命令族
Android的Build編譯系統處理常見的make單命令之外,還提供了其它的一系列make命令族,這里我們簡單過下:
指令 | 說明 |
---|---|
make update-api | 更新API文件,在framework API改動之后,需要首先執行該命令來更新API,公開的API記錄在frameworks/base/api目錄下 |
make | Android默認系統編譯指令,會編譯出整個系統的所有鏡像(其實質最終執行的是make droid) |
make droid | 同上 |
make sdk | 編譯出Android的SDK開發套件 |
make clean-sdk | 清理SDK的編譯產物 |
make dist | 執行整個編譯,並將 MAKECMDGOALS變量定義的輸出文件拷貝到 /out/dist目錄下, 這個命令在實際中用的比較少 |
make all | 編譯所有內容,不管當前產品的定義中是否會包含,官方解釋如下: builds everything make droid does,plus everything whose LOCAL_MODULE_TAGS do not include the “droid” tag. The build server runs this to make sure that everything that is in the tree and has an Android.mk builds. |
make help | 幫助信息命令,顯示當前Android版本主要支持的make命令 |
make snod | 從已經編譯出的包快速構建系統鏡像(譬如你重新單獨編譯了某個模塊,然后想快速進行打包到system.img,可以使用此命令加快速度) |
make clean-$(LOCAL_MODULE) | Let you selectively clean one target. For example, you can type make clean-libutils and t will delete libutils.so and all of the intermediate files. 即清理掉一個指定模塊的編譯結果和中間產物 |
make clean-$(LOCAL_PACKAGE_NAME) | Let you selectively clean one target. For example, you can type make clean-Home and it will clean just the Home app… 即清理掉一個指定模塊的編譯結果和中間產物 |
make clean | deletes all of the output and intermediate files for this configuration. This is the same as rm -rf out/<configuration>/ 通常刪除的是整個Android源碼工程的out/*目錄 |
make clobber | deletes all of the output and intermediate files for all configurations. This is the same as rm -rf out/. 這個命令在實際中,應用得比較少 |
make dataclean | deletes contents of the data directory inside the current combo directory. This is especially useful on the simulator and emulator, where the persistent data remains present between builds. 這個命令在實際中應用得也比價少 |
make installclean | 當我們在執行切換編譯目標時可以執行make installclean,用以清除之前編譯生成的文件,但是又不會將整個out目錄清空,這樣可以加快編譯目標的構建速度 |
make LOCAL_MODULE | 編譯一個指定的模塊,LOCAL_MODULE 為模塊的名稱,這種編譯方法通常運用在整個Android工程沒有構建,但是想快速編譯一個模塊時可以使用,可以加快單個模塊構建速度 |
make targets | will print a list of all of the LOCAL_MODULE names you can make. |
make libandroid_runtime | 編譯所有JNI framework內容。 |
make framework | 編譯所有Javaframework內容(做Android framework開發的小伙們對這條命令應該是再熟悉不過的了)。 |
make services | 編譯系統服務和相關內容 |
make bootimage | 編譯生成boot.img |
make recoveryimage | 編譯生成recovey.img |
make cacheimage | 編譯生成cache.img |
make systemimage | 編譯生成system.img |
make vendorimage | 編譯生成vendor.img |
make superimage | 編譯生成superi.img |
對上述的make命令有幾點需要注意:
1.可能在不同的Android版本有不同表現,且有的可能已經不支持了
2.讀者最好對於每個make編譯命令,自行使用一番,然后慢慢品嘗
二.Android編譯的發展史簡介
有過一定Android開發經驗的讀者應該知道Android最初是用Android.mk配置來編譯源碼的(這里的Android.mk本質上有點類似Makefile文件)。但是隨着Android版本的迭代,源碼工程文件越來越大,包含的模塊越來越多,而以Android.mk組織的項目編譯花費的時間越來越多。面對這個嚴峻的問題,Android的媽咪谷歌終於在在Android7.0開始引入了ninja編譯系統。相對於make來說ninja在大的項目管理中速度和並行方面有突出的優勢,因此Google采用了ninja來取代之前使用的make。由於Android.mk的數量巨大且復雜,不可能把所有的Android.mk改寫成ninja的構建規則,因此Google搞了個kati工具,用於將Androd.mk轉換成ninja的構建規則文件build.ninja,再使用ninja來進行構建工作。
Android編譯的發展依然沒有停止進化,果不其然Android8.0開始,Google引入了Android.bp文件來替代之前的Android.mk文件,Android.bp只是純粹的配置文件,不包括分支、循環等流程控制,本質上就是一個json配置文件。同時還引入Soong這個工具,用於將Android.bp轉換為ninja的構建規則文件build.ninja,再使用ninja來進行構建工作。但之前的模塊全部是用Android.mk來定義的,google不可能一下子把所有模塊都修改成Android.bp,只能逐步替換。無論是Android.mk還是Android.bp最后都是轉化成ninja的構建規則,再進行編譯的。
如果你對上述的概述,還是覺得太麻煩了,這里我們整體來概括一下Android build系統隨着Android版本相應的發展演變過程:
- Android 7.0引入ninja和kati
- Android 8.0使用Android.bp來替換Android.mk,引入Soong
- Android 9.0強制使用Android.bp
2.1 Soong、Blueprint、Kati、Ninja關系
前面一頓咔咔,我們簡單介紹了Android編譯系統的范展示,其中突然一下子冒出了許多的概念,這里我們先暫且不對其中涉及的概念講述,我們先說說Soong、Blueprint、Kati、Ninja之間的關系,如下:
上圖是整體的關系圖,同時在Android源碼工程構建過程中的轉換關系如下:
如果對上述的關系還是沒有捯飭清楚的,我們再來說說,說說:
- 首先通過Kati將Android.mk轉換成ninja格式的文件
- 通過androidmk將將Android.mk轉換成Android.bp,但是只針對沒有分支、循環等流程控制的Android.mk才有效,如果對於有控制流的就必須手動了具體可以想見博客Android.bp正確姿勢添加宏控制編譯指南
- 通過Blueprint+ Soong將Android.bp轉換成ninja格式的文件
不容易啊,這里我們對涉及到Ninja, kati, Soong, bp關系搞清楚了(各種三角戀)!那么關於它們的概念,接下來我們也得簡單介紹介紹,安排上才行!
2.2 Kati簡介
Kati是專為Android開發的一個基於Golang和C++的工具,主要功能是把Android中的Android.mk文件轉換成 Ninja文件。代碼路徑是build/kati,編譯后的產物是ckati。
Kati代碼是開源的,可以把它clone下來,如果感興趣可以查看下其實現原理
這里我們構建一個通過Android.mk配置的LOCAL_MODULE模塊,然后通過top命令就可以查看在編譯的過程中執行了ckati的命令。
2.3 Ninja簡介
ninja是一個編譯框架,會根據相應的ninja格式的配置文件進行編譯,但是ninja文件一般不會手動修改,而是通過將Android.bp文件轉換成ninja格文件來編譯。
2.4 Android.bp簡介
Android.bp的出現就是為了替換Android.mk文件。而bp跟mk文件不同,它是純粹的配置,沒有分支、循環等流程控制,不能做算數邏輯運算。如果需要控制邏輯,那么只能通過Go語言編寫。Android的媽咪谷歌為了讓開發者能更加的快速掌握Android.bp特意提供了androidmk命令(關於它的詳細介紹可以參見博客Android.bp入門指南之Android.mk轉換成Android.bp,這里就不過多的戲說了)用於Android.mk轉換成Android.bp使用,如下轉換命令:
$ androidmk Android.mk > Android.bp
2.5 Blueprint和Soong構建編譯系統
2,5.1 Soong簡介
Soong類似於之前的Makefile編譯系統的核心,負責提供Android.bp語義解析,並將之轉換成Ninja文件。Soong還會編譯生成一個androidmk命令,用於將Android.mk文件轉換為Android.bp文件,不過這個轉換功能僅限於沒有分支、循環等流程控制的Android.mk才有效。
2.5.2 Blueprint簡介
Blueprint是生成、解析Android.bp的工具,是Soong的一部分。Soong負責Android編譯而設計的工具,而Blueprint只是解析文件格式,Soong解析內容的具體含義。Blueprint和Soong都是由Golang寫的項目,從Android 7.0,prebuilts/go/目錄下新增Golang所需的運行環境,在編譯時使用。並且因為Soong和Blueprint是Google谷歌為Android.bp特別定制的工具,所以不需要要摘出來單獨來操作。
三.高版本P/Q/R源碼編譯
通過前面的章節我們了解Android編譯環境的基本構建和編譯的發展史,那么本章節將重點分析Android O之后高階版本的編譯的不同之處。並且本文的博客前年也有說到是以高通版本的Android為基線的。所以在開始本章節的博客前,有兩個知識點需要提前介紹下,一個是Android Q以及之后的動態分區,以及qssi的概念!
3.1 Android動態分區
動態分區是Android的用戶空間分區系統。使用此分區系統,您可以在無線下載(OTA)更新期間創建、銷毀分區或者調整分區大小。借助動態分區,供應商無需擔心各個分區(例如system、vendor和product)的大小。取而代之的是,設備分配一個super分區,其中的子分區可動態地調整大小。單個分區映像不再需要為將來的OTA預留空間。相反,super中剩余的可用空間還可用於所有動態分區(關於動態分區詳見谷歌官方Android實現動態分區)。
3.2 什么是QSSI
QSSI 是 Qualcomm Single System Image 的縮寫,並且高通平台從Android Q開始支持。並且其編譯也和Android原生編譯有差別,其差別如下:
3.3 具有QSSI特性Android關鍵的整體編譯流程
通過前面看到QSSI特性的固件編譯流程也和通用版本的有一定的區別,這里的編譯分為兩種模式,第一種Android的標准編譯模式,另外一種就是高通提供的編譯腳本。
這里需要注意的的是通用版本的Android還是可以直接通過make相關的分區進行直接編譯的,譬如make superimage或者直接執行make編譯
3.3.1 通過Android內置make命令編譯
source build/envsetup.sh
- 編譯 system.img
lunch qssi-userdebug
make target-files-package- 編譯除system.img外的其他img
lunch xx-userdebug
make target-files-package
3.2.2 高通提供的build.sh腳本進行編譯
- 編譯所有img,包括system和其它img
source build/envsetup.sh lunch XX-userdebug ./build.sh dist -j32
- 編譯system.img,產物在qssi目錄下
source build/envsetup.sh lunch xx-userdebug ./build.sh dist qssi_only -j32
- 編譯super.img
source build/envsetup.sh lunch xx-userdebug ./build.sh dist merge_only -j32
- 編譯其它img,例如vendorimage,如果不指定會編譯其它所有img,產物在XX目錄下
source build/envsetup.sh lunch xx-userdebug ./build.sh vendorimage dist target_only -j32
3.4 非QSSI特性的整體編譯流程
非QSSI特性的編譯流程,依然和以前的版本Android編譯變化不大,通常是如下的步驟:
source build/envsetup.sh lunch xx-userdebug make
3.5 http://jintianxuesha.com/?cate=12 動態分區刷機的方法
Android Q版本以及以上將system和vendor分區合並為super分區,無法通過adb reboot bootloader模式單獨刷動態分區里面的img,例如system,vendor,product,odm,只能刷super.img和其他的,但是fastboot模式下可以單獨刷動態分區里面的img,其方法如下:
#推薦進入fastboot模式刷機: adb reboot fastboot fastboot getvar is-userspace is-userspace: yes Finished. Total time: 0.002s fastboot flash vendor vendor.img fastboot flash system system.img fastboot flash vbmeta vbmeta.img fastboot flash vbmeta_system vbmeta_system.img #fastbootd是用戶空間的代碼,因為動態的邏輯分區只能在應用空間識別
1.如果是在linux下fastboot刷機出現權限問題,需要將fastboot的所有者屬性改成root
sudo chown root:root fastboot
sudo chmod +s fastboot
2.如果是在windows環境下使用fastboot,很大概率可能不識別fastboot,此時推薦下載360的手機助手借助它安裝對應的驅動,這樣就能進行相關的識別了,此處是個人經驗
3.6.Framework編譯
現在Android R之上的Framework的編譯已經和之前有所不同,具體參見下面解釋:
- Android R之前單獨編譯framework和service命令為:
make -j8 framework make -j8 services
- Android R之后的命令為:
make -j8 framework-minus-apex make -j8 services
四.Android為啥要引入動態分區
在前面我們簡單說了下動態分區的概念,即在Android Q以及以后得編譯包中,我們找不到了對應的system,vendor等img文件,但是多了一個super.img,system,vendor,product,ODM合並為super分區,這個就是動態分區了。簡單來說就是為了在ota的時候能夠靈活創建分區和修改分區大小,將system,vendor,odm,product合並成super分區,並在super分區上預留出一定量的free space,這樣就可以動態調整這些分區的大小,解決了ota的時候分區不足,以及調整分區的風險.。
當OTA升級之后,需要重新調整分區大小:
寫在最后
好了今天的博客Android高版本P/Q/R源碼編譯指南就到這里了,由於這是一篇實戰類型的博客所以也沒有多少總結的了,跟着干就行了。總之,青山不改綠水長流先到這里了。如果本博客對你有所幫助,麻煩關注或者點個贊,如果覺得很爛也可以踩一腳!謝謝各位了!!好了,青山不改綠水長流,各位江湖見!當然各位讀者的點贊和關注是我寫作路上前進的最大動力了,如果有啥不對或者不爽的也可以踩一踩也無妨!