Android R8

版本:1.4.94
地址:r8

介紹

r8包含了D8 的功能, 實現(xiàn)了對 java 字節(jié)碼優(yōu)化,混淆并轉(zhuǎn)換成 dex 文件的功能。 可以很好的替代了 ProGuard 的在 Android 編譯工具鏈上的應(yīng)用。 同時生成的 dex 文件更為輕小。

r8 主要分為 5 個階段: Read Input,Configuration,Shrink ,Optimize,Write Dex
代碼入口 com.android.tools.r8.R8

Read Inputs

相對于 ProGuard 只支持對 class 文件的解析 。
r8 支持對 dex 和 class 文件的解析。 class 文件使用 ASM 框架進(jìn)行解析。dex 文件直接采用操作 2 進(jìn)制的方案進(jìn)行解析。 dex 版本支持 v35 ( android 2-5 ) v37 ( android 6 - 7) v38 ( android 8 ) v39( android 9 ) 。

dex 生成的版本取決 app 的 minSdkVersion。dex 之所以存在多個版本。

  1. dex 在新的版本中引入了新的字節(jié)碼。
  2. google 會收集特定指令排序在特定版本虛擬機(jī)上運行的 bug 。 從而在生成對應(yīng)版本 dex 的時候規(guī)避掉這些 bug。

相關(guān)字節(jié)碼列表可以查看鏈接 dalvik-bytecode#instructions。

Configuration

工具的運行少不了配置的使用。為了能平滑的替換 ProGuard 的功能。 r8 兼容大部分的 ProGuard rule 。同時擴(kuò)展了 rule 定義。
支持的主要有 keep,if,repackageclasses,flattenpackagehierarchy,overloadaggressively,allowaccessmodification,basedirectory,obfuscationdictionary,classobfuscationdictionary,packageobfuscationdictionary,useuniqueclassmembernames,keepdirectories,renamesourcefileattribute,keepattributes,keeppackagenames,keepparameternames,printconfiguration,printmapping,applymapping,printseeds...
r8 不支持大部分的優(yōu)化配置。 但是 新增了新的 rule 來擴(kuò)展之前的優(yōu)化功能。
forceinline,neverinline,neverclassinline,nevermerge

這里對 rule 的含義不做過多解釋。 可以查看 ProGuard 官方文檔 ProGuard usage 或查看之前的文章 ProGuard 初探

Shrink

移除未被使用的類、字段、方法和屬性。這里和 ProGuard 一樣不會對方法簽名或指令進(jìn)行裁剪。
在處理方法的時候, 需要注意這幾個

  1. Kotlin 的反射
    Kotlin 的反射是基于解析注解實現(xiàn)的。Kotlin 經(jīng)過 kotlinc 生成的 class 文件包含一個注解 @kotlin.Metadata。 由編譯器生成, 記錄 Kotlin 源文件的基本信息。Kotlin 運行時使用 ReadKotlinClassHeaderAnnotationVisitor 對 @kotlin.Metadata 注解進(jìn)行解析。 所以 Kotlin 反射非常慢。在處理 Kotlin 在混淆和裁剪的時候。 需同步修改 @kotlin.Metadata 里面的定義。r8 在這里使用 Kotlin 的官方庫 kotlinx-metadata-jvm 操作 @kotlin.Metadata 元素。

  2. Lambda 表達(dá)式
    Lambda 是在 java 8 上引入的。如果單純要實現(xiàn) Lambda 的效果,技術(shù)方法其實有很多種。 最終使用 invokedynamic 主要有兩點,一是更穩(wěn)定的文件格式。 二是更靈活的轉(zhuǎn)換策略,Lambda 的轉(zhuǎn)換策略由運行期決定的。
    Lambda 分為編譯期和運行期。
    編譯期:
    a. javac 對 Lambda 生成一個 invokedynamic 指令,該指令指向一個 BootstrapMethods 方法,
    b. 將 Lambda 方法內(nèi)代碼轉(zhuǎn)移到該類的一個私有方法內(nèi)。
    c. BootstrapMethods 方法指向生成的的私有方法。


    BootstrapMethods

    運行期:
    執(zhí)行 invokedynamic 。 會執(zhí)行 invokedynamic 指向的 BootstrapMethods 定義的方法返回 CallSite 。 Lambda 返回 CallSite 的方法是 LambdaMetafactory.metafactory 或 LambdaMetafactory.altMetafactory 。默認(rèn)情況下使用 metafactory ,當(dāng)你的 Lambda 實現(xiàn)了多個接口時,將使用 altMetafactory 返回。 最終返回一個實現(xiàn)了該接口的實現(xiàn)類。 這個實現(xiàn)類是由運行期 ASM 動態(tài)生成的,該類主要是做一個轉(zhuǎn)發(fā)的功能, 將方法和參數(shù)轉(zhuǎn)發(fā)給 c 生成的私有方法。
    所以在保留 invokedynamic 字節(jié)碼的時候,需要同步保留 invokedynamic 指向的的 BootstrapMethods 以及BootstrapMethods 指向的私有方法。
    這里還存在一個問題。 javac 生成的是一個私有方法。 一個外部類是怎樣調(diào)用另外一個類的私有方法?

  3. 關(guān)于 java 反射
    r8 對于反射是在最近幾個版本支持的, 支持以下 api
    AtomicIntegerFieldUpdater.newUpdater
    AtomicLongFieldUpdater.newUpdater
    AtomicReferenceFieldUpdater.newUpdater
    Class.forName
    SomeClass.getName
    SomeClass.getCanonicalName
    SomeClass.getSimpleName
    SomeClass.getTypeName
    SomeClass.getField
    SomeClass.getDeclaredField
    SomeClass.getMethod
    SomeClass.getDeclaredMethod
    相比于 ProGuard 使用模板匹配的方式。 r8 將代碼轉(zhuǎn)成 中間表現(xiàn) IR 通過 SSA 的方式對代碼進(jìn)行分析。因為使用代碼分析所以 r8 跟蹤反射功能的適應(yīng)性比 ProGuard 好。在反射優(yōu)化中 r8 和 ProGuard 對于構(gòu)造方法均只能識別無參構(gòu)造方法, 對于其他的構(gòu)造方法在這都是無能為力。

  4. r8 部分支持對 ServiceLoader 機(jī)制。
    ServiceLoader JSP(Service Provider Interfaces)。 ServiceLoader 的實現(xiàn)有兩種版本。 一種是在 JDK 9 以下。 通過定義一個接口。同時將繼承該接口的實現(xiàn)類將記錄在META-INF/services 接口同名文件下。第二種是在 JDK 9 上面用于支持 java9 模塊化下不同模塊的通信。 實現(xiàn)類信息記錄在 module-info.java 下。 對于 r8 只處理第一種實現(xiàn)。 java9 暫不在 r8 的支持范圍內(nèi)。

  5. r8 可以刪除可見的橋接方法
    當(dāng)允許修改訪問權(quán)限,可見的橋接方法將被刪除。
    https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6342411
    可見的橋接方法是為了解決 public class 繼承了一個私有類的時。 反射調(diào)用存在該類父類的 public 方法出現(xiàn)的 IllegalAccessException 錯誤。
    我們只要修改父類為 public 就能安全的刪除橋接方法而不會有任何的影響。

  6. r8 的 Shrink 規(guī)則和 ProGuard 相同。 首先計算所有 keep rule 定義的根節(jié)點。 從這些根節(jié)點發(fā)散出去。
    對于 Externalizable 和 Serializable 需要額外的處理。 Externalizable 需要保留無參構(gòu)造方法。 Externalizable 存在兩個方法 readExternal 和 writeExternal 用來自定義序列化中的操作。 r8 默認(rèn)會把這兩個方法干掉。 ProGuard 則會將它保留。 原因是 r8 認(rèn)為 readExternal 和 writeExternal 沒有被調(diào)用過。而ProGuard 認(rèn)為你繼承了 Externalizable 那么你就有義務(wù)保留它的重寫方法。

ProGuard 和 r8 對比

  1. 保留一個 class r8 僅僅保留 靜態(tài)初始化 cinit 的方法,而 ProGuard 同步保留他們的無參構(gòu)造方法。
  2. 一個虛方法被保留 ProGuard 將保留整條繼承數(shù)上的該方法。 r8 的僅僅保留該方法。 只在該方法調(diào)用 super 才會保留父類的虛方法。
  3. 一個類被 keep 。r8 會同步 keep 它的父類以及他們的接口。但是也只是僅僅 keep 住他們接口本身。 ProGuard 是 keep 他的父類。而接口并不會主動 keep 。 接口的 keep 是在接口方法被調(diào)用的時候。

Optimize

r8 使用 SSA (靜態(tài)單一賦值)對代碼進(jìn)行優(yōu)化。

如果有需要在這個階段將進(jìn)行 java8 脫糖。

  1. Lambda
    由上面的流程介紹可知, javac 生成的方法是私有。需要修改方法為 public 。 同時需要將 ASM 生成的實現(xiàn)類落地。 將對應(yīng)調(diào)用點轉(zhuǎn)成對應(yīng)的方法。

  2. 接口的默認(rèn)方法
    為有默認(rèn)方法的接口生成一個新的類,類名在原有的基礎(chǔ)上加入后綴 -CC。遷移默認(rèn)方法。同時方法名加入前綴 default。同時將調(diào)用點轉(zhuǎn)換成調(diào)用新的靜態(tài)方法。

...
優(yōu)化項

  1. 優(yōu)化 Stringbuilder
  2. 優(yōu)化 String 指令
  3. 簡化 if 指令。
  4. 清理橋接方法。
  5. 刪除沒有影響的方法的調(diào)用
  6. 合并 class
  7. 刪除未被使用方法參數(shù)
  8. 優(yōu)化 枚舉 switch
  9. 刪除未可達(dá)的代碼
  10. 刪除強轉(zhuǎn)指令
  11. 刪除 assert 指令生成的方法。
  12. 折疊 常量數(shù)字的 算數(shù)運算或 邏輯運算
  13. 兼容高版本 api 在低版本沒有的問題。
  14. 內(nèi)連方法
  15. new-array 指令轉(zhuǎn)換 fill-array-data / filled-new-array 節(jié)省 字節(jié)指令。
  16. ...

這一塊的代碼是基于老版本的分析。后續(xù)會有更為詳細(xì)的分析。
--- 待續(xù) ---

Obfuscate

混淆跟 ProGuard 類似。 支持字典的自定義。 不同是 r8 在開啟保留簽名(Signature)會保留內(nèi)部類的類名的時候同時會保留外部類的類名,使兩個類類名保持內(nèi)外類的命名關(guān)系。
r8 在這原來的基礎(chǔ)上支持對行號進(jìn)行優(yōu)化。盡可能把所有方法的開始行號映射為1 。
mapping 文件變?yōu)?/p>

    2:2:android.arch.core.internal.SafeIterableMap$Entry get(java.lang.Object):45:45 -> a
    

前面為映射后行號, 后面為源碼中行號。

優(yōu)化行號的好處在于可以合并相同的 debug_info_item。這個方案有點類似于之前的支付寶瘦身。 但是合并效率當(dāng)然會有不如。

r8 其他使用。

ProGuard 在 Android 工具鏈上的應(yīng)用不僅僅用在代碼優(yōu)化混淆上。同時也用在 mainDexList 的計算。 r8 同樣支持對 mainDexList 計算。甚至 mainDexList 文件可以不落地。 但是 r8 計算的 mainDexList 列表會比 ProGuard 計算出來的還多。 因為它不僅保留了所有代碼入口發(fā)散出去的類,以及他們的直接引用。r8 還保留了所有的帶枚舉的注解。以及被這該注解標(biāo)記的類。

至此 r8 已經(jīng)能接管所有 ProGuard 的功能。


proguard
r8

java 編譯到 dex 的過程中。還有一個 javac 這一個非 Google 的工具鏈。 或許后續(xù)可能會升級 javac 用以對 dex 的支持。

小結(jié)

r8 已經(jīng)足夠的出色了。但是過于苛刻的保留規(guī)則導(dǎo)致之前規(guī)則并不能無條件的適應(yīng)。當(dāng)前輸出只支持 Dex 。 導(dǎo)致該工具不能應(yīng)用在其他的 java 項目上。較為可惜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內(nèi)容