做者 / Google 軟件工程師 SørenGjesse 和 Christoffer Adamsenjava
人們更傾向於安裝並保留較小和安裝佔用空間更小的應用,在新興市場中尤其明顯。有了 R8 編譯器,您能夠經過壓縮、混淆和優化,更全面的縮小應用體積。android
本文咱們將對 R8 的特性進行一個簡要的介紹,並介紹可預期的代碼縮減程度以及如何在 R8 中啓用這些功能。ios
R8 經過下面 4 項特性來減小 Android 應用大小:git
開發應用時,全部代碼都應有目的並在應用中實現相應功能。不過,大多數應用都會使用 Jetpack、OkHttp、Guava、Gson 和 Google Play 服務 等第三方庫,而且用 Kotlin 編寫的應用始終包含 Kotlin 標準庫。當您使用這其中的某個第三方庫時,您的應用中一般只使用其中很小一部分。若不壓縮,全部庫代碼都會保留在您的應用中。github
您的代碼大小也可能比實際須要的大,由於冗長的代碼有時能夠提升可讀性和可維護性: 例如,您可能會盡可能使用有意義的變量名和建造者模式 (builder pattern) 來幫助其餘人更容易檢查和理解您的代碼。可是這些模式會加大代碼量。一般,您本身編寫的代碼有很大的壓縮空間。算法
要在 release build 上啓用 R8 壓縮,須要在應用的主 build.gradle 文件中將 minifyEnable 屬性設置爲 true,以下所示:編程
android { buildTypes { release { minifyEnabled true } } }
別被 minifyEnable 這個名字所迷惑,它會啓用 R8 的代碼縮減功能。api
R8 能夠大大減少應用的大小。例如,去年的 Google I/O 應用大小爲 18.55 MB,壓縮前包含 150,220 個方法和 3 個 DEX 文件。壓縮後,應用大小縮小到 6.45 MB,包含 45,831 個方法和 1 個 DEX 文件。R8 縮減了 65% 的 DEX 文件大小 (測量數據來自 Android Studio 3.5.1 和 IOSched 示例應用)。app
爲簡單起見,咱們寫了一個基於 Java 編程語言的程序做爲參考:jvm
class com.example.JavaHelloWorld { private void unused() { System.out.println("Unused"); } private static void greeting() { System.out.println("Hello, world!"); } public static void main(String[] args) { greeting(); } }
程序的入口是 static void main 方法,咱們使用如下 keep 規則 指定該方法:
-keep class com.example.JavaHelloWorld { public static void main(java.lang.String[]); }
R8 縮減算法的運做方式以下:
class com.example.JavaHelloWorld { private static void a() { System.out.println("Hello, world!"); } public static void main(String[] args) { a(); } }
class com.example.JavaHelloWorld { public static void main(String[] args) { System.out.println("Hello, world!"); } }
如您所見,處理後的代碼比原始代碼短得多。
正如獨立的 Java 程序同樣,Android 應用有許多常見的入口點: Activity (活動)
,Service (服務)
,Content Provider (內容提供者)
和 Broadcast Receiver (廣播接收者)
。aapt2
工具經過基於 Android Manifest
文件生成 keep 規則來爲您處理這些入口點。
除了這些熟知的入口點,Android 應用還須要其餘標準的 keep 規則。這些規則由 Android Gradle 插件提供,您能夠在配置構建時指定該默認配置文件:
android { buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt') } } }
反射 (Reflection) 會致使 R8 在跟蹤代碼時沒法識別到代碼的入口點。第三方庫也可能用到反射,而且因爲第三方庫其實是您的應用的一部分,您 (做爲應用開發者) 將負責這些庫以及您本身的代碼中使用的反射。第三方庫可能附帶了它們本身的規則,可是切記,有些庫不必定是爲 Android 編寫的,抑或是未考慮縮減問題,所以它們可能須要其餘配置。
以一個 Kotlin 類爲例,該類具備一個名爲 name 的字段和一個 main 方法,該方法建立一個實例並將該實例序列化爲 JSON:
class Person(val name: String) fun printJson() { val gson = Gson() val person = Person("Søren Gjesse") println(gson.toJson(person)) }
縮減代碼後,運行程序將輸出一個空的 JSON 對象 {}。這是由於 R8 僅將字段名視爲寫入 (在 Person 構造函數中),但從未讀取,所以 R8 會將其移除。最後 Person 丟失了字段值,形成空的 JSON 對象。可是,該字段由 Gson 序列化讀取,而 Gson 使用反射的方式來執行此操做,所以 R8 沒法看到此字段已被讀取。
要保留名稱字段,請在您的 proguard-rules.pro 文件中添加一個保留規則 -keep:
-keep class com.example.myapplication.Person { public java.lang.String name; }
此規則告訴 R8 不要處理 Person 類中的 name 的字段。將其放置在適當位置後,運行代碼便可獲得預期的 JSON 對象 {"name": "SørenGjesse"}
。
最後,在配置項目時,請確保將 proguard-rules.pro
文件添加到 build.gradle
配置中:
android { buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } }
有興趣更深刻了解 R8 壓縮器如何運做嗎?請參考 R8 開發者文檔 瞭解更多!