為了使APK文件盡可能小,您應該啟用縮小以刪除您的發(fā)布版本中未使用的代碼和資源。 下面描述如何做,以及如何指定在構(gòu)建過程中要保留或丟棄的代碼和資源。
通過ProGuard實現(xiàn)壓縮代碼是合適的,ProGuard從您的打包應用程序中檢測和刪除未使用的類,字段,方法和屬性,包括來自包含的代碼庫(使其成為處理64k參考限制的有價值的工具)。 ProGuard還優(yōu)化字節(jié)碼,刪除未使用的代碼指令,并使用短名稱混淆剩余的類,字段和方法。 混淆代碼使您的APK難以反向工程,這在您的應用使用安全敏感功能(例如許可驗證)時尤其有用。
通過the Android Plugin for Gradle實現(xiàn)壓縮資源是合適的,它從您的打包應用程序中刪除未使用的資源,包括代碼庫中未使用的資源。 它與代碼縮減結(jié)合使用,以便一旦未使用的代碼被刪除,任何不再被引用的資源也可以被安全地刪除。
本文檔中的功能依賴于:
- SDK Tools 25.0.10 or higher
- Android Plugin for Gradle 2.0.0 or higher
壓縮代碼
要使用ProGuard啟用壓縮代碼,請將minifyEnabled true添加到build.gradle文件中的相應構(gòu)建類型。
請注意,代碼縮減會減慢構(gòu)建時間,因此,如果可能,應避免在調(diào)試版本上使用它。 不過,重要的是,您必須在用于測試的最終APK上啟用代碼縮減,因為如果您沒有足夠自定義要保留的代碼,它可能會導致錯誤。
例如,來自build.gradle文件的以下代碼段使構(gòu)建release版本時壓縮代碼:
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(‘proguard-android.txt'),
'proguard-rules.pro'
}
}
...
}
注意:使用Instant Run時Android Studio會禁用ProGuard。
除了minifyEnabled屬性,proguardFiles屬性定義了ProGuard規(guī)則:
- getDefaultProguardFile('proguard-android.txt')方法從Android SDK tools / proguard /文件夾獲取默認的ProGuard設(shè)置。
提示:對于更多的代碼縮減,請嘗試位于同一位置的proguard-android-optimize.txt文件。 它與proguard-android.txt包括相同的ProGuard規(guī)則,但是還包含在字節(jié)碼級別(內(nèi)部和跨方法 )執(zhí)行分析的優(yōu)化來進一步減少您的APK大小,并幫助它運行更快。 - proguard-rules.pro文件是您可以添加自定義ProGuard規(guī)則的位置。 默認情況下,此文件位于模塊的根目錄(在build.gradle文件旁邊)。
要添加特定于每個build variant的更多ProGuard規(guī)則,請在相應的productFlavor塊中添加另一個proguardFiles屬性。 例如,以下Gradle文件將flavor2-rules.pro添加到flavor2 product flavor中。 現(xiàn)在flavor2使用三個ProGuard規(guī)則,因為來自release塊的那些規(guī)則也被應用。
android {
...
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
productFlavors {
flavor1 {
}
flavor2 {
proguardFile 'flavor2-rules.pro'
}
}
}
對于每次構(gòu)建,ProGuard生成以下文件:
- dump.txt
描述APK中所有類文件的內(nèi)部結(jié)構(gòu)。 - mapping.txt
提供原始和混淆后的類,方法和字段名稱之間的轉(zhuǎn)換。 - seeds.txt
列出未被混淆的類和成員。 - usage.txt
列出從APK中移除的代碼。
這些文件被保存在 <module-name>/build/outputs/mapping/release/目錄下。
定制要保留的代碼
對于某些情況,默認的ProGuard配置文件(proguard-android.txt)就足夠了,ProGuard刪除所有未使用的代碼。 然而,許多情況下,ProGuard很難正確分析,它可能會刪除您的應用程序?qū)嶋H需要的代碼。 可能會錯誤地刪除代碼的一些示例包括:
- 當應用程序引用一個僅來自于AndroidManifest.xml文件的類
- 當應用程序從Java Native Interface(JNI)調(diào)用方法時
- 當應用在運行時操作代碼(如使用反射或內(nèi)?。?/li>
應該通過測試應用程序來揭示由不適當刪除的代碼導致的任何錯誤,但也可以通過查看保存在<module-name> / build / outputs / mapping / release /中的usage.txt輸出文件來檢查刪除的代碼。
要修復錯誤并強制ProGuard保留某些代碼,請在ProGuard配置文件中添加一個-keep行。 例如:
-keep public class MyClass
或者,您可以將@Keep注釋添加到要保留的代碼。 在類上添加@Keep會保持整個類不變。 將它添加到方法或字段上將保持方法/字段(和它的名稱)以及類名稱不變。 請注意,此注釋僅在使用注釋支持庫時可用。
使用-keep選項時,您應該考慮許多因素; 有關(guān)自定義配置文件的更多信息,請閱讀ProGuard手冊。 “疑難解答”部分概述了在您的代碼被刪除時可能遇到的其他常見問題。
解碼混淆的stack trace
ProGuard壓縮代碼后,讀取stack trace是很困難(如果不是不可能),因為方法名稱被混淆處理。 幸運的是,ProGuard每次運行時都會創(chuàng)建一個mapping.txt文件,它顯示混淆后的名稱與原始類,方法和字段名稱之間的映射關(guān)系。 ProGuard將文件保存在<module-name> / build / outputs / mapping / release /目錄中。
請注意,每次使用ProGuard創(chuàng)建release版本時,mapping.txt文件都會被覆蓋,因此每次發(fā)布新release版本時都必須小心保存副本。 通過為每個 release build保留mapping.txt文件的副本,如果用戶從舊版本的應用程序提交混淆的stack trace,您將能夠調(diào)試問題。
在Google Play上發(fā)布app時,您可以為每個版本的APK上傳mapping.txt檔案。 然后,Google Play會從用戶報告的問題中反混淆進入的stack traces,以便您可以在Google Play開發(fā)者控制臺中查看這些stack traces。 有關(guān)詳細信息,請參閱幫助中心文章,了解如何反混淆crash stack traces。
要將混淆的stack trace轉(zhuǎn)換為可讀的stack trace,請使用回溯腳本(Windows上為retrace.bat; Mac上為retrace.sh)。 它位于<sdk-root> / tools / proguard /目錄中。 該腳本采用mapping.txt文件和您的stack trace,產(chǎn)生一個新的,可讀的stack trace。 使用retrace工具的語法是:
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
舉例:
retrace.bat -verbose mapping.txt obfuscated_trace.txt
如果不指定stack trace文件,則retrace工具從標準輸入讀取。
壓縮資源
壓縮資源只能與壓縮代碼相結(jié)合。 代碼壓縮器刪除所有未使用的代碼后,資源壓縮器可以識別應用程序仍在使用哪些資源。 尤其當您添加包含資源的代碼庫時,您必須刪除未使用的庫代碼,以便庫資源變?yōu)槲匆?,從而可由資源壓縮器移除。
要啟用資源壓縮,請在build.gradle文件(同時設(shè)置minifyEnabled來啟動代碼壓縮)中將shrinkResources屬性設(shè)置為true。 例如:
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
如果你還沒有使用代碼壓縮構(gòu)建你的app,那么在啟用shrinkResources移除資源之前,你可能需要先編輯proguard-rules.pro文件以保護動態(tài)執(zhí)行和創(chuàng)建的類和方法(比如反射調(diào)用的代碼)。
注意:資源縮小器當前不會刪除在values/ 文件夾中定義的資源(例如strings, dimensions, styles, and colors)。 這是因為Android資源打包工具(AAPT)不允許Gradle插件為資源指定預定義版本。 有關(guān)詳細信息,請參閱問題70869。
定制要保留的資源
如果有要保留或丟棄的特定資源,請使用<resources>tag在項目中創(chuàng)建一個XML文件,并在tools:discard屬性中指定要保留的資源和在tools:keep屬性中指定要丟棄的資源。 兩個屬性都接受以逗號分隔的資源名稱列表。 您可以使用星號字符作為通配符。
舉例:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
tools:discard="@layout/unused2" />
將此文件保存在項目資源中,例如res / raw / keep.xml。 build時不會把這個文件打包到APK中。
當然可以直接刪除資源時卻指定要丟棄的資源可能看起來很愚蠢,但這在使用 build variants時可能很有用。 例如,您可以將所有資源放入公共項目目錄,然后為每個build variant創(chuàng)建一個不同的keep.xml文件,當給定資源在代碼中被使用(因此不會被縮小器刪除),但是該資源實際上不會用于給定的build variant,那么該build variant的keep.xml文件應該指定該資源被丟棄。
啟用strict reference檢查
通常,資源壓縮器可以精確地確定一個資源是否被使用。 但是如果代碼中調(diào)用了Resources.getIdentifier()(或者任何一個庫調(diào)用了這個方法),這意味著你的代碼是基于動態(tài)生成的字符串查找資源名稱。 當你這樣做時,資源壓縮器在默認情況下會防御性地運行,并將匹配的名稱格式的所有資源標記為可能已使用且無法刪除。
例如,以下代碼會將所有帶有img_前綴的資源標記為已使用:
String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());
資源壓縮器還查看代碼中的所有字符串常量以及各種res / raw /資源,以類似于file:///android_res/drawable//ic_plus_anim_016.png的格式查找資源URL。 如果它發(fā)現(xiàn)這樣的字符串或者其他看起來可以用于構(gòu)造這樣的URL,就不會刪除URL對應的資源。
這些都是默認情況下啟用的安全壓縮模式的示例。 但是,您可以關(guān)閉此“better safe than sorry”處理,并指定資源壓縮器僅保留其確定使用的資源。 為此,請在keep.xml文件中將shrinkMode設(shè)置為strict,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />
如果您確實啟用了strict壓縮模式,并且代碼還引用了動態(tài)生成的字符串的資源,如上所示,那么您必須使用tools:keep屬性手動保留這些資源。
刪除未使用的備用資源
Gradle資源壓縮器只會移除app code未引用的資源,這意味著它不會移除對應不同設(shè)備的備選資源。 如果需要,您可以使用Android Gradle插件的resConfigs屬性來刪除app不需要的備選資源文件。
例如,如果您使用的庫包含語言資源(例如AppCompat或Google Play Services),則APK會包含這些庫中所有語言的字符串,無論您的應用程序的其他部分是否有對應的語言。 如果您只想保留app正式支援的語言,可以使用resConfig屬性指定這些語言。 將刪除未指定語言的任何資源。
以下代碼段顯示了如何將您的語言資源限制為僅限英語和法語:
android {
defaultConfig {
...
resConfigs "en", "fr"
}
}
同樣,您可以自定義要在APK中包含哪些screen density 或 ABI資源,并使用APK拆分為不同設(shè)備構(gòu)建不同的APK。
合并重復資源
默認情況下,Gradle還合并相同名稱的資源,例如在不同資源文件夾中具有相同名稱的drawables。 此行為不受shrinkResources屬性控制且不能禁用,因為必須避免在多個資源與代碼查找的名稱匹配時出現(xiàn)錯誤。
僅當兩個或多個文件共享相同的資源名稱,類型和限定符時,才會進行資源合并。 Gradle選擇哪個被認為是重復項中最佳選擇的文件(基于下面描述的優(yōu)先級順序),并且僅將那個資源傳遞給AAPT以在APK文件中分發(fā)。
Gradle在以下位置查找重復的資源:
- The main resources,與the main source set相關(guān),一般位于src / main / res /
- The variant overlays, from the build type and build flavors.。
- The library project dependencies.
Gradle以以下級聯(lián)優(yōu)先級順序合并重復資源:
Dependencies → Main → Build flavor → Build type
例如,如果重復資源同時出現(xiàn)在main resources和build flavor中,Gradle將選擇build flavor中的一個。
如果相同的資源出現(xiàn)在同一源集中,則Gradle不能合并它們并發(fā)出資源合并錯誤。 如果在build.gradle文件的sourceSet屬性中定義多個源集,例如,如果src / main / res /和src / main / res2 /包含相同的資源,則可能會發(fā)生這種情況。
排查資源壓縮問題
當壓縮資源時,Gradle Console會顯示從應用程序包中刪除的資源的摘要。 例如:
:android:shrinkDebugResources
Removed unused resources: Binary resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning
Gradle還在<module-name> / build / outputs / mapping / release /(與ProGuard生存的文件保存在相同的文件夾下)中創(chuàng)建一個名為resources.txt的診斷文件。 此文件包括詳細信息,例如資源之間的引用關(guān)系以及哪些資源被使用或刪除。
例如,要了解為什么@ drawable / ic_plus_anim_016仍在您的APK中,請打開resources.txt文件并搜索該文件名。 您可能會發(fā)現(xiàn)它被另一個資源引用,如下所示:
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
你現(xiàn)在需要知道為什么@ drawable / add_schedule_fab_icon_anim是可及的,如果你向上搜索,你會發(fā)現(xiàn)該資源列在“The root reachable resources are:”。 這意味著有一個代碼引用add_schedule_fab_icon_anim(也就是說,它的R.drawable ID在可達代碼中找到)。
如果不使用strict檢查,如果有字符串常量看起來像被用來構(gòu)造資源名稱從而動態(tài)加載資源,則該資源ID可以標記為可達。 在這種情況下,如果您在build輸出中搜索資源名稱,您可能會發(fā)現(xiàn)這樣的消息:
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.
如果您看到其中一個字符串,并且您確定該字符串未用于動態(tài)加載給定資源,則可以使用以下工具:discard屬性通知build系統(tǒng)將其刪除,可以參考上面的 定制要保留的資源。
該文章是對于Google文檔的翻譯:
Shrink Your Code and Resources