你是不是覺得 R8 很討厭,但 Android 為什麼選擇 R8 ?也許你對 R8 還不夠了解
本篇是來自 Android Developers 的播客 《What’s so great about R8?》 的整合,核心是討論了 Android R8 編譯器以及它對性能的影響,參與討論的嘉賓包括來自 Android 工具團隊、R8 團隊和平台性能團隊的專家(Tor Norby, Romain Guy, Sean, Chris, Shai)。
這是一篇讓你對 R8 不再誤解的內容。
D8 與 R8 編譯器的區別
首先可能不少人還不理解 D8 與 R8 的區別,在 Android 開發裡,我們都知道程式碼從 Java/Kotlin 編譯為位元組碼後,還需要經過另一層編譯才能在設備上運行:
- D8 編譯器 :這是開發者在日常開發中使用的調試編譯器,它速度快、支持增量編譯,目的是讓開發者能夠以最快的速度在設備或模擬器上編輯、運行和調試程式碼,但它不會對程式碼進行深度的性能優化
- R8 編譯器 :這是用於發布 (Release build) 的全球優化編譯器,它會將整個應用作為一個整體進行靜態分析,花費可能高達數十分鐘的時間來應用各種複雜的優化,從而生成體積最小、運行最快的最終程式碼
所以,D8 主要處理 dexing(將 Java 位元組碼轉換為 Dalvik 位元組碼)和基本的程式碼縮小,而 R8 的功能更全面,包括高級優化如跨模組分析。

R8 的核心優化機制
所以 R8 不僅僅是混淆程式碼,它在底層做了大量提升性能的工作:
- Tree Shaking :正常應用裡通常會引入大量第三方庫,但往往只使用其中的一小部分功能,而 R8 能夠透過全局靜態分析,找出哪些程式碼在運行時永遠不會被調用,並將其完全移除 (當然,這也是很多時候大家討厭 R8 的原因,因為 R8 在高版本默認開啟後,應用升級就崩潰)
- 方法與參數內聯 :R8 會分析程式碼的調用情況,將總是接收相同參數的方法,或調用頻繁但非常簡短的方法直接內聯到調用處,從而減少虛擬方法調用的開銷
- 合併介面:如果 R8 發現一個介面在實際生產程式碼中只有一個實現類(例如僅僅為了單元測試而提取的介面),它可以將該介面直接合併到實現類中,減少一層指針查找的性能損耗
- 與 ART 運行時的協同優化:R8 的工作會為設備上的 ART (Android Runtime) 編譯器“鋪平道路”,結合 Baseline Profiles(基準配置文件),R8 知道哪些程式碼是啟動時的“熱程式碼”,並會在 DEX 檔案中留下提示,幫助 ART 在用戶設備上進一步生成高效的機器碼
- 移除 Kotlin 空安全檢查:Kotlin 會在底層插入大量的非空檢查 (如
checkNotNullParameter 等),在發布版本中,R8 可以基於全局分析證明某些值永遠不為空從而移除檢查,或者將其替換為不帶冗長字串的精簡指令,既減小了體積又提升了性能

所以可以看出, R8 比 D8 做了更多的性能和優化工作,本質上其實就是為了讓 App 更快更小。
所以了解 R8 裡做了什麼也很重要,因為經過 R8 後你的 App 運行可能會出現某些奇怪問題,而找到這些問題的原因,就需要你清楚知道 R8 做了什麼。
R8 面臨的最大挑戰:反射與保留規則
但是就像我們前面說的,R8 也帶來了很多問題,比如它雖然 Tree Shaking ,但是它對於 Tree Shaking 也沒辦法做到盡善盡美:
- 反射與 JNI 帶來的問題 :由於 R8 是靜態分析工具,它很難預測在運行時透過字串動態調用的類、方法或欄位(如反射),如果 R8 看不到某個類的靜態調用,就會將其當作“死程式碼”移除,導致應用在運行時拋出
ClassNotFoundException 崩潰
- 保留規則 (Keep Rules) :為了反射和 JNI 問題,開發者必須編寫 Keep Rules 來明確告訴 R8 哪些類和方法不能被優化或移除,但是很多第三方庫為了省事,會編寫過於寬泛的規則(例如要求保留整個庫的所有程式碼),這會嚴重阻礙 R8 發揮作用並拖累整體性能
恩, GSY 大體上就是這樣,因為 GSY 播放器裡有很多 JNI 和反射調用,所以在提供的 Keep Rules 裡,都是直接用寬泛的規則,省事。

開發者體驗與工具鏈的改進
另外,如今的 R8 開發體驗和資料改進已經非常完善,比如大家一開始認識的 R8 已經是“女大十八變”了:
- 反混淆與崩潰日誌:R8 會將類名和方法名重命名為簡短的字母(如 "a", "b", "c")以節省空間,為了讓崩潰日誌可讀,R8 會輸出 Mapping 檔案,現在的 Android Studio 提供了不錯的整合(Android Studio 的 App Quality Insights),開發者可以在 IDE 中直接點擊按鈕,自動下載和映射線上的崩潰堆疊,體驗上和調試 D8 構建一樣順暢(它會利用線上的 mapping 檔案對崩潰堆疊進行反混淆)
- 官方文檔大翻新:在此之前,R8 的文檔一言難盡,而現在 R8 團隊對官方文檔進行了大規模翻新,詳細解釋了 R8 的工作原理、Keep Rules 的語法規範、最佳實踐以及如何排查不合理的保留規則

R8 會大量更改原始碼的情況,所以堆棧軌跡無法指向原始程式碼,例如行號以及類和方法的名稱可能會發生變化,所以在使用 retrace 時,需要 mapping 檔案,例如 : $ANDROID_HOME/cmdline-tools/latest/bin/retrace app/build/outputs/mapping/$releaseVariant/mapping.txt trace.txt 。
R8 的新特性與未來方向
而實際上 R8 目前還處於高速發展的階段,未來還有需要多能力提供,例如:
- 局部/包級別的 R8 優化 (Gradual R8) : 為了幫助歷史包袱沉重的大型應用逐步遷移到 R8,R8 團隊推出了按包範圍開啟 R8 的功能,開發者可以先對 AndroidX 庫、Compose 等確定安全的包開啟優化,然後逐步將自己的業務程式碼加入優化範圍,從而降低崩潰風險
- @UsesReflection 注解:相比於在單獨的配置檔案中寫晦澀的 Keep Rules,R8 團隊正在推廣一種新的
@UsesReflection 注解,開發者可以直接在發起反射調用的地方添加注解,指明它反射了什麼目標,這種方式讓配置跟隨程式碼移動,支持條件保留,且能被 IDE 直接識別和檢查
- 優化的資源縮減 (Optimized Resource Shrinking) :過去,程式碼縮減和資源縮減是分開進行的,現在 R8 將這兩者結合起來,可以跨越程式碼和 XML 資源進行全局追蹤,如果一個 Activity 的程式碼被判定為未使用並被移除了,R8 會順藤摸瓜將它引用的無用 XML 布局、圖片資源一並剔除

所以,官方團隊也建議所有開發者開啟 R8,因為開啟 R8 不僅能減小 APK 體積,還能大幅降低後台 ANR 的概率,並顯著提升應用啟動和運行速度,例如性能團隊的 Shai 分享了一個數據:
根據他們與 Top 應用開發者的合作,將一個應用從不使用 R8 優化到全面深度應用 R8,其帶來的性能提升效果,相當於給用戶的手機免費進行了“3 到 4 年的硬體升級” ,換句話說,運行經過 R8 優化程式碼的舊手機,體驗可以媲美運行未優化程式碼的最新款手機。
當然,這裡的 R8 更多是 full mode 的 R8 ,R8 提供兩種模式,兼容模式和全模式,全模式才是滿血優化,android.enableR8.fullMode=false 的可不算,例如:
在兼容模式下,R8 不改變類中方法和欄位的可見性,而在完全模式下,R8 會透過改變方法和欄位的可見性來增強優化,比如將 private to public 從而支持更多內聯。
總而言之,那就是你需要 R8 ,擁抱 R8 ,All in R8 ,阿巴阿巴阿巴阿巴·····
原文出處:https://juejin.cn/post/7607332124488466495