本頁描述了添加到 AOSP 中的更改,以減少建置之間不必要的文件更改。維護自己的建置系統的設備實施者可以使用此資訊作為減少無線 (OTA) 更新大小的指南。
Android OTA 更新有時會包含與程式碼變更不對應的變更檔。它們實際上是建構系統工件。當在不同時間、不同目錄或不同機器上建置的相同程式碼產生大量變更的檔案時,可能會發生這種情況。此類多餘的檔案會增加 OTA 補丁的大小,並導致難以確定哪些程式碼發生了變更。
為了使 OTA 的內容更加透明,AOSP 包括旨在減少 OTA 補丁大小的建造系統變更。版本之間不必要的檔案變更已被消除,OTA 更新中僅包含與修補程式相關的檔案。 AOSP 還包括一個構建差異工具(可過濾掉常見的構建相關文件更改,以提供更清晰的構建文件差異)和一個塊映射工具(可幫助您保持塊分配一致)。
建置系統可以透過多種方式創建不必要的大補丁。為了緩解這個問題,在 Android 8.0 及更高版本中,實作了新功能來減少每個檔案差異的修補程式大小。減少 OTA 更新包大小的改進包括:
- 使用Brotli ,這是一種通用的無損壓縮演算法,用於非 A/B 裝置更新上的完整影像。 Brotli可以客製化以優化壓縮。在檔案系統中由兩個或多個區塊組成的較大更新(例如,
system.img
)中,設備製造商或合作夥伴可以添加自己的壓縮演算法,並且可以對相同更新的不同區塊使用不同的壓縮演算法. - 使用Puffin重新壓縮,這是用於 deflate 流的確定性修補工具,可處理 A/B OTA 更新產生的壓縮和 diff 函數。
- 更改了增量生成工具的使用方式,例如如何使用
bsdiff
庫來壓縮補丁。在 Android 9 及更高版本中,bsdiff
工具會選擇可為補丁提供最佳壓縮結果的壓縮演算法。 -
update_engine
的改進導致應用程式補丁進行 A/B 裝置更新時消耗的記憶體更少。 - 改進了分割大型 zip 檔案以進行基於區塊的 OTA 更新。
imgdiff
中的模式根據條目名稱分割超大 APK 檔案。與線性分割檔案並使用bsdiff
工具壓縮檔案相比,這會產生更小的補丁。
以下部分討論影響 OTA 更新大小的各種問題、其解決方案以及 AOSP 中的實作範例。
文件順序
問題:當要求提供目錄中的文件清單時,檔案系統不保證文件順序,儘管對於相同的簽出通常是相同的。 ls
等工具預設會對結果進行排序,但find
和make
等指令使用的通配符函數不會排序。在使用這些工具之前,您必須對輸出進行排序。
解決方案:當您使用帶有通配符功能的find
和make
工具時,請在使用這些命令之前對它們的輸出進行排序。在Android.mk
檔案中使用$(wildcard)
或$(shell find)
時,也要對它們進行排序。有些工具(例如 Java)會對輸入進行排序,因此在對檔案進行排序之前,請先驗證您使用的工具是否尚未執行此操作。
範例:使用內建的all-*-files-under
宏在核心建置系統中修復了許多實例,其中包括all-cpp-files-under
(因為多個定義分散在其他 makefile 中)。詳細資訊請參考以下內容:
- https://android.googlesource.com/platform/build/+/4d66adfd0e6d599d8502007e4ea9aaf82e95569f
- https://android.googlesource.com/platform/build/+/379f9f9cec4fe1c66b6d60a6c19fecb81b9eb410
- https://android.googlesource.com/platform/build/+/7c3e3f8314eec2c053012dd97d2ae649ebeb5653
- https://android.googlesource.com/platform/build/+/5c64b4e81c1331cab56d8a8c201f26bb263b630c
建置目錄
問題:更改建置內容的目錄可能會導致二進位檔案不同。 Android 建置中的大多數路徑都是相對路徑,因此 C/C++ 中的__FILE__
不是問題。但是,偵錯符號預設對完整路徑名進行編碼,並且.note.gnu.build-id
是透過對預先剝離的二進位檔案進行哈希處理生成的,因此如果偵錯符號發生變化,它也會發生變化。
解決方案: AOSP 現在使調試路徑相對。有關詳細信息,請參閱 CL: https ://android.googlesource.com/platform/build/+/6a66a887baadc9eb3d0d60e26f748b8453e27a02 。
時間戳
問題:建置輸出中的時間戳記會導致不必要的檔案變更。這種情況很可能發生在以下位置:
- C 或 C++ 程式碼中的
__DATE__/__TIME__/__TIMESTAMP__
巨集。 - 時間戳嵌入基於 zip 的檔案中。
解決方案/範例:若要從建置輸出中刪除時間戳,請使用下面C/C++ 中的 __DATE__/__TIME__/__TIMESTAMP__ 中給出的說明。以及檔案中嵌入的時間戳記。
C/C++ 中的 __DATE__/__TIME__/__TIMESTAMP__
這些巨集總是為不同的構建產生不同的輸出,所以不要使用它們。以下是消除這些巨集的一些選項:
- 刪除它們。有關範例,請參閱https://android.googlesource.com/platform/system/core/+/30622bbb209db187f6851e4cf0cdaa147c2fca9f 。
- 若要唯一標識正在執行的二進位文件,請從 ELF 標頭中讀取 build-id。
- 要了解作業系統的建置時間,請閱讀
ro.build.date
(這適用於除增量建置之外的所有內容,增量建置可能不會更新此日期)。有關範例,請參閱https://android.googlesource.com/platform/external/libchrome/+/8b7977eccc94f6b3a3896cd13b4aeacbfa1e0f84 。
檔案中嵌入時間戳記(zip、jar)
Android 7.0 透過在zip
指令的所有使用中新增-X
解決了 zip 檔案中嵌入時間戳記的問題。這從 zip 檔案中刪除了建構器的 UID/GID 和擴充 Unix 時間戳記。
新工具ziptime
(位於/platform/build/+/main/tools/ziptime/
)會重設 zip 標頭中的正常時間戳記。有關詳細信息,請參閱README 文件。
signapk
工具為 APK 檔案設定時間戳,該時間戳可能因伺服器時區而異。有關詳細信息,請參閱 CL https://android.googlesource.com/platform/build/+/6c41036bcf35fe39162b50d27533f0f3bfab3028 。
版本字串
問題: APK 版本字串通常會在其硬編碼版本後附加BUILD_NUMBER
。即使 APK 中沒有其他任何變化,APK 仍然會有所不同。
解決方案:從 APK 版本字串中刪除內部版本號。
例子:
- https://android.googlesource.com/platform/packages/apps/Camera2/+/5e0f4cf699a4c7c95e2c38ae3babe6f20c258d27
- https://android.googlesource.com/platform/build/+/d75d893da8f97a5c7781142aaa7a16cf1dbb669c
啟用裝置上的驗證計算
如果您的裝置上啟用了dm-verity ,則 OTA 工具會自動取得您的驗證配置,並啟用裝置上的驗證計算。這允許在 Android 裝置上計算 verity 區塊,而不是作為原始位元組儲存在 OTA 套件中。對於 2GB 分割區,Verity 區塊可以使用大約 16MB。
然而,在設備上計算驗證可能需要很長時間。具體來說,前向糾錯碼可能需要很長時間。在 Pixel 設備上,通常需要長達 10 分鐘的時間。在低端設備上可能需要更長的時間。如果您想要停用裝置上的 verity 運算,但仍啟用 dm-verity,則可以透過在產生 OTA 更新時將--disable_fec_computation
傳遞給ota_from_target_files
工具來實現。該標誌會在 OTA 更新期間停用裝置上的驗證計算。它減少了 OTA 安裝時間,但增加了 OTA 套件大小。如果您的裝置未啟用 dm-verity,則傳遞此標誌無效。
一致的建構工具
問題:產生已安裝檔案的工具必須一致(給定的輸入應始終產生相同的輸出)。
解決方案/範例:以下建置工具需要更改:
- 注意文件創建者。 NOTICE 檔案建立器已變更為建立可複製的 NOTICE 集合。請參閱 CL:https: //android.googlesource.com/platform/build/+/8ae4984c2c8009e7a08e2a76b1762c2837ad4f64 。
- Java Android 編譯器套件 (Jack) 。 Jack 工具鏈需要更新以處理生成的建構函數順序中的偶爾變更。建構函數的確定性存取器已新增至工具鏈:https: //android.googlesource.com/toolchain/jack/+/056a5425b3ef57935206c19ecb198a89221ca64b 。
- ART AOT 編譯器 (dex2oat) 。 ART 編譯器二進位檔案收到了更新,新增了建立確定性影像的選項: https://android.googlesource.com/platform/art/+/ace0dc1dd5480ad458e622085e51583653853fb9 。
- libpac.so 檔案 (V8) 。每個建置都會建立不同的
/system/lib/libpac.so
文件,因為每個建置的 V8 快照都會改變。解決方案是刪除快照: https://android.googlesource.com/platform/external/v8/+/e537f38c36600fd0f3026adba6b3f4cbcee1fb29 。 - 應用程式預 dexopt (.odex) 檔案。 dexopt 之前的 (.odex) 檔案包含 64 位元系統上未初始化的填充。已修正:https: //android.googlesource.com/platform/art/+/34ed3afc41820c72a3c0ab9770be66b6668aa029 。
使用建構差異工具
對於無法消除與建置相關的檔案變更的情況,AOSP 包含一個建置差異工具target_files_diff.py
,用於比較兩個檔案包。該工具在兩個建置之間執行遞歸差異,排除常見的與建置相關的檔案更改,例如
- 建置輸出的預期變化(例如,由於建構號更改)。
- 由於目前建置系統中的已知問題而發生的變更。
若要使用建置 diff 工具,請執行以下命令:
target_files_diff.py dir1 dir2
dir1
和dir2
是包含每個建置提取的目標檔案的基底目錄。
保持區塊分配一致
對於給定文件,儘管其內容在兩次建置之間保持相同,但保存資料的實際區塊可能已更改。因此,更新程式必須執行不必要的 I/O 來移動區塊以進行 OTA 更新。
在虛擬 A/B OTA 更新中,不必要的 I/O 會大幅增加儲存寫入時複製快照所需的儲存空間。在非 A/B OTA 更新中,移動區塊進行 OTA 更新會延長更新時間,因為區塊移動會產生更多 I/O。
為了解決這個問題,在 Android 7.0 中,Google 擴充了make_ext4fs
工具,以保持跨建置的區塊分配一致。 make_ext4fs
工具接受可選的-d base_fs
標誌,該標誌在產生ext4
映像時嘗試將檔案指派到相同的區塊。您可以從先前建置的目標檔案的 zip 檔案中提取區塊映射檔案(例如base_fs
映射檔案)。對於每個ext4
分區, IMAGES
目錄下都有.map
檔(例如, IMAGES/system.map
對應system
分區)。然後可以透過PRODUCT_<partition>_BASE_FS_PATH
簽入並指定這些base_fs
文件,如本範例所示:
PRODUCT_SYSTEM_BASE_FS_PATH := path/to/base_fs_files/base_system.map PRODUCT_SYSTEM_EXT_BASE_FS_PATH := path/to/base_fs_files/base_system_ext.map PRODUCT_VENDOR_BASE_FS_PATH := path/to/base_fs_files/base_vendor.map PRODUCT_PRODUCT_BASE_FS_PATH := path/to/base_fs_files/base_product.map PRODUCT_ODM_BASE_FS_PATH := path/to/base_fs_files/base_odm.map
雖然這無助於減小整體 OTA 套件大小,但它確實透過減少 I/O 量來提高 OTA 更新效能。對於虛擬 A/B 更新,它大大減少了應用 OTA 所需的儲存空間量。
避免更新應用程式
除了最大限度地減少建置差異之外,您還可以透過排除透過應用程式商店取得更新的應用程式的更新來減少 OTA 更新大小。 APK 通常包含裝置上各個分區的重要部分。在 OTA 更新中包含應用程式商店更新的應用程式的最新版本可能會對 OTA 套件產生很大的影響,並且為用戶帶來的好處很少。當用戶收到 OTA 包時,他們可能已經擁有直接從應用程式商店收到的更新應用程式或更新版本。