:從環(huán)境準(zhǔn)備到多實例部署)
1. 為什么要在本地編譯 Redroid 鏡像Redroid 這個項目全稱是 Remote Android本質(zhì)上是一套跑在 Linux 容器里的 Android 系統(tǒng)。它跟傳統(tǒng)模擬器最大的區(qū)別在于沒有硬件虛擬化那一層直接復(fù)用宿主機(jī)的內(nèi)核靠容器技術(shù)把 Android 的用戶空間跑起來。帶來的好處很直接——啟動快、資源占用低、一臺物理機(jī)可以同時跑幾十甚至上百個實例。做云手機(jī)、App 自動化測試、群控、云游戲這些場景的人基本都會接觸到它。但真正上手之后你會發(fā)現(xiàn)官方提供的鏡像往往不夠用。原因有幾個一是官方鏡像為了通用性預(yù)裝的組件非?;A(chǔ)很多業(yè)務(wù)需要的服務(wù)、權(quán)限、系統(tǒng)屬性都沒有二是不同宿主機(jī)的內(nèi)核版本、顯卡、網(wǎng)絡(luò)環(huán)境差異很大官方鏡像不一定能直接跑起來三是做產(chǎn)品的時候你總得往系統(tǒng)里塞自己的 App、改默認(rèn)配置、調(diào)性能參數(shù)這些都需要重新打包鏡像。所以“編譯與定制”這件事不是可選項而是必經(jīng)之路。這篇文章我會把整個流程拆開講清楚從環(huán)境準(zhǔn)備、源碼拉取、編譯參數(shù)、鏡像結(jié)構(gòu)到定制化修改、常見報錯排查全部按我實際操作的順序來。適合已經(jīng)了解 Docker 基本用法、想在云手機(jī)方向做深度定制的讀者。如果你只是想讓 Redroid 跑起來看看效果官方鏡像就夠了但如果你想把它變成能交付的產(chǎn)品那這篇內(nèi)容應(yīng)該能幫你少走不少彎路。2. 編譯前的環(huán)境準(zhǔn)備與依賴梳理2.1 宿主機(jī)系統(tǒng)的選擇與內(nèi)核要求Redroid 對宿主機(jī)最核心的要求是內(nèi)核。它依賴幾個關(guān)鍵的內(nèi)核特性binder、ashmem或者 memfd、以及 cgroup 的相關(guān)支持。binder 是 Android 進(jìn)程間通信的基礎(chǔ)ashmem 是 Android 的共享內(nèi)存機(jī)制。這兩個東西在標(biāo)準(zhǔn)發(fā)行版內(nèi)核里默認(rèn)是不開啟的所以你得自己確認(rèn)或者重新編譯內(nèi)核。我實測下來Ubuntu 20.04 和 22.04 是比較省心的選擇社區(qū)文檔也最全。內(nèi)核版本建議 5.4 以上5.10 和 5.15 這兩個 LTS 版本兼容性最好。你可以用下面這組命令快速檢查當(dāng)前內(nèi)核是否滿足條件# 檢查 binder 模塊 ls /dev/binder* 2/dev/null # 檢查 ashmem ls /dev/ashmem 2/dev/null # 檢查內(nèi)核配置 zcat /proc/config.gz | grep -E CONFIG_ANDROID_BINDER|CONFIG_ASHMEM|CONFIG_MEMFD如果/dev/binder不存在說明 binder 沒加載。Ubuntu 上可以嘗試modprobe binder_linux但很多發(fā)行版內(nèi)核根本沒編這個模塊那就只能換內(nèi)核或者用帶 binder 的內(nèi)核包。這一步是很多人卡住的第一個坑別急著往下走先把內(nèi)核搞定。提示如果你用的是云服務(wù)器很多廠商的默認(rèn)內(nèi)核是裁剪過的binder 和 ashmem 都可能缺失。下單前最好先確認(rèn)或者選支持自定義內(nèi)核的機(jī)型。2.2 編譯工具鏈與依賴包安裝Redroid 的鏡像編譯本質(zhì)上是在構(gòu)建一個 Android 系統(tǒng)的 rootfs所以工具鏈比較重。核心依賴包括Docker用于構(gòu)建和運(yùn)行、Git、Python3、以及一堆構(gòu)建工具。我一般會一次性把依賴裝齊sudo apt update sudo apt install -y git curl python3 python3-pip \ build-essential zip unzip tar gzip \ libssl-dev libncurses-dev flex bison \ android-sdk-libsparse-utils這里重點(diǎn)說兩個容易被忽略的包。一個是android-sdk-libsparse-utils它提供了img2simg和simg2img工具Redroid 的鏡像最終是 sparse 格式的沒有這個工具你沒法處理鏡像文件。另一個是libssl-dev編譯過程中有些組件需要鏈接 OpenSSL缺了會報鏈接錯誤。Docker 的版本建議 20.10 以上因為 Redroid 容器需要用到一些較新的 namespace 和 cgroup 特性。裝完之后記得把當(dāng)前用戶加入 docker 組不然每次都要 sudosudo usermod -aG docker $USER newgrp docker2.3 磁盤空間與內(nèi)存的規(guī)劃這一點(diǎn)我必須單獨(dú)拎出來講因為它是隱形成本。Redroid 的源碼加上編譯中間產(chǎn)物輕松吃掉 50GB 以上。如果你還要保留多個版本的鏡像100GB 是起步。內(nèi)存方面編譯過程建議至少 8GB16GB 比較舒服。如果內(nèi)存不夠編譯到一半被 OOM Killer 干掉是常有的事。我一般會單獨(dú)掛一塊盤給編譯目錄用df -h確認(rèn)空間然后設(shè)置好工作目錄。另外編譯過程會產(chǎn)生大量小文件文件系統(tǒng)的 inode 數(shù)量也要留意ext4 默認(rèn)的 inode 數(shù)量一般夠用但如果你用的是某些精簡文件系統(tǒng)可能會遇到 inode 耗盡的問題。3. Redroid 鏡像的編譯流程拆解3.1 源碼獲取與目錄結(jié)構(gòu)說明Redroid 的源碼托管在 GitLab 上主倉庫是redroid-doc里面包含了編譯腳本和文檔。但真正編譯鏡像用的是redroid-script這個倉庫它封裝了一套基于 Docker 的構(gòu)建流程。我通常這樣拉取git clone https://github.com/remote-android/redroid-doc.git cd redroid-doc目錄結(jié)構(gòu)大致是這樣的android-builder目錄下是構(gòu)建腳本redroid目錄下是運(yùn)行時的配置。核心的構(gòu)建腳本是build.sh它會調(diào)用 Docker 去拉取一個構(gòu)建鏡像然后在里面執(zhí)行 Android 的編譯流程。這里有個設(shè)計上的取舍值得說Redroid 沒有讓你從 AOSP 源碼從頭編譯而是基于已有的 Android 系統(tǒng)鏡像做二次加工。這樣做的好處是編譯時間從幾小時縮短到幾十分鐘壞處是你能改的東西受限于基礎(chǔ)鏡像。對于大多數(shù)定制需求來說這個取舍是合理的因為真正需要改內(nèi)核或者 framework 層的場景并不多。3.2 編譯參數(shù)的選擇與含義構(gòu)建腳本支持一系列參數(shù)最關(guān)鍵的幾個是ANDROID_VERSION、ARCH、TARGET。ANDROID_VERSION決定了基礎(chǔ)鏡像的 Android 版本常見的有 11、12、13。版本越高對內(nèi)核的要求也越高比如 Android 13 需要內(nèi)核支持更多的 binder 特性。ARCH一般是arm64或者x86_64。這里有個實際經(jīng)驗如果你在 x86 服務(wù)器上跑選x86_64性能最好因為不需要指令翻譯但如果你要跑 ARM 架構(gòu)的 App就得選arm64這時候宿主機(jī)如果是 x86就需要 QEMU 做指令翻譯性能會打折扣。我實測下來x86_64 鏡像跑 ARM App 的兼容性一般能跑但別指望流暢。TARGET參數(shù)用來指定輸出鏡像的用途比如userdebug會帶 root 和調(diào)試能力user則是生產(chǎn)版本。做定制開發(fā)階段我建議用userdebug方便進(jìn)去改東西。一個典型的編譯命令長這樣./build.sh \ --android-version 13 \ --arch x86_64 \ --target userdebug \ --output ./output3.3 編譯過程的監(jiān)控與中斷處理編譯一旦開始會經(jīng)歷幾個階段拉取基礎(chǔ)鏡像、解包、修改、重新打包。整個過程視網(wǎng)絡(luò)和機(jī)器性能大概 20 到 60 分鐘。我一般會開著docker stats看資源占用同時用tail -f跟構(gòu)建日志。如果中途失敗了別急著重跑。先看日志最后幾十行通常是某個依賴下載失敗或者磁盤滿了。構(gòu)建腳本會在工作目錄留下中間產(chǎn)物你可以進(jìn)去手動修復(fù)后繼續(xù)而不是從頭再來。這一點(diǎn)在反復(fù)調(diào)試定制內(nèi)容的時候特別有用。注意編譯過程中不要手動去動工作目錄里的文件腳本有狀態(tài)標(biāo)記亂動會導(dǎo)致下次構(gòu)建行為異常。要改東西等構(gòu)建結(jié)束或者明確知道自己在干什么。4. 鏡像定制化的核心手段4.1 往鏡像里預(yù)裝 App 和系統(tǒng)服務(wù)最基礎(chǔ)的定制就是預(yù)裝 App。Redroid 的構(gòu)建腳本支持一個--preinstall參數(shù)指向一個目錄里面放 APK 文件構(gòu)建時會自動把它們?nèi)M(jìn)/system/priv-app或者/data/app。但這里有個細(xì)節(jié)放到priv-app的 App 需要簽名匹配否則系統(tǒng)不認(rèn)。我一般會把 App 放到/data/app下用腳本在首次啟動時安裝。具體做法是在構(gòu)建腳本的定制階段往system/etc/init下加一個 rc 文件讓系統(tǒng)啟動時執(zhí)行一段腳本去安裝 APK。這個思路比直接塞進(jìn)系統(tǒng)分區(qū)靈活因為系統(tǒng)分區(qū)是只讀的塞進(jìn)去的 App 升級麻煩。# 示例在 init.rc 中添加啟動服務(wù) service install_apps /system/bin/sh /system/etc/install_apps.sh class main oneshot對應(yīng)的install_apps.sh里用pm install逐個安裝。注意這個腳本要在zygote啟動之后執(zhí)行否則pm命令不可用。4.2 修改系統(tǒng)屬性和默認(rèn)配置系統(tǒng)屬性是定制里最常改的東西。比如你要改默認(rèn)語言、時區(qū)、屏幕密度、是否允許 root都是通過build.prop或者system.prop來控制的。Redroid 的構(gòu)建腳本允許你提供一個prop文件構(gòu)建時合并進(jìn)去。我整理了一份常用屬性對照表方便你按需修改屬性名作用常用值ro.product.locale默認(rèn)語言地區(qū)zh-CNpersist.sys.timezone默認(rèn)時區(qū)Asia/Shanghairo.sf.lcd_density屏幕密度160 / 240 / 320ro.debuggable是否可調(diào)試1ro.secure是否安全模式0persist.sys.usb.configUSB 配置mtp,adb改這些屬性的時候要注意有些屬性是只讀的改了不生效得在構(gòu)建階段就寫進(jìn)去。運(yùn)行階段用setprop只能改非 ro 開頭的屬性。4.3 調(diào)整內(nèi)核參數(shù)與容器運(yùn)行時配置鏡像本身定制完之后運(yùn)行時的配置同樣重要。Redroid 容器啟動時需要掛載 binder 和 ashmem 設(shè)備還要設(shè)置一些 cgroup 參數(shù)。這些在docker run命令里體現(xiàn)docker run -itd --privileged \ --name redroid \ -p 5555:5555 \ -v /dev/binder:/dev/binder \ -v /dev/ashmem:/dev/ashmem \ redroid/redroid:13.0.0_amd64 \ androidboot.redroid_width1080 \ androidboot.redroid_height1920 \ androidboot.redroid_dpi320--privileged是必須的因為容器需要訪問宿主機(jī)的設(shè)備節(jié)點(diǎn)。androidboot.redroid_*這些參數(shù)是 Redroid 特有的用來控制虛擬屏幕的分辨率和密度。如果你要跑多個實例每個實例的端口和這些參數(shù)都要不一樣。提示生產(chǎn)環(huán)境不建議用--privileged權(quán)限太大??梢杂?-device單獨(dú)掛載需要的設(shè)備節(jié)點(diǎn)配合--cap-add精細(xì)化控制權(quán)限。5. 實操過程中踩過的坑與排查方法5.1 容器啟動失敗binder 設(shè)備找不到這是最高頻的問題?,F(xiàn)象是容器啟動后立刻退出日志里報Failed to open /dev/binder。原因就是宿主機(jī)沒有 binder 設(shè)備節(jié)點(diǎn)。解決辦法分兩種情況如果內(nèi)核支持 binder 但沒加載modprobe binder_linux即可如果內(nèi)核根本不支持那就得換內(nèi)核。我遇到過一種隱蔽情況內(nèi)核支持 binder但設(shè)備節(jié)點(diǎn)名字不是/dev/binder而是/dev/binderfs/binder。這時候要么做個軟鏈接要么在 docker run 時掛載正確的路徑。排查的時候用find /dev -name *binder*先確認(rèn)節(jié)點(diǎn)位置。5.2 編譯報錯磁盤空間不足與 inode 耗盡編譯到一半報No space left on device但df -h看空間還有剩這時候大概率是 inode 用完了。用df -i確認(rèn)。解決辦法是清理編譯中間產(chǎn)物或者換一個 inode 更多的文件系統(tǒng)。我一般會在編譯前把工作目錄清空避免舊產(chǎn)物占著 inode。另一個空間陷阱是 Docker 的鏡像層。每次編譯都會產(chǎn)生新的鏡像層時間長了能占幾十 GB。定期docker system prune清理一下但注意別把正在用的鏡像刪了。5.3 網(wǎng)絡(luò)與 ADB 連接問題Redroid 默認(rèn)監(jiān)聽 5555 端口用adb connect localhost:5555就能連上。但有時候連不上常見原因有三個一是端口沒映射對檢查docker ps的端口映射二是容器內(nèi)的 adbd 沒啟動進(jìn)容器ps -ef | grep adbd看看三是防火墻攔了這個在云服務(wù)器上很常見安全組要放行對應(yīng)端口。如果 adb 連上了但設(shè)備顯示 offline通常是 adb 版本和容器內(nèi) adbd 版本不匹配。升級本地的 adb 到最新版一般能解決。5.4 常見問題速查表現(xiàn)象可能原因排查方向容器啟動即退出binder/ashmem 缺失檢查 /dev 下設(shè)備節(jié)點(diǎn)編譯中斷報空間不足磁盤或 inode 耗盡df -h 和 df -iadb 連不上端口/防火墻/adbd逐層檢查網(wǎng)絡(luò)和進(jìn)程App 安裝失敗簽名或權(quán)限問題檢查 priv-app 簽名畫面黑屏分辨率參數(shù)錯誤核對 androidboot 參數(shù)性能卡頓架構(gòu)不匹配確認(rèn)鏡像架構(gòu)與宿主機(jī)一致6. 定制化進(jìn)階從能跑到好用6.1 多實例管理與資源隔離一臺機(jī)器跑多個 Redroid 實例是云手機(jī)的典型需求。每個實例要獨(dú)立的端口、獨(dú)立的數(shù)據(jù)目錄、獨(dú)立的屏幕參數(shù)。我一般用腳本批量生成 docker run 命令端口從 5555 開始遞增數(shù)據(jù)目錄按實例編號命名。資源隔離方面用--cpus和--memory限制每個實例的 CPU 和內(nèi)存。不限制的話一個實例跑滿會把其他實例拖死。我實測下來一個 1080p 的實例2 核 2GB 是比較舒服的配置跑輕量 App 夠用。6.2 鏡像瘦身與啟動加速官方鏡像動輒幾個 GB傳輸和啟動都慢。瘦身的手段有幾個刪掉不用的系統(tǒng) App、壓縮資源文件、去掉調(diào)試符號。但要注意刪系統(tǒng) App 可能影響依賴得一個個確認(rèn)。我一般會保留核心框架把能刪的預(yù)裝 App 都刪掉能省下幾百 MB。啟動加速方面可以把不必要開機(jī)啟動的服務(wù)禁掉減少 init 階段的工作量。另外用docker commit把定制好的容器保存成新鏡像下次直接啟動省去初始化時間。6.3 版本管理與回滾策略定制鏡像一定要做版本管理。我的做法是每次編譯出來的鏡像都打上日期和版本號標(biāo)簽比如redroid-custom:20250101-v1。同時保留一份構(gòu)建腳本和定制文件的快照這樣出問題能快速回滾。提示不要把定制內(nèi)容直接改在官方鏡像上而是維護(hù)一份定制腳本每次基于官方鏡像重新構(gòu)建。這樣官方更新時你能快速跟進(jìn)而不是被自己的修改綁死。7. 一些個人體會做 Redroid 定制這段時間最大的感受是難點(diǎn)不在編譯本身而在環(huán)境適配。同樣的腳本在不同機(jī)器上跑出來的結(jié)果可能完全不一樣內(nèi)核版本、Docker 版本、甚至文件系統(tǒng)類型都會影響結(jié)果。所以我的建議是先把環(huán)境摸清楚把內(nèi)核和依賴確認(rèn)到位再動手編譯。磨刀不誤砍柴工這句話在這個場景里特別貼切。另外定制的時候要克制。很多人一上來就想大改結(jié)果改出一堆問題。我的做法是每次只改一個點(diǎn)改完驗證通過了再改下一個。這樣出問題的時候排查范圍小定位快。最后再分享一個小技巧把常用的定制操作寫成腳本參數(shù)化這樣換一臺機(jī)器或者換一個 Android 版本改幾個參數(shù)就能復(fù)用效率提升非常明顯。