編輯:關於Android編程
簡單的介紹了Android Studio代碼調試的一些技巧.現在呢,我們來談談android源碼編譯的一些事.(俺認為,作為android developer人人都應該有一份自己Android源碼,這樣我們就可以隨時對自己有疑惑的地方通過親手調試來加強理解).
本文使用最新的Ubuntu 16.04,在開始之前,請首先確保自己已經安裝了Git.沒安裝的同學可以通過以下命令進行安裝:
sudo apt-get install git
git config –global user.email “[email protected]”
git config –global user.name “test”
其中[email protected]為你自己的郵箱.
在步入正題之前,我們先來說一下android源碼編譯的四個流程:1.源碼下載;2.構建編譯環境;3.編譯源碼;4運行.下文也將按照該過程進行講述.
由於某牆的原因,這裡我們采用國內的鏡像源進行下載.目前,可用的鏡像源一般是科大和清華的,具體使用差不多
通過執行以下命令實現repo工具的下載和安裝
mkdir ~/bin
PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
這裡,我來簡單的介紹下repo工具,我們知道AOSP項目由不同的子項目組成,為了方便進行管理,Google采用Git對AOSP項目進行多倉庫管理.在聊repo工具之前,我先帶你來聊聊多倉庫項目:
我們有個非常龐大的項目Pre,該項目由很多個子項目R1,R2,…Rn等組成,為了方便管理和協同開發,我們為每個子項目創立自己的倉庫,整個項目的結構如下:
將一個項目Pre進行分庫後會遇到這麼一個問題:如果我們想要創建Pre分支來做feature開發,這就意味著,我們需要到每個子項目中分別創建對應的分支,這個過程如果純粹靠手工做,那簡直是個災難,利索當然我們會想寫個自動化處理程序(我們假設這個工具叫做RepoUtil)來幫助我們解決這個問題.這個RepoUtil也會有版本管理之類的需求,因此我們也用Git對其管理,並為其創建對應的倉庫.此時整個項目的結構如下:
這裡RepoUtil知道整個項目Pre下的每個子項目(即維護子項目的列表),同時需要提供對這些子項目的管理功能,比如統一創建分支等.但是從”單一職責”角度來看,RepoUitl這個工具的功能過於復雜,我們完全可以將維護子項目列表這個功能抽取出來作為一個新項目sub_projects,因為子項目也會變化,因此,為其創建對應的倉庫,並用Git管理,這樣的化,RepoUtil只需要通過簡單的對ub_projects進行依賴即可,此時整個項目的結構如下:
vcWxvtfps8kszai5/bX308NHaXTD/MHuyrXP1rbUQU9TUM/uxL+1xLncwO0uPC9wPg0KPGgzIGlkPQ=="建立源碼文件夾">建立源碼文件夾
熟悉Git的同學都應該知道,我們需要為項目在本地創建對應的倉庫.同樣,這裡為了方便對代碼進行管理,我們為其創建一個文件夾.這裡我在當前用戶目錄下創建了source文件夾,後面所有的下載的源碼和編譯出的產物也都放在這裡,命令如下:
mkdir source
cd source
我們將上面的source文件夾作為倉庫,現在需要來初始化這個倉庫了.通過執行初始化倉庫命令可以獲取AOSP項目master上最新的代碼並初始化該倉庫,命令如下:
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest
或者使用:
repo init -u git://aosp.tuna.tsinghua.edu.cn/aosp/platform/manifest
兩者實現的效果一致,僅僅只是協議不同.
如果執行該命令的過程中,如果提示無法連接到 gerrit.googlesource.com,那麼我們只需要編輯 ~/bin/repo文件,找到REPO_URL這一行,然後將其內容修改為:
REPO_URL = 'https://gerrit-google.tuna.tsinghua.edu.cn/git-repo'
然後重新執行上述命令即可.
不帶參數的manifest命令用於獲取master上最新的代碼,有時候,我們想要指定的某個版本的源碼,此時可以通過-b參數指定獲取某個特定的android版本,比如我們想要獲取android-4.0.1_r1分支,那麼命令如下:
repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-4.0.1_r1
(這裡AOSP項目當前所有的分支列表可以在官網查看:分支列表)
初始化倉庫之後,就可以開始正式同步代碼到本地了,命令如下:
repo sync
以後如果需要同步遠程代碼到本地,也只需要執行該命令即可.在同步過程中,如果因為網絡原因中斷,使用該命令繼續同步即可.不出意外,5個小時便可以將全部源碼同步到本地.所以呢,這個過程可以放在晚上睡覺期間完成.
(提示:一定要確定代碼完全同步了,不然在下面編譯過程出現的錯誤會讓你痛不欲生,不確定的童鞋可以多用repo sync同步幾次)
源碼下載完成後,就可以構建編譯環境了.在開始之前,我們先來看看一些編譯要求:
1. 硬件要求:
64位的操作系統只能編譯2.3.x以上的版本,如果你想要編譯2.3.x以下的,那麼需要32位的操作系統.
磁盤空間越多越好,至少在100GB以上.意思就是,你可以去買個大點的硬盤了啊
如果你想要在是在虛擬機運行linux,那麼至少需要16GB的RAM/swap.
(實際上,我非常不推薦在虛擬機中編譯2.3.x以上的代碼.)
2. 軟件要求:
1. 操作系統要求
在AOSP開源中,主分支使用Ubuntu長期版本開發和測試的,因此也建議你使用Ubuntu進行編譯,下面我們列出不同版本的的Ubuntu能夠編譯那些android版本:
2. JDK版本要求
除了操作系統版本這個問題外,我們還需要關注JDK版本問題,為了方便,同樣我們也列出的不同Android版本的源碼需要用到的JDK版本:
更具體的可以參看: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-8-jdk
如果你要編譯的是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
然後再執行安裝命令:
sudo apt-get install openjdk-7-jdk
有時候,我們需要編譯不同版本的android系統,就可能使用不同的jdk版本.關於jdk版本切換,可以使用如下命令:
sudo update-alternative --config java
sudo update-alternative --config javac
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.2-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
(其中幾個命令中參數是重復的,但不妨礙我們)
確保上述過程完成後,接下來我們需要初始化編譯環境,命令如下:
source build/envsetup.sh
執行該命令結果如下:
不難發現該命令只是引入了其他執行腳本,至於這些腳本做什麼,目前不在本文中細說.
該命令執行成功後,我們得到了一些有用的命令,比如最下面要用到的lunch命令.
初始化編譯環境之後,我們正式進入源碼編譯階段.這個階段又包括兩個階段:選擇編譯版本及執行編譯.
通過lunch指令設置要要編譯的具體版本.比如,在這裡我們要編譯aosp_arm64-eng,因此執行指令:
lunch aosp_arm64-eng
如果你不知道自己要編譯什麼版本,那麼只需要執行不帶參數的lunch指令.之後,控制台會列出當前源碼支持的所有設備型號及編譯類型:
然後根據你的需要選擇即可,輸入相應的數字即可.通常,我們選擇aosp_arm-eng即可,但是在ubuntu 16.04(64位)中編譯完成後啟動虛擬機時可能會遇到卡在黑屏或者無響應的情況,此時不妨采用aosp_arm64-eng.這裡,我選擇2,即aosp_arm64-eng
我們簡單的介紹,編譯版本的命令規則,其形式都是BUILD-BUILDTYPE.
BUILD指的是特定功能的組合特定代碼集合的名稱,源碼可以運行在什麼環境,比如full表示模擬器等.BUILD TYPE則指的是編譯類型,通常有三種:
-user:首先的訪問,適用於發行版的產品.
-userdebug:和user模式類似,但是具有root權限並具有調試功能,適用於調試.
-eng:工程機模式,包含額外的調試工具.
不難發現,我們需要以eng模式進行編譯,以便我們調試.
通過make指令進行代碼編譯,該指令可以指定-j
參數來設置參與編譯的線程數量,以提高編譯速度.比如這裡我們設置8個線程同時編譯:
make -j8
需要注意的是,參與編譯的線程並不是越多越好,通常是根據你機器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(選擇剛才你設置的目標版本,比如這裡了我選擇的是2)
emulator
如你是在編譯完後立刻運行虛擬機,由於我們之前已經執行過source及lunch命令了,因此現在你只需要執行命令就可以運行虛擬機:
emulator
不出意外,在等待一會之後,你會看到運行界面:
既然談到了模擬器運行,這裡我們順便介紹模擬器運行所需要四個文件:
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.
通過make命令編譯可以整個android源碼,這種需求相對較少.更多的情況是,我們修改或者新增一些模塊,那麼如何編譯單獨的模塊呢?
Google也為開發者提供了相應的命令來支持單獨的模塊的編譯.上面我們提到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.
這裡我最為常用的就是mmm,也就是用來編譯指定目錄.通常來說,每個目錄只包含一個模塊.比如這裡我們編譯Launcher2模塊:
mmm packages/apps/Launcher2/
稍等一會之後,如果提示### 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使用,很簡單,只需要執行命令make sdk
即可.
在編譯過程中,基本上會遇到各種各樣的錯誤,大部分錯誤我們都可以在google搜到響應的解決方案.這裡只簡單的列舉我遇到的幾個錯誤:
錯誤一: You are attemping to build with the incorrect version.具體錯誤如下:
如果你認真看了構建環境的的要求,那麼這個問題是可以避免的.當然,發生了也很容易解決:安裝openjdk 8,別忘了使用sudo update-alternative命令切換jdk版本.
錯誤二: Out of memory error.具體錯誤如下:
這個錯誤比較常見,尤其是在編譯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
如圖:
執行完該命令後,再使用make命令繼續編譯.某些情況下,當你執行jack-admin kill-server時可能提示你命令不存在,此時去你去out/host/linux-x86/bin/目錄下會發現不存在jack-admin文件.如果我是你,我就會重新repo sync下,然後從頭來過.
錯誤三:使用emulator時,虛擬機停在黑屏界面,點擊無任何響應.此時,可能是kiner內核問題,解決方法如下:
執行如下命令:
./out/host/linux-x86/bin/emulator -partition-size 1024 -kernel ./prebuilts/qemu-kernel/arm/kernel-qemu-armv7
通過使用kernel-qemu-armv7內核 解決模擬器等待黑屏問題.而-partition-size 1024 則是解決警告: system partion siez adjusted to match image file (163 MB >66 MB)問題.
如果你一開始編譯的版本是aosp_arm-eng,使用上述命令仍然不能解決等待黑屏問題時,不妨編譯aosp_arm64-eng試試.
到現在為止,我們已經說明了整個android編譯的流程,除此之外,也簡單的android源碼的多倉庫管理機制.下面,你需要的就是,自動動手,為自己編譯一份源碼.後面,我會繼續在此基礎上增加一些原理的說明,並結合Android Studio進行源碼調試.
Android 中下拉菜單,即如html中的<select>,關鍵在於調用setDropDownViewResource方法,以XML的方式定義下拉菜單要顯示
dom 我的理解就是先把整個文檔讀取到內存中,然後才解析,讀取大點的文件的話這樣效率就會很低。而 sax和pull 它們是基於事件解析的。一行一行去解析,效率會高點。下面
在Android開發中,往往要用到自定義的控件來實現我們的需求或效果。在使用自定義 控件時,難免要用到自定義屬性,那怎麼使用自定義屬性呢?在文件res/values/下新
最近在開發中遇到了這樣一個問題,在下拉刷新組件中包含了一個輪播圖組件,當左右滑動的圖片時很容易觸發下拉刷新,如下圖所示:如圖中紅色箭頭所示方向切換輪播圖,很容易觸發下拉刷