壓縮代碼和資源

原文:https://developer.android.com/studio/build/shrink-code.html

要盡可能減小 APK 文件,您應該啟用壓縮來移除發布構建中未使用的代碼和資源。此頁面介紹如何執行該操作,以及如何指定要在構建時保留或舍棄的代碼和資源。

代碼壓縮通過 ProGuard 提供,ProGuard 會檢測和移除封裝應用中未使用的類、字段、方法和屬性,包括自帶代碼庫中的未使用項(這使其成為以變通方式解決64k 引用限制的有用工具)。ProGuard 還可優化字節碼,移除未使用的代碼指令,以及用短名稱混淆其余的類、字段和方法。混淆過的代碼可令您的 APK 難以被逆向工程,這在應用使用許可驗證等安全敏感性功能時特別有用。

資源壓縮通過 Android Plugin for Gradle 提供,該插件會移除封裝應用中未使用的資源,包括代碼庫中未使用的資源。它可與代碼壓縮發揮協同效應,使得在移除未使用的代碼后,任何不再被引用的資源也能安全地移除。

本文介紹的功能依賴下列組件:

SDK Tools25.0.10 或更高版本

Android Plugin for Gradle2.0.0 或更高版本

壓縮代碼

要啟用通過 ProGuard 實現的代碼壓縮,請在build.gradle文件相應的構建類型中添加minifyEnabled true。

請注意,代碼壓縮會拖慢構建速度,因此您應該盡可能避免在調試構建中使用。不過,重要的是您一定要為用于測試的最終 APK 啟用代碼壓縮,因為如果您不能充分地自定義要保留的代碼,可能會引入錯誤。

例如,下面這段來自build.gradle文件的代碼用于為發布構建啟用代碼壓縮:

android {

buildTypes {

release {

minifyEnabled true

proguardFiles getDefaultProguardFile(‘proguard-android.txt'),

'proguard-rules.pro'

}

}

...

}

:Android Studio 會在使用Instant Run時停用 ProGuard。

除了minifyEnabled屬性外,還有用于定義 ProGuard 規則的proguardFiles屬性:

getDefaultProguardFile(‘proguard-android.txt')方法可從 Android SDKtools/proguard/文件夾獲取默認 ProGuard 設置。

提示:要想做進一步的代碼壓縮,可嘗試使用位于同一位置的proguard-android-optimize.txt文件。它包括相同的 ProGuard 規則,但還包括其他在字節碼一級(方法內和方法間)執行分析的優化,以進一步減小 APK 大小和幫助提高其運行速度。

proguard-rules.pro文件用于添加自定義 ProGuard 規則。默認情況下,該文件位于模塊根目錄(build.gradle文件旁)。

要添加更多各構建變體專用的 ProGuard 規則,請在相應的productFlavor代碼塊中再添加一個proguardFiles屬性。例如,以下 Gradle 文件會向flavor2產品風味添加flavor2-rules.pro。現在flavor2使用所有三個 ProGuard 規則,因為還應用了來自release代碼塊的規則。

android {

...

buildTypes {

release {

minifyEnabled true

proguardFiles getDefaultProguardFile('proguard-android.txt'),

'proguard-rules.pro'

}

}

productFlavors {

flavor1 {

}

flavor2 {

proguardFile 'flavor2-rules.pro'

}

}

}

每次構建時 ProGuard 都會輸出下列文件:

dump.txt

說明 APK 中所有類文件的內部結構。

mapping.txt

提供原始與混淆過的類、方法和字段名稱之間的轉換。

seeds.txt

列出未進行混淆的類和成員。

usage.txt

列出從 APK 移除的代碼。

這些文件保存在/build/outputs/mapping/release/。

自定義要保留的代碼

對于某些情況,默認 ProGuard 配置文件 (proguard-android.txt) 足以滿足需要,ProGuard 會移除所有(并且只會移除)未使用的代碼。不過,ProGuard 難以對許多情況進行正確分析,可能會移除應用真正需要的代碼。舉例來說,它可能錯誤移除代碼的情況包括:

當應用引用的類只來自AndroidManifest.xml文件時

當應用調用的方法來自 Java 原生接口 (JNI) 時

當應用在運行時(例如使用反射或自檢)操作代碼時

測試應用應該能夠發現因不當移除而導致的錯誤,但您還可通過查看/build/outputs/mapping/release/中保存的usage.txt輸出文件來檢查移除了哪些代碼。

要修正錯誤并強制 ProGuard 保留特定代碼,請在 ProGuard 配置文件中添加一行-keep代碼。例如:

-keeppublicclassMyClass

您還可以向您想保留的代碼添加@Keep注解。在類上添加@Keep可原樣保留整個類。在方法或字段上添加它可完整保留方法/字段(及其名稱)以及類名稱。請注意,只有在使用注解支持庫時,才能使用此注解。

在使用-keep選項時,有許多事項需要考慮;如需了解有關自定義配置文件的詳細信息,請閱讀ProGuard 手冊問題排查一章概述了您可能會在混淆代碼時遇到的其他常見問題。

解碼混淆過的堆疊追蹤

在 ProGuard 壓縮代碼后,讀取堆疊追蹤變得困難(即使并非不可行),因為方法名稱經過了混淆處理。幸運的是,ProGuard 每次運行都會創建一個mapping.txt文件,其中顯示了與混淆過的名稱對應的原始類名稱、方法名稱和字段名稱。ProGuard 將該文件保存在應用的/build/outputs/mapping/release/目錄中。

請注意,您每次使用 ProGuard 創建發布構建時都會覆蓋mapping.txt文件,因此您每次發布新版本時都必須小心地保存一個副本。通過為每個發布構建保留一個mapping.txt文件副本,您就可以在用戶提交的已混淆堆疊追蹤來自舊版本應用時對問題進行調試。

在 Google Play 上發布應用時,您可以上傳每個 APK 版本的mapping.txt文件。Google Play 將根據用戶報告的問題對收到的堆疊追蹤進行去混淆處理,以便您在 Google Play Developer Console 中進行檢查。如需了解詳細信息,請參閱幫助中心有關如何對崩潰堆疊追蹤進行去混淆處理的文章。

要自行將混淆過的堆疊追蹤轉換成可讀的堆疊追蹤,請使用retrace腳本(在 Windows 上為retrace.bat;在 Mac/Linux 上為retrace.sh)。它位于/tools/proguard/目錄中。該腳本利用mapping.txt文件和您的堆疊追蹤生成新的可讀堆疊追蹤。使用 retrace 工具的語法如下:

retrace.bat|retrace.sh [-verbose] mapping.txt []

例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt

如果您不指定堆疊追蹤文件,retrace 工具會從標準輸入讀取。

壓縮資源

資源壓縮只與代碼壓縮協同工作。代碼壓縮器移除所有未使用的代碼后,資源壓縮器便可確定應用仍然使用的資源。這在您添加包含資源的代碼庫時體現得尤為明顯——您必須移除未使用的內容庫代碼,使內容庫資源變為未引用資源,才能通過資源壓縮器將它們移除。

要啟用資源壓縮,請在build.gradle文件中將shrinkResources屬性設置為true(在用于代碼壓縮的minifyEnabled旁邊)。例如:

android {

...

buildTypes {

release {

shrinkResources true

minifyEnabled true

proguardFiles getDefaultProguardFile('proguard-android.txt'),

'proguard-rules.pro'

}

}

}

如果您尚未使用作代碼壓縮用途的minifyEnabled構建應用,請先嘗試使用它,然后再啟用shrinkResources,因為您可能需要編輯proguard-rules.pro文件以保留動態創建或調用的類或方法,然后再開始移除資源。

:資源壓縮器目前不會移除values/文件夾中定義的資源(例如字符串、尺寸、樣式和顏色)。這是因為 Android 資源打包工具 (AAPT) 不允許 Gradle 插件為資源指定預定義版本。有關詳情,請參閱問題 70869

自定義要保留的資源

如果您有明確想要保留或舍棄的資源,請在您的項目中創建一個包含標記的 XML 文件,并在tools:keep屬性中指定每個要保留的資源,在tools:discard屬性中指定每個要舍棄的資源。這兩個屬性都接受逗號分隔的資源名稱列表。您可以使用星號字符作為通配符。

例如:


tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"

tools:discard="@layout/unused2"/>

將該文件保存在項目資源中,例如,保存在res/raw/keep.xml。構建不會將該文件打包到 APK 之中。

指定要舍棄的資源可能看似愚蠢,因為您本可將它們刪除,但在使用構建變體時,這樣做可能很有用。例如,如果您明知給定資源表面上會在代碼中使用(并因此不會被壓縮器移除),但實際不會用于給定構建變體,就可以將所有資源放入同一項目目錄,然后為每個構建變體創建一個不同的keep.xml文件。

啟用嚴格引用檢查

正常情況下,資源壓縮器可準確判定系統是否使用了資源。不過,如果您的代碼調用Resources.getIdentifier()(或您的任何內容庫進行了這一調用——AppCompat內容庫會執行該調用),這就表示您的代碼是根據動態生成的字符串查詢資源名稱。當您執行這一調用時,默認情況下資源壓縮器會采取防御性行為,將所有具有匹配名稱格式的資源標記為可能已使用,無法移除。

例如,以下代碼會使系統將所有帶img_前綴的資源標記為已使用。

Stringname=String.format("img_%1d",angle+1);

res=getResources().getIdentifier(name,"drawable",getPackageName());

資源壓縮器還會瀏覽代碼以及各種res/raw/資源中的所有字符串常量,尋找格式類似于file:///android_res/drawable//ic_plus_anim_016.png的資源網址。如果它找到與其類似的字符串,或找到其他看似可用來構建與其類似的網址的字符串,則不會將它們移除。

這些是默認情況下啟用的安全壓縮模式的示例。但您可以停用這一“有備無患”處理方式,并指定資源壓縮器只保留其確定已使用的資源。要執行此操作,請在keep.xml中將shrinkMode設置為strict,如下所示:


tools:shrinkMode="strict"/>

如果您確已啟用嚴格壓縮模式,并且代碼也引用了包含動態生成字符串的資源(如上所示),您就必須利用tools:keep屬性手動保留這些資源。

移除未使用的備用資源

Gradle 資源壓縮器只會移除未被您的應用代碼引用的資源,這意味著它不會移除用于不同設備配置的備用資源。必要時,您可以使用 Android Gradle 插件的resConfigs屬性來移除您的應用不需要的備用資源文件。

例如,如果您使用的內容庫包含語言資源(例如使用的是 AppCompat 或 Google Play 服務),則 APK 將包括這些內容庫中消息的所有已翻譯語言字符串,無論應用的其余部分是否翻譯為同一語言。如果您想只保留應用正式支持的語言,可以利用resConfig屬性指定這些語言。系統會移除未指定語言的所有資源。

下面這段代碼展示了如何將語言資源限定為僅支持英語和法語:

android {

defaultConfig {

...

resConfigs "en", "fr"

}

}

同理,您也可以利用APK 拆分為不同設備構建不同的 APK,自定義在 APK 中包括的屏幕密度或 ABI 資源。

合并重復資源

默認情況下,Gradle 還會合并同名資源,例如可能位于不同資源文件夾中的同名可繪制對象。這一行為不受shrinkResources屬性控制,也無法停用,因為在有多個資源匹配代碼查詢的名稱時,有必要利用這一行為來避免錯誤。

只有在兩個或更多個文件具有完全相同的資源名稱、類型和限定符時,才會進行資源合并。Gradle 會在重復項中選擇其視為最佳選擇的文件(根據下述優先順序),并只將這一個資源傳遞給 AAPT,以供在 APK 文件中分發。

Gradle 會在下列位置尋找重復資源:

與主源集關聯的主資源,一般位于src/main/res/。

變體疊加,來自構建類型和構建風味。

內容庫項目依賴項。

Gradle 會按以下級聯優先順序合并重復資源:

依賴項 → 主資源 → 構建風味 → 構建類型

例如,如果某個重復資源同時出現在主資源和構建風味中,Gradle 會選擇構建風味中的重復資源。

如果完全相同的資源出現在同一源集中,Gradle 無法合并它們,并且會發出資源合并錯誤。如果您在build.gradle文件的sourceSet屬性中定義了多個源集,在特定情況下(例如src/main/res/和src/main/res2/包含完全相同的資源時),就可能發生這種情況。

排查資源壓縮問題

當您壓縮資源時,Gradle Console 會顯示它從應用軟件包中移除的資源的摘要。例如:

:android:shrinkDebugResources

Removed unused resources: Binary resource data reduced from 2570KB to 1711KB: Removed 33%

:android:validateDebugSigning

Gradle 還會在/build/outputs/mapping/release/(ProGuard 輸出文件所在的文件夾)中創建一個名為resources.txt的診斷文件。該文件包括諸如哪些資源引用了其他資源以及使用或移除了哪些資源等詳情。

例如,要了解您的 APK 為何仍包含@drawable/ic_plus_anim_016,請打開resources.txt文件并搜索該文件名。您可能會發現,有其他資源引用了它,如下所示:

16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true

16:25:48.009 [QUIET] [system.out]? ? @drawable/ic_plus_anim_016

現在您需要了解為何@drawable/add_schedule_fab_icon_anim可以訪問——如果您向上搜索,就會發現“The root reachable resources are:”之下列有該資源。這意味著存在對add_schedule_fab_icon_anim的代碼引用(即在可訪問代碼中找到了其 R.drawable ID)。

如果您使用的不是嚴格檢查,則存在看似可用于為動態加載資源構建資源名稱的字符串常量時,可將資源 ID 標記為可訪問。在這種情況下,如果您在構建輸出中搜索資源名稱,可能會找到類似下面這樣的消息:

10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506

used because it format-string matches string pool constant ic_plus_anim_%1$d.

如果您看到一個這樣的字符串,并且您能確定該字符串未用于動態加載給定資源,就可以按照有關如何自定義要保留的資源部分中所述利用tools:discard屬性通知構建系統將它移除。

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

推薦閱讀更多精彩內容

  • 為了使APK文件盡可能小,您應該啟用縮小以刪除您的發布版本中未使用的代碼和資源。 下面描述如何做,以及如何指定在構...
    小蕓論閱讀 1,263評論 0 5
  • 這部分內容,自己其實看過很多遍,但是無一例外的是:在若干天后,自己在用到這一方面的內容時,竟然忘了,我懷疑自己有個...
    GYLEE閱讀 391評論 0 1
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,639評論 25 708
  • 原文鏈接:https://developer.android.com/studio/build/shrink-co...
    抹香君閱讀 5,577評論 0 7
  • 昨天寶寶會扶著邁步了,周日時發現他已經玩撥珠玩得很溜了。不知不覺小家伙已經十個月,為什么我還天天云里霧里地,似乎一...
    清風田田閱讀 164評論 0 0