編譯 Android 7.1 (LineageOS 14.1) 並安裝到樹莓派3上

陳培郁 (Raymond)
豬窩部落格
Published in
14 min readJan 10, 2018

這篇文章簡單描述我下載並編譯 LineageOS 原始碼,並安裝到 Raspberry Pi 3 上面的步驟,以及過程中遇到的問題與解法,希望供其他有興趣的人參考。

LineageOS 是一個開放原始碼的第三方 Android OS,提供一些預先安裝的 APP 與特有功能,更多資料可以參考 wiki。由於擁有廣大且活躍的社群資源,因此很適合想要自訂 Android 系統的玩家使用。

Copyright Geek Till It Hertz

準備硬體設備

  • 樹莓派 3 與搭配的外接顯示設備(如配備 HDMI 的螢幕)
  • Micro SD 卡與讀卡機
  • 電腦

我們編譯所產生的系統會燒錄到 Micro SD 卡上面,並裝在樹莓派上面作為開機碟使用。只是要讓系統運作的話,大概 4GB 就可以了,但為了能夠安裝更多的 APP 與存放資料,還是建議使用容量更大的卡片。

我們也需要一台工作用的電腦,用來編譯整個系統。這台電腦需要大約 250 GB 的磁碟空間以容納原始碼、編譯過程產生的檔案以及各種編譯所需的程式庫。另外,建議記憶體與 swap 最好要有 16GB 左右(我是 8GB 實體記憶體加上 8GB swap。)

架設編譯環境

推薦使用 64 位元的 Ubuntu 16.04 LTS 版本。理論上使用不同 Ubuntu 版本也可以,但我在 16.04 編譯的過程最為順利。

首先需要安裝下列套件:

sudo apt-get update
sudo apt-get install bc bison build-essential ccache curl flex g++-multilib gcc-multilib git gnupg gperf lib32ncurses5-dev lib32readline-dev lib32z1-dev libesd0-dev liblz4-tool libncurses5-dev libsdl1.2-dev libssl-dev libwxgtk3.0-dev libxml2 libxml2-utils lzop pngcrush rsync schedtool squashfs-tools xsltproc zip zlib1g-dev python-mako imagemagick openjdk-8-jdk gcc-arm-linux-gnueabihf

幾個東西要特別提一下:

  • imagemagick:這是一套繪圖軟體,LineageOS 使用它來產生一些動畫效果,在編譯階段會需要用到
  • openjdk:如果你使用的系統不是 16.04 而是更舊的版本,可能不會有 openjdk 8 的套件,需要再自行尋找 deb 檔來安裝。若你需要自行安裝 openjdk 8,注意不要使用 Google 在 AOSP 文件中建議的 8u45 版本!這版本太舊無法編譯 LineageOS,請使用更新的版本
  • gcc-arm-linux-gnueabihf:ARM 的 toolchain 套件,需要用它來編譯 Linux 核心供樹莓派平台使用

下載原始碼

要下載原始碼,需要用到 Google 提供的 repo 工具。我們先使用下面這些指令下載它:

mkdir ~/bin
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo

這會在你的 home 目錄下建立一個 bin 資料夾,並把 repo 工具存放在此處。

接著我們來準備下載 LineageOS 14.1 的原始碼。如果你之前沒有設定過 git,請先設定好你的 username 與 email:

git config --global user.name "Your Name"
git config --global user.email "your_email@email.com"

接著執行下面指令:

mkdir -p ~/android/lineage
cd ~/android/lineage
~/bin/repo init -u https://github.com/LineageOS/android.git -b cm-14.1

這個步驟會在home 目錄下建立一個 android/lineage 資料夾,然後進行 repo 初始化的動作。如果你的 git 沒有設定完成,這個步驟會要求你進行設定。初始化完成後,這個目錄下會多出一個叫做 .repo 的隱藏資料夾。

我們還需要額外下載一份供樹莓派 3 使用的 manifest 檔案:

curl --create-dirs -L -o .repo/local_manifests/manifest_brcm_rpi3.xml -O -L https://raw.githubusercontent.com/lineage-rpi/android_local_manifest/cm-14.1/manifest_brcm_rpi3.xml

這份檔案是在 github 上面,我們透過 curl 把它下載到 .repo/local_manifests 資料夾下。用它來取代 LineageOS 的部分專案內容,改成適用於樹莓派 3 的版本。

最後,執行以下指令正式下載原始碼:

~/bin/repo sync -j32

下載過程會耗費數個小時,端看你的網路環境。最後的 -j32 參數代表要使用 32 條 thread 同時進行下載,可以縮短下載所需時間。

準備編譯

下載完程式碼之後,還有一些修正要加上去,可以解決編譯期間可能會遇到的問題。

首先編輯 vendor/cmsdk/Android.mk 檔案,把第 166 行的

LOCAL_REQUIRED_MODULES := services

改成

LOCAL_REQUIRED_MODULES := org.cyanogenmod.platform-res services

這是修正一個相依性相關的問題。在一些比較緩慢的電腦上,或是沒有使用multi-thread 進行編譯時有可能會遇到問題,因此要加上這行修正相依性。更多資訊請參考這裡

接著,請設定這段環境變數:

export JACK_SERVER_VM_ARGUMENTS="-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4g"

這會設定 jack(Android 使用到的一個編譯工具)編譯時所需要的參數。重要的是後面的 -Xmx4g 這個參數,它會加大 jack 使用的記憶體空間,以免編譯過程因為記憶體空間不足而失敗。更多資訊請參考這裡

最後,修正兩個跟顯示相關的問題。編輯 frameworks/av/media/libstagefright/colorconversion/SoftwareRenderer.cpp,把 115~117 行註解掉,變成這樣:

//                halFormat = HAL_PIXEL_FORMAT_YV12;
// bufWidth = (mCropWidth + 1) & ~1;
// bufHeight = (mCropHeight + 1) & ~1;

還有編輯 frameworks/base/opengl/java/android/opengl/GLSurfaceView.java,把第 995 行的

super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0)

改成這樣:

super(8, 8, 8, 8, withDepthBuffer ? 24 : 0, 0);

更多資訊請參考這裡

開始編譯 Android

到這邊我們已經準備好可以進行編譯了。回到 lineage 目錄下,我們再來下這些指令:

source build/envsetup.sh
lunch lineage_rpi3-userdebug
make ramdisk systemimage -j4

第一行的 source build/envsetup.sh 會收集並準備編譯過程中需要的資訊,接著第二行會設定我們要編譯的目標,第三行才是真正的 make。

編譯一樣要花上數個小時。如果過程中遇到問題,可以先到本文後面找找看是不是我遇到的這些問題,以及可能的解法。

編譯 Linux Kernel

前一節的編譯 Android 成功後,我們還要接著編譯 Linux 核心。這一步基本上就是要使用 ARM 的 toolchain 來編譯出可以供數莓派使用的 Linux。我們使用以下指令:

cd kernel/rpi
ARCH=arm make lineageos_rpi3_defconfig
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make zImage dtbs -j4

我們指定架構是 arm、使用 arm-linux-gnueabihf 為交叉編譯器來進行編譯。這個過程比較快,可能可以在一個小時內完成。

燒錄到 SD 卡

所有必要的東西都編譯完成了,接著我們就可以來準備進行燒錄了。首先,我們需要將 SD 卡分割成四個磁區:

  • 磁區 1,需要 512MB,格式化為 FAT32,標示為可開機,儲存開機時所需資料
  • 磁區 2,需要 2GB,格式化為 ext4,儲存 Android 系統資料
  • 磁區 3,需要 512MB,格式化為 ext4,用作快取
  • 磁區 4,將所有剩餘空間分配於此,格式化為 ext4,儲存使用者與 APP 資料

以上四個磁區都是 primary。需特別注意磁區 1 必須格式化為 FAT32,否則無法用它來開機。

在 Ubuntu 中,可以使用 fdisk 來進行磁區分割的動作,然後使用 mkfs.vfat 與 mkfs.ext4 來格式化各磁區。最終磁區資訊應該會大致如下:

Device     Boot   Start      End  Sectors  Size Id Type
/dev/sdb1 * 2048 1050623 1048576 512M b W95 FAT32
/dev/sdb2 1050624 5244927 4194304 2G 83 Linux
/dev/sdb3 5244928 6293503 1048576 512M 83 Linux
/dev/sdb4 6293504 62945279 56651776 27G 83 Linux

在此 /dev/sdb 就是我的 SD 卡,sdb1 ~ sdb4 分別對應到前面敘述的四個磁區。

對於磁區 1,我們首先把它 mount 到系統上:

sudo mount /dev/sdb1 /path/to/mount/point

接著將這些檔案複製進去:

cp device/brcm/rpi3/boot/* /mount/point
cp kernel/rpi/arch/arm/boot/zImage /mount/point
cp kernel/rpi/arch/arm/boot/dts/bcm2710-rpi-3-b.dtb /mount/point/
mkdir /mount/point/overlays
cp kernel/rpi/arch/arm/boot/dts/overlays/vc4-kms-v3d.dtbo /mount/point/overlays/vc4-kms-v3d.dtbo
cp out/target/product/rpi3/ramdisk.img /mount/point

請自行將路徑更換成你的實際掛載點路徑。最後記得 umount。

對於磁區 2,我們要燒綠整個檔案系統進去:

sudo dd if=/out/target/product/rpi3/system.img of=/dev/sdb2 bs=1M

這一步是把我們編譯出來的 system.img 燒錄至磁區 2,這裡使用 dd 指令來完成。做這個動作之前不需要掛載。

磁區 3 跟 4 保留空間將來使用。

大功告成

好了,東西都準備就緒了。把你的 SD 卡插到數莓派 3 上面,接上螢幕開機看看吧。

將數莓派接上螢幕,並使用鍵盤滑鼠來控制它

編譯時的錯誤與解法

下面列出我在編譯階段時遇過的狀況。在這篇文章裡我已經有把解決方法加到準備步驟中,如果仍然遇到狀況,請再根據這段的內容確認一下設定是否正確。

錯誤 1:javadoc 找不到必需的檔案:

javadoc: error — In doclet class com.google.doclava.Doclava, method start has thrown an exception java.lang.reflect.InvocationTargetException
com.sun.tools.javac.code.Symbol$CompletionFailure: class file for com.android.okhttp.ConnectionPool not found

解法:這個問題可能是因為 openjdk 的版本不正確導致。請不要使用 Google 在 AOSP 文件裡描述的版本,而是使用 Ubuntu 16.04 套件庫能抓到的新版本。在我的機器上安裝的是 1.8.0_151,你可以用 java -version 指令確認自己安裝的版本。

錯誤 2:找不到某個 manifest 檔案:

javac: file not found: /home/username/android/system/out/target/common/obj/APPS/org.cyanogenmod.platform-res_intermediates/src/cyanogenmod/platform/Manifest.java

解法:這是 makefile 相依性的問題。編輯 vendor/cmsdk/Android.mk 檔案,把第 166 行的

LOCAL_REQUIRED_MODULES := services

改成

LOCAL_REQUIRED_MODULES := org.cyanogenmod.platform-res services

或者,編譯時使用 multi-thread(加入 -j4 參數)似乎可以避免此問題。

錯誤 3:記憶體不足

GC overhead limit exceeded
Try increasing heap size with java option '-Xmx<size>'
Warning: This may have produced partial or corrupted output.
ninja: build stopped: subcommand failed.

解法:編譯時提供 jack 工具更多的記憶體空間。使用以下指令:

export JACK_SERVER_VM_ARGUMENTS="-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4g"
jack-admin kill-server && jack-admin start-server

這會增加它的可用空間,同時重新 jack server 讓設定生效。

結論

這文章紀錄我自己編譯及設定時所使用的步驟,參考前人的資料彙整而來。要感謝 KonstaT@github.com 將 LineageOS porting 到數莓派 3 的專案,他的網頁提供了這篇文章大部分的基本操作步驟。

成功編譯 Android 系統之後,未來我們可以更加深入了解 Android 專案的編譯與架構,同時繼續在數莓派上面安裝並加入更多的 APP 與硬體功能,打造符合自己需求的產品。

參考資料

--

--

陳培郁 (Raymond)
豬窩部落格

軟體工程師,喜歡關注台灣與美國的科技新聞、分享自己的見解與技術交流。 技能包含 Android 開發、Linux 網路、資訊安全、影音串流。