前言
包大小是衡量APP性能的一項重要指標,它直接影響用戶的下載點擊率(包太大不想下)、下載安裝成功率(下載慢不用了)、APP卸載率(太占空間先刪掉)。包大小的計算邏輯很簡單,它是各種類型的文件占用磁盤大小相加。APP瘦身的技術卻很復雜,代碼文件的復雜度和編譯器策略決定了可執行文件的大小,業務功能和工程架構決定了代碼文件的復雜度。iOS APP瘦身,需要掌握的技能有XCode構建技術、LLVM編譯器技術、CocoaPods構建技術、圖片壓縮技術、持續集成技術。本文總結提煉了Alibaba.com App的瘦身的技術和策略,系統化地介紹APP瘦身的業務價值、分析技術、瘦身技術、防劣化機制,讓讀者可以系統化地了解APP瘦身的技術體系。本文還基于實踐經驗,介紹各種瘦身技術的ROI,讓讀者可以避免踩雷,將資源浪費在效果不佳的技術上。希望對你有所幫助。??
業務價值
包體大小每上升6MB,應用下載轉化率就會下降1%
在2019谷歌開發者大會上,谷歌給出了一個很詳細的數據,包體大小每上升6MB,應用下載轉化率就會下降1%。不同地區轉化率略有差異,APK包體大小每減少10MB ,全球平均下載轉化率會提升1.75%,新興國家代表印度和巴西下載轉化率提升2.0%以上,高端市場代表美國和德國下載轉化率提升1.5%。
?上圖標題:APK減少10MB,在不同國家轉化率增長
數據來源:google play 內部數據
詳細材料:?白鯨出海:2019谷歌開發者大會首日看點
上述數據調研分析報告是2019年以前,已經有所滯后,僅供參考
包大小影響下載轉化率可能有3個原因:
1.蜂窩網絡環境下,用戶不愿意支付流量費用。包大小超過200MB時,App Store會彈框提醒用戶下載可能會產生流量費用。
2.下載時間太長,用戶不愿意等就取消了
3.下載過程中出現網絡連接問題?雖然Google Play沒有給出不同APP類目的數據,但是從以上三個原因推斷,不同類目包大小對下載轉化率的影響估計差不多。App Store的用戶人群比較高端,可以參考美國和德國的數據。?
20%的人因為存儲空間有限而卸載應用程序
clevertap在2021年做了一項調查,他們調查了2000多個移動應用程序用戶,詢問了他們卸載移動應用程序的主要原因,其中有20%的人因為存儲空間有限而卸載應用程序。
最主要的3個原因是:
1.他們不再使用該應用程序
2.有限的存儲空間
3.太多的廣告。
?詳細材料:clevertap:Why Users Uninstall Apps?
?App Store 發布和下載限制
兼容iOS8的App,主二進制文件的Text段不能超過60MB, 否則將無法提交App Store。App Store下載包超過200MB,無法使用蜂窩流量下載和更新。
分析技術
APP瘦身最終目標是減少App Store的安裝包大小和下載包大小,但研發階段對比XCode構建包大小會更方便,需要理清楚他們之間的口徑差異。
結果指標:App Store安裝包大小和下載包大小
查看路徑是App Store Connect->TestFlight->交付版本->構建版本元數據->App Store文件大小
過程指標:XCode構建包大小
XCode構建產物ipa包的大小和App Store的安裝大小口徑差異很大,通過模擬Apple處理的流程可以得到它們的關系。開發者上傳的ipa包里有多個架構的產物,多套尺寸的圖片資源,蘋果會進行裁剪和二次分發,轉化為App Store下載的ipa包。
構建產物ipad包大?。红o態庫二進制文件(arm64、armv7)、動態庫(arm64、armv7)、asset.car(@2x、@3x)、其他資源文件App Store的安裝大?。红o態庫二進制文件(單架構)、動態庫(單架構)、asset.car(單尺寸)、其他資源文件
使用lipo工具拆分單架構
lipo "originalExecutable" -thin arm64 -output "arm64Executable"
使用assetutil工具拆分asset
xcrun --sdk iphoneos assetutil --scale 3 --output "$targetFolder/Assets3.car" "$sourceFolder/Assets.car"
構建包組成
學習如何治病前,得先了解人體的構成。同樣的道理,我們首現要了解iOS工程結構和IPA包結構。
iOS工程結構:iOS工程由殼工程和Pod模塊組成,模塊有靜態庫和動態庫兩種類型。殼工程的構成有主Target、Apple插件Target。模塊內部的構成有源代碼文件(OC、C、C++的.h和.m)、nib、bunlde、xcassets、多語言文件、各種配置文件(plist、json)。IPA包結構:iOS上傳到App Store是IPA包,IPA包解壓后是一個文件夾,內部由各種類型的文件構成,主要包括MachO可執行文件、.framework(動態庫)、Assets.car、.appex(Apple插件)、.strings(多語言)、.bundle、nib、json、png...。從iOS工程到IPA包:iOS工程構建為IPA包,核心的變化是編譯和文件拷貝,靜態庫里的源代碼會被編譯為MachO可執行文件,xcassets文件夾會被轉化為Assets.car,其他都可以簡單理解為文件拷貝。
通過分析構建包的組成,可以判斷有哪些優化空間。如果動態庫占比太高,就可能有大量可裁剪代碼。建議根據資源大小敏感程度劃分,比如:MachO executeable(包含所有靜態庫)、動態庫、Apple Extension、assets.car、bundle、nib、音頻、視頻。
Pod模塊大小
APP瘦身會涉及大量業務模塊,離不開業務團隊的參與。因此,我們需要分析出每個Pod模塊的大小,從而可以橫向比較各個業務團隊的包大小占比。靜態庫和動態庫計算的原理不同。對于靜態庫,先解析linkmap數據,計算出Pod模塊代碼大小,在解析Pods-targetName-resource.sh的資源拷貝代碼,計算出拷貝到Pod模塊的資源大小。對于動態庫,先使用lipo拆分動態庫的二進制文件,計算出單架構的代碼大小,然后再計算動態庫framework內的資源文件,得到動態庫的資源文件大小。
運行時Objc類覆蓋率
如果能知道App運行時有哪些類被使用過,就可以下線掉無用的模塊或代碼文件,Objc類覆蓋率指標可以幫到我們。APP運行時,某1個Pod模塊被加載的類數量除以所有類數量,可以稱為這個模塊的Objc類覆蓋率。核心技術是判斷一個類是否被加載過,下面介紹一個經過線上驗證的輕量級方案。ObjC的類第一次被使用時會調用+initialize方法,類被加載過后cls->isInitialized會返回True。isInitialized方法讀取了metaClass的data變量里的flags,如果flags里的第29位為1,則返回True。
// objc-class.mm
Class class_initialize(Class cls, id inst) {
if (!cls->isInitialized()) {
initializeNonMetaClass (_class_getNonMetaClass(cls, inst));
}
return cls;
}
// objc-runtime.h
#define RW_INITIALIZED (1<<29)
bool isInitialized() {
return getMeta()->data()->flags & RW_INITIALIZED;
}
未被使用的圖片資源
技術原理是先解析出所有拷貝到構建產物的資源文件,再解析出代碼中實際引用到的資源文件,兩者的差集就是無用資源。
第一步獲取全量資源文件。在Cocoapos工程中,“Pods-targetName-resource.sh”腳本負責拷貝Pod里的文件資源到構建產物,包括所有文件類型bundle、xcassets、json、png。解析該腳本可以得到每個Pod模塊都拷貝了哪些圖片資源。
// Pods-targetName-resource.sh
install_resource "${PODS_ROOT}/APodName/APodName.framework/APodName.bundle"
install_resource "${PODS_ROOT}/BPodName/BPodName.framework/BPodName.xcassets"
install_resource "${PODS_ROOT}/BPodName/BPodName.framework/xxx.png"
第二步,獲取代碼中實際引用到的資源文件。OC代碼中引用資源文件都是以字符串字面量的形式聲明,構建后存放在Mach-O文件"__cstring" section。利用strings解析framework的二進制文件就可以得到代碼中所有的字符串聲明,然后基于代碼的各種引用方式匹配“xxx”,"xxx@2x.png","xxx.png","xxx.bundle/xxx.png"-
strings executable | grep 'xxx' > cstrings.txt
編譯時未被引用的類
iOS編譯的產物是Mach-o格式的,文件里 __DATA __objc_classrefs 段記錄了所有引用過的類的地址,__DATA __objc_classlist段記錄了所有類的地址,兩者Differ可以得到未被引用類的地址。然后將地址符號化,就可以得到未被引用類信息。因為Objc是動態語言,如果使用runtime動態調用某個class,這種情況掃描不出來。(比如Target Action和 JS Core)
otool -v -s __DATA __objc_classrefs xxxMainClient #讀取__DATA Segment中section為__objc_classrefs的符號
otool -v -s __DATA __objc_classlist xxxMainClient #讀取__DATA Segment中section為__objc_classlist的符號
nm -nm xxxMainClient
?掃描未使用類的開源工具?
瘦身技術
包大小瘦身可以從純技術視角瘦身,也可以從邏輯視角瘦身。
從純技術視角有兩種思路,第一種思路是優化編譯邏輯, 第二種思路是刪減各種其他類型(非編譯產物)的文件。編譯優化對業務邏輯無侵入式,風險和成本比較低,但收益通常也不高。刪減文件則比較復雜,刪減資源文件收益高但成本不小,逐個刪減源碼文件風險高且收益小。
相比而言,從邏輯的視角瘦身效果會更明顯。站在邏輯的視角,工程是由許多功能模塊組成的,主要可以分為業務功能模塊(首頁、搜索、收銀臺、訂單)、基礎功能模塊(網絡庫、圖片庫、中間件、各種三方庫...)。大型工程通常幾百甚至上千個模塊組成,小模塊的有幾十KB,大的模塊有1MB~10+MB。功能模塊內聚性強,當某個功能模塊可以廢棄或被替代時,整體下線的風險和成本比較低,ROI很高。
瘦身技術大圖
根據項目經驗,我梳理出各種優化方法的ROI,下面依次介紹。
??組件瘦身
組件瘦身的ROI:類加載率0%的組件 > 無用功能組件 > 重復功能組件“類加載率0%的組件”是一個完整的Pod模塊,模塊的代碼和資源可以被整體刪除,ROI最高?!盁o用功能組件”通常是模塊內某個文件目錄,需要處理一些耦合,梳理資源的歸屬,ROI其次?!爸貜凸δ芙M件”需要適當重構,遷移成本和風險最高,ROI相對較低。
類加載率0%的組件
“類加載率0%的組件”是指運行時沒有任何一個類被加載過的組件,它在運行過程中完全沒有用到,可以整體下線。統計類加載率時一般會進行采樣,有些低頻業務組件可能會誤報,下線前需要和業務Owner二次確認。?
??
無用功能組件
“無用功能組件”是邏輯上已經沒用的組件,它可能代碼還有耦合,但邏輯已經沒用,通過重構就可以下線。比如AB實驗沒效果的業務代碼、往年大促的業務代碼、業務改版后遺留的老業務、已經過時的三方庫。
重復功能組件
對于大規模團隊,不同業務線可能會引入“重復功能組件”,比如圖片選擇器、緩存庫、UI組件。重復的功能組件會帶來不必要的包大小壓力,同時也會帶來維護成本。
資源瘦身
資源瘦身ROI:大資源>有損壓縮>重復資源>iconfont>多語言文案瘦身>無用資源
大資源
對大資源進行單點優化收益很大,優先分析100KB以上的資源。比如音頻文件,我們工程中音頻鈴聲900KB,優化后去掉了700KB。
有損壓縮
XCode構建時會做“compile asset catalog”,會重新對圖片進行無損壓縮。因此使用imageoptim等工具進行無損壓縮效果不明顯,其中壓縮png圖片沒有效果,壓縮jpg圖片有一定效果。根據實踐經驗,icon做有損壓縮并不影響視覺體驗,壓縮率可以達到70%~80%。比如使用tinypng算法壓縮一張425.9 KB的png圖片,壓縮后79.8 KB,壓縮率達到81%。業界有不少png壓縮工具,我們使用到的有tinypng、pngquant、pngcrush、optipng(無損)、advpng。實測發現不同的圖片,壓縮效果最好的工具是不一樣,所以壓縮圖片時可以用每個工具嘗試,然后取效果最好的。需要注意的是,壓縮APNG圖片可能會變成非動圖,盡量避免對APNG圖片進行壓縮,壓縮前先識別是否APNG圖片。
# 判斷是否APNG格式,APNG格式不壓縮
function isApng() {
ret=`grep "acTL" "$1"`
lastWord="${ret##* }"
if [[ $lastWord == "matches" ]]; then
echo "YES"
else
echo "NO"
fi
}
# 使用各種工具壓縮png圖片
# pngquant
pngquant 256 -s5 --quality 70 --output "${tmpFileName}" -- "${tmpOrigName}"
# pngcrush
pngcrush -brute -rem alla -nofilecheck -bail -blacken -reduce -cc -- "$tmpOrigName" "$tmpFileName"
# optipng
optipng -o6 -i0 -out "$tmpFileName" -- "$tmpOrigName"
# advpng
cp "$tmpOrigName" "$tmpFileName"
advpng -4 -z -- "$tmpFileName"
# 逐個比較壓縮效果,保留效果最好的壓縮圖片
sizeBefore=`stat -f%z "${tmpOrigName}"`
sizeBefore=`expr $sizeBefore`
sizeNow=`stat -f%z "${tmpFileName}"`
sizeNow=`expr $sizeNow`
if [ "$sizeNow" -lt "$sizeBefore" ]; then
# 大小變小了
echo "[advpng] $tmpOrigName"
retImg="$tmpFileName"
fi
# 記錄hash值
img_sum=`/usr/bin/shasum -a 256 "$retImg" | cut -d " " -f1`
cache_file="$cacheFolder/$img_sum.png"
無用資源
業務長期迭代會積累許多無用的資源。通過代碼靜態掃描,可以分析出沒有被引用的圖片資源。我們有工程無用資源有12MB,其中有20個模塊無用資源超過100KB。?
重復圖標
不同業務需求會引入重復資源,長期積累也會造成很大浪費。解決方案是在構建時計算資源的哈希值,去重相同哈希資源,并保留源文件名和哈希值的映射表。運行時Hook 資源加載的”imageNamed“方法,根據映射表替換資源名稱。
ODR
iOS不能像Android一樣,運行時更新Framework。iOS的ODR技術(On Demand Resource)提供了運行時動態下載的能力。如果啟動用不到的資源文件,可以通過ODR處理。
iconfont
iconfont支持縮放、修改顏色,它size小,適合用于箭頭、占位圖等圖標場景,使用iconfont可以減少包大小也能提高開發視覺體驗的統一性。首先,基于設計規范出一套完整的iconfont,封裝iconfont組件,提供易用的API。然后禁止新業務場景增加圖片資源,團隊形成開發習慣后再逐步替換存量的圖片。
多語言文案
對于國際化App,多語言文案也是大頭。我們App支持20個語種,每個語種4000多條文案,文案總大小6MB,瘦身后減少了2MB。
編譯優化
編譯優化ROI:Optimization Level > 動態庫復用主二進制靜態庫> 鏈接器產物壓縮
XCode編譯優化選項中,Optimization Level效果最明顯,建議所有模塊構建都開啟Oz選項。如果APP有動態庫,并且依賴了openssl等基礎靜態庫,建議和APP工程共享,并通過EXPORTED_SYMBOLS_FILE的選型,確保動態庫中需要用到的符號都在編譯過程保留。如果團隊技術儲備強,可以自研鏈接器產物壓縮技術,效果也很大。具體效果如下。
主工程 | OC模塊 62880行代碼 | C++模塊 | |
---|---|---|---|
LTO | <10KB | 開啟前 modulesize:2020KB, MainProjectSize:6463KB 開啟后 modulesize:1582KB MainProjectSize:6888KB 收益:13KB | / |
剝離符號表:Strip Linked Product | <10KB | / | / |
精簡編譯產物Oz:Optimization Level | 100-200KB | Os:2261KB Oz:2020KB 收益:241KB | / |
Symbols Hidden by Default | <10KB | 優化前:2020KB 優化后:2020KB 收益:0KB | / |
剔除未引用的C/C++/Swift代碼:Dead Code Strippin | <10KB | / | / |
Asset Catalog Compiler | <10KB | ||
動態庫共享APP基礎靜態庫 | / | / | 動態庫A依賴了公共的靜態庫庫B,通過設置只導出必要符號 收益:1367KB |
C++全局靜態數組改成動態分配內存 | / | 收益:200KB | |
C++去掉RTTI支持 | / | 收益:1MB |
?精簡編譯產物Oz:Optimization Level
Optimization Level多個級別,-Oz比-O3的編譯產物體積小10%左右。設置-Oz以后,XCode會優化連續的匯編指令,從而減少二進制大小,但副作用是執行速度會變慢。C++工程建議都開啟。
主工程Release
Optimization Level :-Oz
Framework工程
Optimization Level :-Oz
LTO(OC/C++)
LTO 是在LLVM鏈接時,優化跨模塊調用代碼。根據大家分享的經驗,它不同APP包大小優化效果差異較大,需要具體測試。另外它也有一些副作用,建議只在Release包開啟。LTO介紹
優點:
●將一些函數內聯化
●去除了一些無用代碼
●對程序有全局的優化作用
缺點:
●降低編譯鏈接速度,只建議在打正式包時開啟
●降低 link map 可讀性(出現XX-lto.thin的類)
主工程設置
主工程Release
Link-Time Optimization 設置為Incremental
framework工程Release
Link-Time Optimization 設置為Incremental
動態庫復用主二進制靜態庫
C++動態庫經常用到一些基礎庫比如openssl、libyuv、libcurl,他們一般是靜態庫。如果動態庫引用了靜態庫,它編譯時默認會內嵌靜態庫的所有符號。雖然我們可以在動態庫中設置只導出需要用到的靜態庫符號,但是有可能多個動態庫都用到了同一個基礎庫,這樣還是會造成基礎庫的冗余。比如openssl大小1MB,如果A、B兩個動態庫依賴了openssl,APP也引用了openssl,最終ipa包實際有3個openssl,有2MB大小是冗余的。
這種場景下,最佳解決方案是共享符號表,讓動態庫可以調用主二進制的基礎庫符號,從而可以去掉內置的靜態庫。只要修改XCode的Link配置,無需額外的代碼開發。
動態庫工程:
1、設置當遇到未定義的函數時,動態查找APP主二進制符號表。
2、關閉bitcode
Other Linker Flags -> -undefined dynamic_lookup
Enable Bitcode -> No
3導出動態庫需要調用的外部符號,寫到一個文件exported_symbols內
nm -u xxx.framework/xxx > exported_symbols.txt
APP工程:
1、配置需要導出exported_symbols文件內的所有符號,避免編譯時動態庫需要用到的符號被strip掉。
2、關閉bitcode。
// exported_symbols.txt是需要被導出的符號文件路徑
EXPORTED_SYMBOLS_FILE -> exported_symbols.txt
Enable Bitcode -> No
如果C++動態庫里依賴了外部靜態庫,該靜態庫的符號默認會被全部導出。實際上動態庫只用了其中的部分符號,可以設置導出符號的白名單,從而減少導出沒有用到的靜態庫符號。
注意事項:
1APP和framwork工程都要關閉bitcode,否則編譯不過。2如果多個動態庫都使用同一個基礎庫,導出的符號表需要取并集。3動態庫升級要及時更新符號表,基礎庫升級要測試兼容性。
鏈接器產物壓縮(黑科技)
iOS工程構建產物是MachO文件,MachO文件中的TEXT段存放了各種只讀的數據段,__cstring段存放了普通的C String,__objc_methtype和__objc_methname存放了Objc的方法簽名和方法名。比入Objc代碼中聲明的@"Hello world",底層會產生一個CFString,構建后存放在__cstring中。這些數據很占空間,一般工程至少會有10MB以上,壓縮的收益很可觀。我們上線后,App Store安裝包大小從191MB優化到174MB,減少了16MB。
技術原理:
鏈接時將TEXT段數據移到__DATA段并壓縮,運行時先執行解壓代碼,解壓TEXT段數據存到自定義段中,將代碼中對字符串的引用的地址修正為解壓后的自定義段。
剝離符號表:Strip Linked Product
Strip Linked Product設置會剝離特定的符號,Debug環境不要設置YES,否則調試時看不到符號。
主工程Release
Deployment Postprocessing :YES
Strip Linked Product :YES
Strip Style :All Symbols(剝離所有符號表和重定向信息)
Framework工程
Deployment Postprocessing :YES
Strip Linked Product :YES
Strip Style :Non-Global Symbols(剝離包括調試信息等非全局的符號,保留外部符號)
說明:
1、靜態庫不能將Strip Style 設置為All Symbols,因為剝離了所有符號的靜態庫是無法被正常鏈接的
2、去除符號不影響 dSYM 文件中的符號信息,查看崩潰日志時,可以從 dSYM 文件中找對應符號
Symbols Hidden by Default
Symbols Hidden by Default用于設置符號默認可見性,如果設置為YES,XCode會把所有符號都定義為”private extern”,包大小會略有減少。動態庫設置為NO,否則會有鏈接錯誤。
主工程Release
Symbols Hidden by Default :Yes
Framework工程 靜態庫/動態庫
Symbols Hidden by Default :NO
剔除未引用的C/C++/Swift代碼:Dead Code Stripping
Dead Code Stripping開啟后會在鏈接時移除未使用的代碼,它對靜態語言C/C++/Swift有效,對動態語言OC無效。
主工程
Dead Code Stripping :Yes
Asset Catalog Compiler
Optimization有三個選項,空、time和Space,選擇Space可以優化包大小
主工程
Asset Catalog Compiler->Optimization設置為space
C++只導出必要符號:Symbol Visibility
C++的靜態庫和動態庫都只導出必須的符號,默認設置為隱藏所有符號,然后用Visibility Attributes單獨控制需要導出的符號
默認隱藏所有C++符號
Other C++ Flags->添加-fvisibility=hidden
設置需要導出的符號
__attribute__((visibility("default"))) void MyFunction1() {}
__attribute__((visibility("default"))) void MyFunction2() {}
....
代碼下線
根據Objc類覆蓋率統計的結果,可以逐步下線掉未被使用的類。代碼文件的量級比較小,下線代碼需要仔細確認,避免引起功能問題或crash,ROI比較低。
??Flutter專項
Flutter是單獨構建的,引入的內容包括Flutter引擎產物、Flutter業務代碼產物。APP如果引入Flutter,需要對Flutter進行專項瘦身。
精簡編譯產物Oz:Optimization Level
-Oz選項相比Os,收益預估11%,但首屏性能1%~9%的損耗
Dart符號剔除和混淆
根據官方文檔,可以通過--dwarf-stack-trace選項去除Dart標準的調試符號。通過--obfuscate 選項,可以將較長的符號替換為短符號,副作用是符號會被混淆。實踐效果:Release環境下,--dwarf-stack-trace和--obfuscate選項開啟后減少14%的大小
Flutter icon搖樹優化
--tree-shake-icons實踐效果:Alibaba.com App開啟后減少了300KB左右大小
去除NOTICES文件
實踐效果:壓縮前700KB,壓縮后80KB
防劣化機制
增量標準和集成卡口
?包大小瘦身的技術就像各種減肥運動,跑步、跳舞、燃脂瑜伽。但想達到減肥的目標,只靠運動是不夠的,還得控制卡路里的攝入,管住嘴的人最后才能減肥成功。因此,我們還需要”新增卡路里的規范”(包大小增量規范)和監工(集成卡口)。持續集成當中加入一個卡口插件,分析構建包的linkemap文件得到模塊的大小可,然后對比基線數據,如果違反了包大小增量標準,則禁止集成。
包大小增量規范(參考):
1.基線數據:基于特定版本,通過上文提到的”計算模塊大小“的方法,計算出每個模塊的基線數據
2.存量模塊:模塊增量超過基準數據100KB時禁止集成。設置補償機制,如果能對存量模塊瘦身,可以抵消新增的模塊大小。對于特殊情況可以走特殊審批,加入審批流程是啟發大家反思。增加那么多是否有價值?有沒有帶入不必要的資源?
3.新模塊:新業務功能需要走審批流程,評估新業務價值和包大小增量是否合理。
橫向對比業務健康度
當包大小從技術層面已經優化到極致時,想要進一步瘦身,只能從業務價值的角度去挖掘。如果一個模塊磁盤尺寸大,用戶使用量卻少,那可以認為它對業務的價值較小。因此我們可以定義一個技術指標來衡量模塊單位大小的業務貢獻度?;谌莘e率指標,我們就可以橫向對比各個業務,要求容積率低的業務做包大小瘦身,或下線不太重要的業務功能。
容積率 = Business PV / Business size
總結
總結一下包大瘦身的實施路徑。第一步,制定目標,跟蹤APP下載轉化率(App Store Connect Analytics)、APP安裝包大小、XCode構建包大小等結果指標。第二步,建設分析體系,包括Pod模塊大小分析、Objc類覆蓋率分析、無用圖片資源分析等。第三步,根據ROI使用各項瘦身技術,組件瘦身>資源瘦身>編譯優化>代碼下線.第四步,建設防劣化機制,包括增量標準、集成卡口能力、健康度分析。