天天看點

自己動手編譯最新Android源碼及SDK(Ubuntu)

<div class="markdown_views">
                    <p>在<a href="http://blog.csdn.net/dd864140130/article/details/51560664" target="_blank" rel="external nofollow"  target="_blank">Android Studio代碼調試</a>一文中,簡單的介紹了代碼調試的一些技巧.現在我們來談談android源碼編譯的一些事.(俺認為,作為android developer人人都應該有一份自己Android源碼,這樣我們就可以随時對自己有疑惑的地方通過親手調試來加強了解).</p>
           

本文适用于以下以下編譯途徑: Ubuntu 16.04上編譯Android 6.0.x 及以上版本

請首先確定自己已經安裝了Git.沒安裝的同學可以通過以下指令進行安裝:

sudo apt-get install git 
git config –global user.email “[email protected]” 
git config –global user.name “test”           
  • 1
  • 2
  • 3

其中[email protected]為你自己的郵箱.

簡要說明

android源碼編譯的四個流程:1.源碼下載下傳;2.建構編譯環境;3.編譯源碼;4運作.下文也将按照該流程講述.

源碼下載下傳

由于某牆的原因,這裡我們采用國内的鏡像源進行下載下傳.

目前,可用的鏡像源一般是科大和清華的,具體使用差不多,這裡我選擇清華大學鏡像進行說明.(參考:科大源,清華源)

repo工具下載下傳及安裝

通過執行以下指令實作repo工具的下載下傳和安裝

mkdir ~/bin
PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo           
  • 1
  • 2
  • 3
  • 4

補充說明

這裡,我來簡單的介紹下repo工具,我們知道AOSP項目由不同的子項目組成,為了友善進行管理,Google采用Git對AOSP項目進行多倉庫管理.在聊repo工具之前,我先帶你來聊聊多倉庫項目:

我們有個非常龐大的項目Pre,該項目由很多個子項目R1,R2,…Rn等組成,為了友善管理和協同開發,我們為每個子項目創立自己的倉庫,整個項目的結構如下:
自己動手編譯最新Android源碼及SDK(Ubuntu)
将一個項目Pre進行分庫後會遇到這麼一個問題:如果我們想要建立Pre分支來做feature開發,這就意味着,我們需要到每個子項目中分别建立對應的分支,這個過程如果純粹靠手工做,那簡直是個災難,利索當然我們會想寫個自動化處理程式(我們假設這個工具叫做RepoUtil)來幫助我們解決這個問題.這個RepoUtil也會有版本管理之類的需求,是以我們也用Git對其管理,并為其建立對應的倉庫.此時整個項目的結構如下:
自己動手編譯最新Android源碼及SDK(Ubuntu)
這裡RepoUtil知道整個項目Pre下的每個子項目(即維護子項目的清單),同時需要提供對這些子項目的管理功能,比如統一建立分支等.但是從”單一職責”角度來看,RepoUitl這個工具的功能過于複雜,我們完全可以将維護子項目清單這個功能抽取出來作為一個新項目sub_projects,因為子項目也會變化,是以,為其建立對應的倉庫,并用Git管理,這樣的化,RepoUtil隻需要通過簡單的對ub_projects進行依賴即可,此時整個項目的結構如下:
自己動手編譯最新Android源碼及SDK(Ubuntu)

AOSP項目結構和我上文的描述非常類似.repo工具對應RepoUtil,mainfest對應sub_projects.

總結一下:repo就是這麼一種工具,由一系列python腳本組成,通過調用Git指令實作對AOSP項目的管理.

建立源碼檔案夾

熟悉Git的同學都應該知道,我們需要為項目在本地建立對應的倉庫.同樣,這裡為了友善對代碼進行管理,我們為其建立一個檔案夾.這裡我在目前使用者目錄下建立了source檔案夾,後面所有的下載下傳的源碼和編譯出的産物也都放在這裡,指令如下:

mkdir source
cd source           
  • 1
  • 2

初始化倉庫

我們将上面的source檔案夾作為倉庫,現在需要來初始化這個倉庫了.通過執行初始化倉庫指令可以擷取AOSP項目master上最新的代碼并初始化該倉庫,指令如下:

repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest
           
  • 1
  • 2

或者使用:

repo init -u git://aosp.tuna.tsinghua.edu.cn/aosp/platform/manifest           
  • 1

兩者實作的效果一緻,僅僅隻是協定不同.

如果執行該指令的過程中,如果提示無法連接配接到 gerrit.googlesource.com,那麼我們隻需要編輯 ~/bin/repo檔案,找到REPO_URL這一行,然後将其内容修改為:

REPO_URL = 'https://gerrit-google.tuna.tsinghua.edu.cn/git-repo'           
  • 1

然後重新執行上述指令即可.

補充說明

不帶參數的manifest指令用于擷取master上最新的代碼,但是可以通過-b參數指定擷取某個特定的android版本,比如我們想要擷取android-4.0.1_r1分支,那麼指令如下:

repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-_r1           
  • 1

(AOSP項目目前所有的分支清單參看:分支清單)

注意:這裡隻是說明如何擷取指定版本的内容,後面的操作還是示範仍然基于編譯android 6.0.*,編譯其他版本可能略有不同.

同步源碼到本地

初始化倉庫之後,就可以開始正式同步代碼到本地了,指令如下:

repo sync           
  • 1

以後如果需要同步最新的遠端代碼到本地,也隻需要執行該指令即可.在同步過程中,如果因為網絡原因中斷,使用該指令繼續同步即可.不出意外,5個小時便可以将全部源碼同步到本地.是以呢,這個過程可以放在晚上睡覺期間完成.

(提示:一定要确定代碼完全同步了,不然在下面編譯過程出現的錯誤會讓你痛不欲生,不确定的童鞋可以多用repo sync同步幾次)。

由于網絡原因,在使用repo sync同步代碼的過程中會多次出錯,總不能時時刻刻刻盯着,能不能在同步失敗的情況下,自動重試呢?當然可以,我們可以寫一個簡單的shell腳本:

#!/bin/bash
#FileName source_asyn.sh
PATH=~/bin:$PATH
# 注意修改成你要編譯的版本,比如這裡我在mac上編譯的是Android-7.1.1——r6
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-._r6
repo sync
while [$? = ]: do
echo "=========download failed,again============"
sleep 
repo sync
done           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

将該檔案儲存在源碼目錄下,也就是我們的source目錄,然後執行該腳本即可,現在你可以安心的等待源碼下載下傳完成了。

除了上面那種傳統同步代碼的方式外,我們可以使用清華源提供的初始化包的方式。使用wget指令下載下傳`https://mirrors.tuna.tsinghua.edu.cn/aosp-monthly/aosp-latest.tar`,當然這裡可以直接使用迅雷或者百度網盤的離線下載下傳功能來下載下傳。下載下傳完成後解壓到源碼目錄,源碼目錄下會多出一個aosp檔案夾,進入該檔案夾,再使用repo sync指令同步即可。通過這種方式,會快很多。

#下載下傳初始化包aosp-latest.tar
wget https://mirrors.tuna.tsinghua.edu.cn/aosp-monthly/aosp-latest.tar
#解壓aosp-latest.tar
tar xf aosp-latest.tar
# 進入工程目錄aosp
cd aosp   
# 同步
repo sync 
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

注意:由于 .repo 目錄是隐藏目錄,是以在下載下傳完成之前你是不看到啥東西的。

建構編譯環境

源碼下載下傳完成後,就可以建構編譯環境了.在開始之前,我們先來看看一些編譯要求:

1. 硬體要求:

64位的作業系統隻能編譯2.3.x以上的版本,如果你想要編譯2.3.x以下的,那麼需要32位的作業系統.

磁盤空間越多越好,至少在100GB以上.意思就是,你可以去買個大點的硬碟了啊

如果你想要在是在虛拟機運作linux,那麼至少需要16GB的RAM/swap.

(實際上,我非常不推薦在虛拟機中編譯2.3.x以上的代碼.)

2. 軟體要求:

1. 作業系統要求

在AOSP開源中,主分支使用Ubuntu長期版本開發和測試的,是以也建議你使用Ubuntu進行編譯,下面我們列出不同版本的的Ubuntu能夠編譯那些android版本:

Android版本 編譯要求的Ubuntu最低版本
Android 6.0至AOSP master Ubuntu 14.04
Android 2.3.x至Android 5.x Ubuntu 12.04
Android 1.5至Android 2.2.x Ubuntu 10.04

2. JDK版本要求

除了作業系統版本這個問題外,我們還需要關注JDK版本問題,為了友善,同樣我們也列出的不同Android版本的源碼需要用到的JDK版本:

Android版本 編譯要求的JDK版本
AOSP的Android主線 OpenJDK 8
Android 5.x至android 6.0 OpenJDK 7
Android 2.3.x至Android 4.4.x Oracle JDK 6
Android 1.5至Android 2.2.x Oracle JDK 5

更具體的可以參看:Google源碼編譯要求

我現在在Ubuntu 16.04下編譯AOSP主線代碼,是以需要安裝OpenJDK 8,執行指令如下:

sudo apt-get install openjdk-8-jdk

如果你需要在Ubuntu 14.04下編譯AOSP主線代碼,同樣需要安裝OpenJDK 8,此時需要執行如下指令:

sudo apt-get update
sudo apt-get install openjdk--jdk           
  • 1
  • 2

如果你要編譯的是Android 5.x到android 6.0之間的系統版本,需要采用openjdk7.但是在Ubuntu 15.04及之後的版本的線上安裝庫中隻支援openjdk8和openjdk9的安裝.是以,如果你想要安裝openjdk 7需要首先設定ppa:

sudo add-apt-repository ppa:openjdk-r/ppa 
sudo apt-get update           
  • 1
  • 2

然後再執行安裝指令:

sudo apt-get install openjdk--jdk            
  • 1

有時候,我們需要編譯不同版本的android系統,就可能使用不同的jdk版本.關于jdk版本切換,可以使用如下指令:

sudo update-alternative --config java
sudo update-alternative --config javac           
  • 1
  • 2

3. 其他要求

Google官方建構編譯環境指南中已經說明了Ubuntu14.04,Ubuntu 12.04,Ubuntu 10.04需要添加的依賴,這裡我們就不做介紹了.我原先以為,Ubuntu16.04的設定和Ubuntu14.04的依賴設定應該差不多,但是隻能說too young too simple.

下面是Ubuntu16.04中的依賴設定:

sudo apt-get install libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-dev g++-multilib 
sudo apt-get install -y git flex bison gperf build-essential libncurses5-dev:i386 
sudo apt-get install tofrodos python-markdown libxml2-utils xsltproc zlib1g-dev:i386 
sudo apt-get install dpkg-dev libsdl1-dev libesd0-dev
sudo apt-get install git-core gnupg flex bison gperf build-essential  
sudo apt-get install zip curl zlib1g-dev gcc-multilib g++-multilib 
sudo apt-get install libc6-dev-i386 
sudo apt-get install lib32ncurses5-dev x11proto-core-dev libx11-dev 
sudo apt-get install libgl1-mesa-dev libxml2-utils xsltproc unzip m4
sudo apt-get install lib32z-dev ccache           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

(其中幾個指令中參數是重複的,但不妨礙我們)

初始化編譯環境

確定上述過程完成後,接下來我們需要初始化編譯環境,指令如下:

source build/envsetup.sh           
  • 1

執行該指令結果如下:

自己動手編譯最新Android源碼及SDK(Ubuntu)

不難發現該指令隻是引入了其他執行腳本,至于這些腳本做什麼,目前不在本文中細說.

該指令執行成功後,我們會得到了一些有用的指令,比如最下面要用到的lunch指令.

編譯源碼

初始化編譯環境之後,就進入源碼編譯階段.這個階段又包括兩個階段:選擇編譯目标和執行編譯.

選擇編譯目标

通過lunch指令設定編譯目标,所謂的編譯目标就是生成的鏡像要運作在什麼樣的裝置上.這裡我們設定的編譯目标是aosp_arm64-eng,是以執行指令:

lunch aosp_arm64-eng           
  • 1

編譯目标格式說明

編譯目标的格式:BUILD-BUILDTYPE,比如上面的aosp_arm-eng的BUILD是aosp_arm,BUILDTYPE是eng.

什麼是BUILD

BUILD指的是特定功能的組合的特定名稱,即表示編譯出的鏡像可以運作在什麼環境.其中,aosp(Android Open Source Project)代表Android開源項目;arm表示系統是運作在arm架構的處理器上,arm64則是指64位arm架構;處理器,x86則表示x86架構的處理器;此外,還有一些單詞代表了特定的Nexus裝置,下面是常用的裝置代碼和編譯目标,更多參考官方文檔
受型号 裝置代碼 編譯目标
Nexus 6P angler aosp_angler-userdebug
Nexus 5X bullhead aosp_bullhead-userdebug
Nexus 6 shamu aosp_shamu-userdebug
Nexus 5 hammerhead aosp_hammerhead-userdebug

提示:如果你沒有Nexus裝置,那麼通常選擇arm或者x86即可

什麼是BUILDTYPE

BUILD TYPE則指的是編譯類型,通常有三種:

-user:代表這是編譯出的系統鏡像是可以用來正式釋出到市場的版本,其權限是被限制的(如,沒有root權限,不鞥年dedug等)

-userdebug:在user版本的基礎上開放了root權限和debug權限.

-eng:代表engineer,也就是所謂的開發工程師的版本,擁有最大的權限(root等),此外還附帶了許多debug工具

了解編譯目标的組成之後,我們就可以根據自己目前的情況選擇了.那不知道編譯目标怎麼辦?

我們隻需要執行不帶參數的lunch指令,稍後,控制台會列出所有的編譯目标,如下:

自己動手編譯最新Android源碼及SDK(Ubuntu)

接着我們隻需要輸入相應的數字即可.

來舉個例子:你沒有Nexus裝置,隻想編譯完後運作看看,那麼就可以選擇aosp_arm-eng.

(我在ubuntu 16.04(64位)中編譯完成後啟動虛拟機時,卡在黑屏,嘗試編譯aosp_arm64-eng解決.是以,這裡我使用了aosp_arm64-eng)

開始編譯

通過make指令進行代碼編譯,該指令通過

-j

參數來設定參與編譯的線程數量,以提高編譯速度.比如這裡我們設定8個線程同時編譯:

make -j8           
  • 1

需要注意的是,參與編譯的線程并不是越多越好,通常是根據你機器cup的核心來确定:core*2,即目前cpu的核心的2倍.比如,我現在的筆記本是雙核四線程的,是以根據公式,最快速的編譯可以make -j8.

(通過

cat /proc/cpuinfo

檢視相關cpu資訊)

如果一切順利的化,在幾個小時之後,便可以編譯完成.看到

### make completed successfully (01:18:45(hh:mm:ss)) ###

表示你編譯成功了.

運作模拟器

在編譯完成之後,就可以通過以下指令運作Android虛拟機了,指令如下:

source build/envsetup.sh
lunch(選擇剛才你設定的目标版本,比如這裡了我選擇的是)
emulator           
  • 1
  • 2
  • 3

如果你是在編譯完後立刻運作虛拟機,由于我們之前已經執行過source及lunch指令了,是以現在你隻需要執行指令就可以運作虛拟機:

emulator           
  • 1

不出意外,在等待一會之後,你會看到運作界面:

自己動手編譯最新Android源碼及SDK(Ubuntu)

補充

既然談到了模拟器運作,這裡我們順便介紹模拟器運作所需要四個檔案:

  1. Linux Kernel
  2. system.img
  3. userdate.img
  4. ramdisk.img

如果你在使用lunch指令時選擇的是aosp_arm-eng,那麼在執行不帶參數的emualtor指令時,Linux Kernel預設使用的是

/source/prebuilds/qemu-kernel/arm/kernel-qemu

目錄下的kernel-qemu檔案;而android鏡像檔案則是預設使用

source/out/target/product/generic

目錄下的system.img,userdata.img和ramdisk.img,也就是我們剛剛編譯出來的鏡像檔案.

上面我在使用lunch指令時選擇的是aosp_arm64-eng,是以linux預設使用的

/source/prebuilds/qemu-kernel/arm64/kernel-qemu

下的kernel-qemu,而其他檔案則是使用的

source/out/target/product/generic64

目錄下的system.img,userdata.img和ramdisk.img.

當然,emulator指令允許你通過參數制定使用不同的檔案,具體用法可以通過

emulator --help

檢視

子產品編譯

除了通過make指令編譯可以整個android源碼外,Google也為我們提供了相應的指令來支援單獨子產品的編譯.

編譯環境初始化(即執行

source build/envsetup.sh

)之後,我們可以得到一些有用的指令,除了上邊用到的lunch,還有以下:

- croot: Changes directory to the top of the tree.
  - m: Makes from the top of the tree.
  - mm: Builds all of the modules in the current directory.
  - mmm: Builds all of the modules in the supplied directories.
  - cgrep: Greps on all local C/C++ files.
  - jgrep: Greps on all local Java files.
  - resgrep: Greps on all local res/*.xml files.
  - godir: Go to the directory containing a file.
           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

其中mmm指令就是用來編譯指定目錄.通常來說,每個目錄隻包含一個子產品.比如這裡我們要編譯Launcher2子產品,執行指令:

mmm packages/apps/Launcher2/           
  • 1

稍等一會之後,如果提示:

### make completed success fully ###

即表示編譯完成,此時在

out/target/product/gereric/system/app

就可以看到編譯的Launcher2.apk檔案了.

重新打包系統鏡像

編譯好指定子產品後,如果我們想要将該子產品對應的apk內建到系統鏡像中,需要借助

make snod

指令重新打包系統鏡像,這樣我們新生成的system.img中就包含了剛才編譯的Launcher2子產品了.重新開機模拟器之後生效.

單獨安裝子產品

我們在不斷的修改某些子產品,總不能每次編譯完成後都要重新打包system.img,然後重新開機手機吧?有沒有什麼簡單的方法呢?

在編譯完後,借助adb install指令直接将生成的apk檔案安裝到裝置上即可,相比使用make snod,會節省很多事件.

補充

我們簡單的來介紹

out/target/product/generic/system

目錄下的常用目錄:

Android系統自帶的apk檔案都在

out/target/product/generic/system/apk

目錄下;

一些可執行檔案(比如C編譯的執行),放在

out/target/product/generic/system/bin

目錄下;

動态連結庫放在

out/target/product/generic/system/lib

目錄下;

硬體抽象層檔案都放在

out/targer/product/generic/system/lib/hw

目錄下.

SDK編譯

SDK的編譯和子產品編譯非常相似,其指令如下:

首先是初始化編譯環境:

source build/envsetup.sh           
  • 1

接下來是設定編譯目标

lunch sdk-eng           
  • 1

最後通過以下指令編譯SDK

make sdk           
  • 1

出現以下提示表示編譯完成,不出意外,在out/host/linux-x86/sdk就可以看到了:

自己動手編譯最新Android源碼及SDK(Ubuntu)

注:SDK編譯完後,之前編譯的system.img等檔案會被删除,是以,在編譯SDK之前,建議自行備份一下.當然,如果被移除了也沒關系,可以通過make指令再編譯.

另外,某些指令也會删除sdk,是以在編譯SDK後,我們将其備份到另外一個目錄,以備不時之需.

錯誤集合

在編譯過程中,遇到的大部分錯誤都可以在google搜到解決方案.這裡隻列舉幾個常見的錯誤:

錯誤一: You are attemping to build with the incorrect version.具體錯誤如下:

自己動手編譯最新Android源碼及SDK(Ubuntu)

如果你認真看了建構環境的的要求,那麼這個錯誤是可以避免的.當然,這個問題也很容易解決:安裝openjdk 8,别忘了使用sudo update-alternative指令切換jdk版本.

錯誤二: Out of memory error.具體錯誤如下:

自己動手編譯最新Android源碼及SDK(Ubuntu)

這個錯誤比較常見,尤其是在編譯AOSP主線代碼時,常常會因為JVM heap size太小而導緻該錯誤.

此時有兩種解決方法:

方法一:

在編譯指令之前,修改prebuilts/sdk/tools/jack-admin檔案,找到檔案中的這一行:

JACK_SERVER_COMMAND="java -Djava.io.tmpdir=$TMPDIR $JACK_SERVER_VM_ARGUMENTS -cp $LAUNCHER_JAR $LAUNCHER_NAME"

然後在該行添加-Xmx4096m,如:

JACK_SERVER_COMMAND="java -Djava.io.tmpdir=$TMPDIR $JACK_SERVER_VM_ARGUMENTS -Xmx4096m -cp $LAUNCHER_JAR $LAUNCHER_NAME"

然後再執行time make -8j

方法二:

在控制台執行以下指令:

export JACK_SERVER_VM_ARGUMENTS="-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4096m"
out/host/linux-x86/bin/jack-admin kill-server
out/host/linux-x86/bin/jack-admin start-server           
  • 1
  • 2
  • 3

如圖:

自己動手編譯最新Android源碼及SDK(Ubuntu)

執行完該指令後,再使用make指令繼續編譯.某些情況下,當你執行jack-admin kill-server時可能提示你指令不存在,此時去你去out/host/linux-x86/bin/目錄下會發現不存在jack-admin檔案.如果我是你,我就會重新repo sync下,然後從頭來過.(其實,你可以用方法一,而無須重新repo sync)

注意:在編譯SDK的時候同樣可能出現因jvm heap size太小而導緻編譯錯誤,一旦出現用以上任何一種方案都可以解決.

錯誤三:使用emulator時,虛拟機停在黑屏界面,點選無任何響應.此時,可能是kerner核心問題,解決方法如下:

執行如下指令:

./out/host/linux-x86/bin/emulator -partition-size  -kernel ./prebuilts/qemu-kernel/arm/kernel-qemu-armv7 
           
  • 1
  • 2

通過使用kernel-qemu-armv7核心 解決模拟器等待黑屏問題.而-partition-size 1024 則是解決警告: system partion siez adjusted to match image file (163 MB >66 MB)

如果你一開始編譯的版本是aosp_arm-eng,使用上述指令仍然不能解決等待黑屏問題時,不妨編譯aosp_arm64-eng試試.

錯誤四: Out of memory error.具體錯誤如下:

該錯誤和第二條本質一樣,尤其是你在進行編譯Android 8.*版本時更容易遇到該問題,其表現如下:

自己動手編譯最新Android源碼及SDK(Ubuntu)

或是這樣:

自己動手編譯最新Android源碼及SDK(Ubuntu)

無論表現是什麼,但錯誤的本質還是Java heap space 。當然,其解決該問題的方式和之前的第二條解決方案一樣。

簡單的來說一下具體步驟:打開prebuilts/sdk/tools/jack-admin檔案,修改以下内容:

自己動手編譯最新Android源碼及SDK(Ubuntu)

保險起見,你需要重走編譯流程,即執行以下指令:

source build/envsetup.sh
lunch
make -j12           
  • 1
  • 2
  • 3

不出意外,你很快就可以看到編譯成功的提示了.

結束吧

到現在為止,你已經了解了整個android編譯的流程.除此之外,我也簡單的說明android源碼的多倉庫管理機制.下面,不妨自己動手嘗試一下.