Android R8

版本:1.4.94
地址:r8

介紹

r8包含了D8 的功能, 實(shí)現(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ī)上運(yùn)行的 bug 陵究。 從而在生成對應(yīng)版本 dex 的時候規(guī)避掉這些 bug。

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

Configuration

工具的運(yùn)行少不了配置的使用畔乙。為了能平滑的替換 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 的反射是基于解析注解實(shí)現(xiàn)的。Kotlin 經(jīng)過 kotlinc 生成的 class 文件包含一個注解 @kotlin.Metadata刊头。 由編譯器生成黍瞧, 記錄 Kotlin 源文件的基本信息。Kotlin 運(yùn)行時使用 ReadKotlinClassHeaderAnnotationVisitor 對 @kotlin.Metadata 注解進(jìn)行解析原杂。 所以 Kotlin 反射非常慢印颤。在處理 Kotlin 在混淆和裁剪的時候。 需同步修改 @kotlin.Metadata 里面的定義穿肄。r8 在這里使用 Kotlin 的官方庫 kotlinx-metadata-jvm 操作 @kotlin.Metadata 元素年局。

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


    BootstrapMethods

    運(yùn)行期:
    執(zhí)行 invokedynamic 。 會執(zhí)行 invokedynamic 指向的 BootstrapMethods 定義的方法返回 CallSite 膳沽。 Lambda 返回 CallSite 的方法是 LambdaMetafactory.metafactory 或 LambdaMetafactory.altMetafactory 汗菜。默認(rèn)情況下使用 metafactory ,當(dāng)你的 Lambda 實(shí)現(xiàn)了多個接口時挑社,將使用 altMetafactory 返回陨界。 最終返回一個實(shí)現(xiàn)了該接口的實(shí)現(xiàn)類。 這個實(shí)現(xiàn)類是由運(yù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)行分析嫉戚。因?yàn)槭褂么a分析所以 r8 跟蹤反射功能的適應(yīng)性比 ProGuard 好刨裆。在反射優(yōu)化中 r8 和 ProGuard 對于構(gòu)造方法均只能識別無參構(gòu)造方法, 對于其他的構(gòu)造方法在這都是無能為力彬檀。

  4. r8 部分支持對 ServiceLoader 機(jī)制帆啃。
    ServiceLoader JSP(Service Provider Interfaces)。 ServiceLoader 的實(shí)現(xiàn)有兩種版本窍帝。 一種是在 JDK 9 以下努潘。 通過定義一個接口。同時將繼承該接口的實(shí)現(xiàn)類將記錄在META-INF/services 接口同名文件下坤学。第二種是在 JDK 9 上面用于支持 java9 模塊化下不同模塊的通信疯坤。 實(shí)現(xiàn)類信息記錄在 module-info.java 下。 對于 r8 只處理第一種實(shí)現(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 相同突梦。 首先計(jì)算所有 keep rule 定義的根節(jié)點(diǎn)。 從這些根節(jié)點(diǎn)發(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 生成的實(shí)現(xiàn)類落地堡掏。 將對應(yīng)調(diào)用點(diǎn)轉(zhuǎn)成對應(yīng)的方法应结。

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

...
優(yōu)化項(xiàng)

  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. 刪除強(qiáng)轉(zhuǎn)指令
  11. 刪除 assert 指令生成的方法拴鸵。
  12. 折疊 常量數(shù)字的 算數(shù)運(yùn)算或 邏輯運(yùn)算
  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口叙。這個方案有點(diǎn)類似于之前的支付寶瘦身。 但是合并效率當(dāng)然會有不如嗅战。

r8 其他使用庐扫。

ProGuard 在 Android 工具鏈上的應(yīng)用不僅僅用在代碼優(yōu)化混淆上。同時也用在 mainDexList 的計(jì)算仗哨。 r8 同樣支持對 mainDexList 計(jì)算形庭。甚至 mainDexList 文件可以不落地。 但是 r8 計(jì)算的 mainDexList 列表會比 ProGuard 計(jì)算出來的還多厌漂。 因?yàn)樗粌H保留了所有代碼入口發(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 項(xiàng)目上。較為可惜好港。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末愉镰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子钧汹,更是在濱河造成了極大的恐慌丈探,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拔莱,死亡現(xiàn)場離奇詭異碗降,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)塘秦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門遗锣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嗤形,你說我怎么就攤上這事精偿。” “怎么了赋兵?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵笔咽,是天一觀的道長。 經(jīng)常有香客問我霹期,道長叶组,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任历造,我火速辦了婚禮甩十,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吭产。我一直安慰自己侣监,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布臣淤。 她就那樣靜靜地躺著橄霉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪邑蒋。 梳的紋絲不亂的頭發(fā)上姓蜂,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天按厘,我揣著相機(jī)與錄音,去河邊找鬼钱慢。 笑死逮京,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的束莫。 我是一名探鬼主播懒棉,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼麦箍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起陶珠,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤挟裂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后揍诽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诀蓉,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年暑脆,在試婚紗的時候發(fā)現(xiàn)自己被綠了渠啤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡添吗,死狀恐怖沥曹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碟联,我是刑警寧澤妓美,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布鲤孵,位于F島的核電站壶栋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏普监。R本人自食惡果不足惜贵试,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凯正。 院中可真熱鬧毙玻,春花似錦、人聲如沸廊散。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奸汇。三九已至施符,卻和暖如春往声,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背戳吝。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工浩销, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人听哭。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓慢洋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親陆盘。 傳聞我的和親對象是個殘疾皇子普筹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

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