組件獨立升級全解析)
1. 為什么會有MainlineAndroid碎片化這口鍋搞Android系統(tǒng)定制或整機方案時間長了基本都躲不開一個問題怎么讓系統(tǒng)更新更快、更安全、還不用每次都逼用戶刷全量包。以前我們發(fā)一個安全補丁從AOSP拉下來、合代碼、過測試、調(diào)CTS、發(fā)OTA一整套流程走完三個月能落地已經(jīng)算順利。用戶手里的機器八成還在跑著上一個版本甚至有人還在用老掉牙的內(nèi)核和系統(tǒng)組件。Mainline項目就是沖著這個痛點去的。簡單說它把Android系統(tǒng)里的一部分關(guān)鍵組件拆出來做成可以獨立更新的模塊繞過整機OTA直接通過系統(tǒng)更新通道推到用戶設(shè)備上。這些模塊以APEX或者APK-in-APEX的形式存在底層原理、打包方式、簽名校驗、分區(qū)掛載全都有講究。我第一次拿到APEX包拆開看的時候第一反應(yīng)是這不就是一個帶文件映射表的簽名壓縮包嗎后來才發(fā)現(xiàn)它對系統(tǒng)分區(qū)的掛載策略、回滾機制、安全校驗鏈路都產(chǎn)生了連鎖影響。如果你接觸過Android系統(tǒng)裁剪、ROM定制、設(shè)備認(rèn)證或者只是好奇為什么某些系統(tǒng)組件不升級整體版本也能更新這篇文章值得看完。我會把Mainline設(shè)計思路、APEX打包原理、分區(qū)演進、分發(fā)鏈路、廠商適配注意事項以及Debug經(jīng)驗一次說清盡量用我們做項目踩坑時總結(jié)出來的話講。2. Mainline要解決的問題本質(zhì)2.1 系統(tǒng)組件更新的兩頭堵先說說沒有Mainline之前日子怎么過。Android的系統(tǒng)組件大多放在system分區(qū)system分區(qū)在設(shè)備的出廠階段就被寫入之后除了整包OTA幾乎沒有其他合法手段單獨改它。廠商想要推送一個新的媒體解碼庫、網(wǎng)絡(luò)協(xié)議?;蜻\行時更新唯一的正規(guī)路徑就是發(fā)OTA而OTA從生成到灰度到強制推送涉及運營商測試、軟件兼容性、海外版本規(guī)范等一系列環(huán)節(jié)周期極長。我問過不少做方案集成的人你們項目里最怕什么答案出奇一致——怕客戶問“能不能只更新某一個系統(tǒng)組件”。過去真沒法做到因為system分區(qū)的完整性和簽名的統(tǒng)一性是綁定死的。你單獨換掉其中某個庫整個分區(qū)的簽名校驗就掛了設(shè)備直接進recovery。Mainline的思路是把這些組件從system分區(qū)中拆出來單獨成為“模塊”。模塊所在的邏輯分區(qū)獨立于system它的更新不需要改動system分區(qū)的任何字節(jié)也不需要重啟進入OTA流程。相當(dāng)于把之前一個封閉的系統(tǒng)箱拆成了一排可以獨立開關(guān)的抽屜每個抽屜都有自己獨立的鎖和合頁。2.2 分發(fā)方式的根本變化Mainline的另一個變化在于分發(fā)通道。傳統(tǒng)OTA必須由設(shè)備廠商發(fā)起從服務(wù)器下發(fā)整包增量包應(yīng)用層配合恢復(fù)系統(tǒng)進行安裝。Mainline模塊則交由系統(tǒng)更新服務(wù)統(tǒng)一管理服務(wù)器可直接推送模塊更新不再強制依賴廠商發(fā)布整包OTA。這樣做的好處是即便設(shè)備廠商已經(jīng)停止整機維護那些關(guān)鍵模塊仍能持續(xù)獲得修復(fù)在一定程度上延長了設(shè)備的安全生命周期。實際項目中我們看到的結(jié)果是一臺兩年前上市的設(shè)備廠商已經(jīng)不再發(fā)大版本OTA但系統(tǒng)自帶的媒體組件、網(wǎng)絡(luò)組件、權(quán)限組件每隔幾個月還會更新。用戶感知不到具體哪個模塊變了但能感覺到部分安全性問題被修補了兼容性問題變少了。這事的本質(zhì)是Google在推進一種“服務(wù)化”的系統(tǒng)更新理念——系統(tǒng)不再只是一個發(fā)行版本更多像一套可持續(xù)升級的組件集合。Mainline不是某個功能的更新而是整個更新策略演進的起點。3. APEX與APK-in-APEX模塊拆分的基石3.1 APEX到底是什么APEX格式第一眼看上去像ZIP實際上也確實兼容ZIP結(jié)構(gòu)它內(nèi)部有原生庫、可執(zhí)行文件、配置文件、甚至JAR包外層再用簽名保護。但和普通ZIP最大的區(qū)別是APEX設(shè)計出來就是要被內(nèi)核掛載為只讀文件系統(tǒng)的掛載后內(nèi)部文件會直接映射到特定的系統(tǒng)路徑比如/apex/com.android.media。Android系統(tǒng)啟動過程中有一個專門的apexd守護進程負(fù)責(zé)處理這些包。它的工作流程大致是從/system/apex或/data/apex/session目錄掃描合法的APEX文件校驗簽名和AVB完整性然后解析包內(nèi)的manifest配置確定這個包應(yīng)該掛載到哪個路徑、是否需要激活、激活后哪些SELinux策略要跟著變化。我之前第一次嘗試手寫APEX包的manifest時犯過一個錯把provideNativeLibs寫漏了一個平臺庫名結(jié)果該模塊內(nèi)的組件跑到一半就崩了logcat里全是一堆linker cannot find。后來才明白APEX的manifest不只是簡單描述信息它決定了掛載后的APEX namespace可見范圍寫漏一個庫意味著這個作用域里的進程全部找不到對應(yīng)依賴。3.2 APK-in-APEX的存在原因APK-in-APEX聽起來繞口但拆開理解很簡單APEX本身不是給Java層直接跑的Java層的APK需要單獨的格式。如果一個模塊既要提供底層native能力又要提供上層應(yīng)用或服務(wù)最簡單的方式就是把APK也封裝進APEX里。Android在代碼里區(qū)分了兩種情況APEX里直接放APK文件和普通APK安裝路徑完全不同它不經(jīng)過PackageManager的安裝流程。APK Update則是一種特殊形態(tài)允許更新一個已預(yù)置在system分區(qū)中的APK而無需整個APEX替換。這個機制對降低模塊體積很重要。不同廠商在其方案中會預(yù)置多個APEX包采用APK-in-APEX方式的好處是可以只更新上層APK那部分底層庫保持不變數(shù)據(jù)量明顯小很多這對蜂窩網(wǎng)絡(luò)下更新尤其友好。3.3 SDK擴展與API穩(wěn)定性Mainline模塊更新對上層應(yīng)用最大的威脅是模塊版本在變API會不會跟著變了Android用了一個叫SDK擴展的機制來解決這個問題模塊代碼里使用Build.VERSION.SDK_INT之外多了一個SDK_INT加擴展版本號的概念。舉個例子某個權(quán)限相關(guān)的模塊更新后系統(tǒng)暴露給應(yīng)用的API行為發(fā)生變化但應(yīng)用本身是通過Build.VERSION_CODES里的擴展版本號來判斷能不能用新API的因此即使底層模塊已經(jīng)更新舊應(yīng)用也不會突然崩潰。模塊更新由服務(wù)器控制但API的變更節(jié)奏受擴展版本約束兩者配合才能做到平穩(wěn)演進。這條設(shè)計我認(rèn)為是整個Mainline框架里最值得稱道的部分它沒有讓模塊更新變成天氣預(yù)報——誰知哪片云會下雨。所有行為變更都有版本號兜底應(yīng)用層可以像以前判斷系統(tǒng)版本一樣判斷模塊能力。4. 啟動鏈路與分區(qū)演進4.1 從單一system分區(qū)到可疊加分區(qū)Mainline模塊要生效系統(tǒng)光有“更新了模塊”這個邏輯還不夠必須在啟動路由層面就給模塊留位置。假設(shè)模塊都更新到了/userdata或者某個獨立分區(qū)系統(tǒng)啟動時如果不掛載它們等于白更新。Android的啟動流程從Bootloader到Init再到apexd每一層都需要知道哪些APEX是合法的、在哪里、以什么順序掛載。AOSP里引入了一種叫做“APEX激活”的機制安裝在用戶數(shù)據(jù)區(qū)的模塊包在每次開機時會被檢查并掛載如果校驗失敗則回退到出廠內(nèi)置的系統(tǒng)版本。這意味著系統(tǒng)分區(qū)的頂層多了一層邏輯疊加——出廠的基礎(chǔ)分區(qū)只負(fù)責(zé)提供“初始可用環(huán)境”每次開機后實際生效的系統(tǒng)視圖是“出廠版本已激活模塊增量版本”的疊加。這種設(shè)計對OTA的影響也很大因為模塊可能單獨更新也可能綁定某次OTA一起更新廠商必須設(shè)置好用戶數(shù)據(jù)分區(qū)的大小和預(yù)留空間否則用戶數(shù)據(jù)壓力大的設(shè)備上模塊更新很容易被空間不足卡住。4.2 根分區(qū)驗證與可更新分區(qū)的協(xié)調(diào)Android對系統(tǒng)完整性的保護一直沒有放松AVBAndroid Verified Boot和dm-verity依然是分區(qū)的安全基石。Mainline引入了一個問題如果模塊分區(qū)可以動態(tài)改變那原本根植于system分區(qū)完整性的信任模型還能不能成立答案是可以的但約束變細(xì)了。AVB的驗證鏈條并沒有被打破只是多了一條驗證方在啟動階段除了驗證system分區(qū)還需要驗證激活的模塊分區(qū)。模塊分區(qū)有自己獨立的簽名鏈可以跟系統(tǒng)根密鑰內(nèi)部分離但必須在出廠時預(yù)置可信的公鑰。這個邏輯項目中經(jīng)常被誤解。有同事曾問我如果廠商想替換掉某個Mainline模塊的簽名密鑰是不是只需要在發(fā)布新包時用新簽名就行答案是否定的。密鑰更新需要單獨的升級鏈在老設(shè)備上工廠預(yù)置的信任公鑰不變別的新簽名包根本過不了校驗。換簽名這種事只能在出廠的信任根里整體加白名單無法在用戶設(shè)備上自行追加。4.3 動態(tài)分區(qū)與Virtual A/B的配合Mainline模塊的獨立更新如果還走傳統(tǒng)雙分區(qū)切換A/B分區(qū)的老路成本太高。傳統(tǒng)A/B方案意味著設(shè)備里必須留兩套完整系統(tǒng)分區(qū)中低端存儲就吃緊了。Virtual A/B就是為這個場景設(shè)計的設(shè)備上只有一份完整屬性和一份表示“更新后數(shù)據(jù)差異”的快照重啟切換時只更改查看視角而不是真正寫一套系統(tǒng)。和Mainline結(jié)合后模塊更新和系統(tǒng)OTA可以各自獨立觸發(fā)回滾也能分別處理。有一次我們模擬場景系統(tǒng)OTA包因為內(nèi)核模塊兼容問題失敗但此時某個Mainline模塊已經(jīng)提前更新到新版本了正常情況下可能造成新模塊與舊系統(tǒng)不兼容。得益于快照機制模塊和系統(tǒng)的狀態(tài)能夠分別回滾不至于出現(xiàn)一個進得去、一個退不出的尷尬局面。5. 模塊分發(fā)鏈路與設(shè)備認(rèn)證5.1 誰在管理模塊的更新Mainline模塊的更新通道和系統(tǒng)OTA是有區(qū)別的。在主流方案的實現(xiàn)中系統(tǒng)更新服務(wù)充當(dāng)了一個角色你可以把它理解成一個“模塊更新調(diào)度器”——它定期檢查服務(wù)器端的模塊包是否有新版本如果有且當(dāng)前設(shè)備條件滿足網(wǎng)絡(luò)、電量、存儲空間就會自動下載并安裝。這一步在很多開發(fā)者眼里是個黑盒服務(wù)器到底推送了什么什么時候推送廠商能不能控制實際上廠商是有配置空間的。AOSP里有個叫apexd的服務(wù)負(fù)責(zé)激活而具體的更新數(shù)據(jù)管理以及授權(quán)策略是落在整個系統(tǒng)更新框架里的。廠商可以做策略定制比如只在WiFi下自動更新或者將所有模塊更新收斂到整機OTA一起執(zhí)行。我們實測過兩種模式模塊獨立更新對用戶打擾極小后臺靜默完成幾乎無感但如果廠商把所有模塊更新都綁定到整機OTA那Mainline的意義就被削弱了因為又一次把更新時間拖到了整包OTA的節(jié)奏上。建議在項目和方案里盡量保留模塊獨立更新的靈活性。5.2 設(shè)備認(rèn)證與模塊兼容性Mainline更新雖然更靈活但也帶來了新的兼容性壓力。模塊更新之后如果和某個廠商深度定制的底層驅(qū)動不兼容用戶會遇到詭異問題——WiFi打不開、媒體播放異常、或指紋識別失靈。原因是Mainline模塊的版本更新通常在AOSP主線上驗證過但安卓生態(tài)里大量芯片廠商和集成廠商會改動到底層驅(qū)動或者擴展接口。模塊更新時如果覆蓋到的代碼路徑恰好和廠商改動耦合就會出現(xiàn)只在這個設(shè)備上復(fù)現(xiàn)的問題。我建議認(rèn)證流程中建立一個模塊兼容性子用例重點覆蓋網(wǎng)絡(luò)、媒體和運行時三大塊。設(shè)備認(rèn)證不只包含整機OTA升級路徑也包括關(guān)鍵模塊單獨更新后的穩(wěn)定性驗證。項目實測完成后能明顯減少線上反饋的模塊更新兼容性Bug這是成本低收益高的投入。6. 廠商適配中的真問題與排查辦法6.1 分區(qū)大小與構(gòu)建配置廠商適配Mainline的第一步往往是修改分區(qū)表。因為模塊分區(qū)不是憑空出現(xiàn)的它需要從system或vendor空間里切出一塊或者直接新建一塊獨立分區(qū)。不同方案會選擇不同策略我們見過兩種一是復(fù)用系統(tǒng)自帶的apex目錄所在分區(qū)把增量模塊安裝在數(shù)據(jù)區(qū)緩存中二是單獨劃分一個更小的邏輯分區(qū)專門放已激活的模塊包。方案沒有絕對優(yōu)劣但要求廠商必須計算清楚出貨策略里要內(nèi)置多少套基礎(chǔ)模塊余量要撐得過未來兩年內(nèi)的模塊版本膨脹。我們早期有個項目只預(yù)留了300MB給模塊目錄結(jié)果第8個月就開始頻繁出現(xiàn)空間不足的更新失敗后來不得不把數(shù)據(jù)區(qū)再劃一部分出來做補償?shù)扔谕品貋砹艘淮伟l(fā)布規(guī)劃。6.2 SELinux策略與模塊邊界Mainline模塊激活后它帶來的不只是文件路徑變化還有自己的SELinux標(biāo)簽和策略。AOSP中每個模塊對應(yīng)的策略文件都是獨立維護的模塊更新時如果引入新的服務(wù)或訪問新的文件節(jié)點策略不跟著適配就會出現(xiàn)permission denied。行業(yè)內(nèi)跟進這個問題的標(biāo)準(zhǔn)做法是在集成時打開SELinux的permissive日志觀察啟動階段和模塊激活階段的avc denial信息但要清楚這只是調(diào)試手段正式版本中應(yīng)確保targeted模式無新拒絕日志。常見的陷阱是模塊更新后新增了一個property或sysfs節(jié)點系統(tǒng)的property_contexts或file_contexts卻沒有補齊。6.3 OTA與模塊更新并發(fā)整機OTA和Mainline模塊更新同時觸發(fā)是我們項目里最需要關(guān)注的異常場景。因為系統(tǒng)可能正在升級system分區(qū)而模塊同時也在重掛載/apex兩者有極小概率發(fā)生I/O或動態(tài)鏈接競爭。從Android系統(tǒng)的調(diào)度設(shè)計來看OTA本身會有staging狀態(tài)而模塊更新由另一個服務(wù)在管理說兩者完全沒有沖突是不可能的。要規(guī)避必須從廠商自定義的升級策略層面控制整機OTA觸發(fā)時先暫停模塊更新等OTA完成重啟后再恢復(fù)。這個策略之前沒有考慮后來遇到一次升級失敗的設(shè)備取證日志顯示兩個更新進程進行了文件讀寫競爭當(dāng)時就意識到必須做全局互斥。6.4 回滾與設(shè)備健康檢查模塊更新失敗后的回滾路徑也是項目上線前容易被忽略的。傳統(tǒng)OTA失敗大多可以靠A/B分區(qū)兜底但模塊更新如果寫壞了回退邏輯是依賴apexd在開機時發(fā)現(xiàn)激活校驗失敗自動使用出廠版本。有一個要點出廠版本是否存在于system分區(qū)的內(nèi)置APEX目錄中這決定了回滾是否可靠。部分廠商為了省空間在定制時刪掉了一些系統(tǒng)內(nèi)置APEX的出廠副本只保留用戶數(shù)據(jù)區(qū)的“最新版本”一旦用戶數(shù)據(jù)區(qū)版本損壞或校驗失敗設(shè)備就會陷入模塊缺失的狀態(tài)。這個坑我們在售后中碰到過處理成本非常高。結(jié)論很明確出廠的“兜底版本”不能省那點空間省不得。7. 實測多年后的運維經(jīng)驗做了幾年Mainline項目實施最深的體會是這個框架不是把系統(tǒng)更新變簡單而是把問題拆細(xì)了。傳統(tǒng)OTA是一整鍋粥糊了就是鍋糊了Mainline像是一排抽屜任何一個抽屜卡住只會影響局部但排查時需要你知道哪個抽屜在哪、誰負(fù)責(zé)打開它、以及鑰匙在誰手里。分享三個實戰(zhàn)經(jīng)驗第一日志分析要看apexd和update_engine的日志如果模塊更新卡住大概率是空間不足或者DM設(shè)備映射問題這兩個進程的日志比logcat更直接。第二測試適配時一定要做“出廠版本損壞回滾”的壓力測試。方法很簡單模擬強制修改激活區(qū)數(shù)據(jù)后用重啟腳本觀察是否自動回退。很多廠商設(shè)備在普通OTA上過了測試但這道回滾測試沒做好真實設(shè)備才會原形畢露。第三模塊更新必須納入整機版本管理。不要以為模塊是服務(wù)器分發(fā)就不用像固件一樣管理版本號現(xiàn)場調(diào)試時我們遇到過模塊版本和系統(tǒng)框架版本不匹配導(dǎo)致上層服務(wù)反復(fù)崩潰的情況。建議項目里建立一個模塊與系統(tǒng)版本匹配表的自動化檢查每次更新前先核對條目。Mainline帶來的更新策略演進本質(zhì)上讓Android從一個“按版本換代”的系統(tǒng)逐步走向了“按模塊演進”的架構(gòu)。這種變化對用戶是透明的但對做系統(tǒng)的人來說意味著掌握的知識面要更寬了——從分區(qū)布局到簽名鏈從SELinux策略到啟動順序每一個環(huán)節(jié)都可能因為一個小小的APEX包而牽動全局。我在實際項目里吃過虧之后的最大感受是千萬不要在設(shè)備出貨前才去摸這套機制那會兒你大概率已經(jīng)被幾個更新失敗反饋和一個“系統(tǒng)空間不足”的紅燈籠搞到加班到凌晨。真正穩(wěn)妥的辦法是在立項和定制階段就把模塊分區(qū)、簽名策略、空間預(yù)留、版本匹配這些前置條件定清楚后面才不會天天幫線上擦屁股。這個方向后續(xù)還有不少可以繼續(xù)挖的空間感興趣的可以去研究一下動態(tài)系統(tǒng)更新和可組合系統(tǒng)鏡像對分區(qū)規(guī)劃帶來的進一步變化。有一說一在Android這條路上折騰久了你會發(fā)現(xiàn)系統(tǒng)軟件工程最迷人的地方就在于——每一次你以為已經(jīng)了解全部新的版本就會冒出來打臉一次。