Gradle 翻譯 ProGuard Shrink 混淆和壓縮 [MD]


博文地址

我的GitHub 我的博客 我的微信 我的郵箱
baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目錄

 

官方文檔

壓縮代碼和資源

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

代碼壓縮 Code shrinking】 
通過 ProGuard 提供,ProGuard 會檢測和移除封裝應用中未使用的類、字段、方法和屬性,包括 libraries 中的未使用項(這使其成為以 變通方式 解決 64k引用限制 的有用工具)。ProGuard 還可優化字節碼,移除未使用的代碼指令,以及用短名稱混淆其余的類、字段和方法。混淆過的代碼可令您的 APK 難以被逆向工程。

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

壓縮代碼

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

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

例如,下面這段來自 build.gradle 文件的代碼用於為 release build 啟用代碼壓縮:

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

注:Android Studio 會在使用 Instant Run 時停用 ProGuard。如果您需要為增量式構建壓縮代碼,請嘗試 試用 Gradle 壓縮器

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

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

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

  • proguard-rules.pro 文件用於添加自定義 ProGuard 規則。默認情況下,該文件位於模塊根目錄(build.gradle 文件旁)。
Add project specific特定的 ProGuard rules here. 
You can control the set of applied應用的 configuration files using the proguardFiles setting in build.gradle. 
For more details, see http://developer.android.com/guide/developing/tools/proguard.html

If your project uses WebView with JS, uncomment取消注釋 the following and specify指定 the fully qualified class name to the JavaScript interface class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

Uncomment this to preserve the line number information for debugging stack traces 保留調試堆棧跟蹤的行號信息.
#-keepattributes SourceFile,LineNumberTable

If you keep the line number information, uncomment this to hide the original source file name.
#-renamesourcefileattribute SourceFile

要添加更多各 build variant 專用的 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 移除的代碼。

這些文件保存在 <module-name>/build/outputs/mapping/(productFlavor)/release/ 中。

自定義要保留的代碼

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

  • 當應用引用的類 only from AndroidManifest.xml 文件時
  • 當應用調用的方法來自 Java Native Interface (JNI)
  • 當應用在運行時 runtime(例如使用反射或自檢introspection)操作代碼時

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

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

-keep public class MyClass

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

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

解碼混淆過的堆疊追蹤

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

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

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

要自行將混淆過的堆疊追蹤轉換成可讀的堆疊追蹤,請使用 retrace 腳本(在 Windows 上為 retrace.bat;在 Mac/Linux 上為 retrace.sh)。它位於 <sdk-root>/tools/proguard/bin 目錄中。

該腳本利用 mapping.txt 文件和您的堆疊追蹤生成新的可讀堆疊追蹤。使用 retrace 工具的語法如下:

retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
//例如:【retrace.bat -verbose mapping.txt obfuscated_trace.txt】

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

通過 Instant Run 啟用代碼壓縮

如果代碼壓縮在您 增量構建 incrementally building 應用時非常重要,請嘗試 適用於 Gradle 的 Android 插件 內置的試用代碼壓縮器。與 ProGuard 不同,此壓縮器支持 Instant Run。

您也可以使用與 ProGuard 相同的配置文件來配置 Android 插件壓縮器。但是,Android 插件壓縮器不會對您的代碼進行混淆處理或優化,它只會刪除未使用的代碼。因此,您應該僅將其用於 debug builds,並為 release builds 啟用 ProGuard,以便對發布 APK 的代碼進行混淆處理和優化。

要啟用 Android 插件壓縮器,只需在 "debug" 構建類型中將 useProguard 設置為 false(並保留 minifyEnabled 設置 true):

android {
    buildTypes {
        debug {
            minifyEnabled true
            useProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

注:如果 Android 插件壓縮器最初刪除了某個方法,但您之后更改了代碼,使該方法可訪問,Instant Run 會將其視為 結構代碼更改 並執行冷交換。

壓縮資源

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

要啟用資源壓縮,請在 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/ 文件夾中定義的例如字符串、尺寸、樣式和顏色等資源(但是會移除res目錄下的drawable、layout、color等資源)。這是因為 Android 資源打包工具 (AAPT) 不允許 Gradle 插件為資源指定預定義版本。有關詳情,請參閱問題 70869。

自定義要保留的資源

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

例如:

<?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。構建不會將該文件打包到 APK 之中。

指定要舍棄的資源可能看似愚蠢,因為您本可將它們刪除,但在使用 build variants 時,這樣做可能很有用。例如,如果您明知給定資源表面上會在代碼中使用[appears to be used in code](並因此不會被壓縮器移除),但實際不會用於給定構建變體,就可以將所有資源放入公用項目目錄,然后為每個構建變體創建一個不同的 keep.xml 文件。It's also possible that 構建工具無法根據需要正確識別資源,這是因為編譯器會添加內聯資源 ID[adds the resource IDs inline],而資源分析器可能不知道真正引用的資源和恰巧具有相同值的代碼中的整數值之間的差別。

啟用嚴格引用檢查

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

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

String name = String.format("img_%1d", angle + 1);
int res = getResources().getIdentifier(name, "drawable", getPackageName());

資源壓縮器還會瀏覽代碼以及各種 res/raw/ 資源中的所有字符串常量[looks through all the string constants in your code],尋找格式類似於[looking for resource URLs in a format similar to] file:///android_res/drawable//ic_plus_anim_016.png 的資源網址。如果它找到與其類似的字符串,或找到其他看似可用來構建與其類似的網址的字符串,則不會將它們移除。

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

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:shrinkMode="strict" />

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

移除未使用的備用資源

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

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

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

android {
    defaultConfig {
        resConfigs "en", "fr"
    }
}

Similarly, you can customize自定義 which screen density or ABI resources to include in your APK by building multiple APKs that each target a different device configuration.

合並重復資源

Merge duplicate resources

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

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

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

  • 與主源集[main source set]關聯的主資源,一般位於 src/main/res/ 中。
  • 變體疊加[variant overlays],來自 build type and build flavors。
  • 庫項目依賴項。

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

Dependencies → [Main] → Build flavor → Build type

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

如果完全相同的資源出現在同一 source set 中,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 還會在ProGuard 輸出文件所在的文件夾中創建一個名為 resources.txt 的診斷文件,該文件包括諸如哪些資源引用了其他資源以及使用或移除了哪些資源等詳情。

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

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 屬性通知構建系統將它移除。

Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates.

上次更新日期:四月 25, 2018

附:一些引用文件或生成的文件

proguard-android.txt

文件路徑

文件內容(注意,里面提示此文件不會再維護了,並且是在構建是生成默認的文件):

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
#
# This file is no longer maintained維護 and is not used by new (2.2+) versions of the
# Android plugin for Gradle. Instead相反, the Android plugin for Gradle generates the
# default rules at build time and stores them in the build directory.

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose

# Optimization優化 is turned off by default. Dex does not like code run through the ProGuard 
# optimize and preverify steps 優化和預驗證步驟 (and performs some of these optimizations on its own).
-dontoptimize
-dontpreverify

# Note that if you want to enable optimization, you cannot just include optimization flags 
# in your own project configuration file; instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your project.properties file.

-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
    native <methods>;
}

# keep setters in Views so that animations can still work. see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# For enumeration枚舉 classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

-keepclassmembers class **.R$* {
    public static <fields>;
}

# The support library contains references to對...的引用 newer platform versions.
# Don't warn警告 about those in case this app is linking against鏈接到 an older
# platform version.  We know about them, and they are safe.
-dontwarn android.support.**

# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}

proguard-android-optimize.txt

它包括與 proguard-android.txt 相同的 ProGuard 規則,但還包括其他在字節碼一級(方法內和方法間)執行分析的優化,以進一步減小 APK 大小和幫助提高其運行速度。

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
#
# This file is no longer maintained and is not used by new (2.2+) versions of the
# Android plugin for Gradle. Instead, the Android plugin for Gradle generates the
# default rules at build time and stores them in the build directory.
# Optimizations優化: If you don't want to optimize, use the proguard-android.txt configuration file 
# instead of this one, which turns off the optimization flags. Adding optimization introduces帶來
# certain risks一定的風險, since for example not all optimizations performed by ProGuard works on all
# versions of Dalvik. The following flags turn off various各個 optimizations known to have issues問題, 
# but the list may not be complete or up to date最新的. (The "arithmetic"算術 optimization can be used if 
# you are only targeting Android 2.0 or later.)  Make sure you test thoroughly徹底 if you go this route路線.
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*    #*/
-optimizationpasses 5
-allowaccessmodification
-dontpreverify

# The remainder其余部分 of this file is identical相同 to the non-optimized非優化 version of the Proguard
# configuration file (except除了 that the other file has flags to turn off optimization).

...
# 后面的與 proguard-android.txt 完全一樣

構建時 ProGuard 輸出的文件

dump.txt

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

_____________________________________________________________________
- Program class: com/bqt/test/MainActivity
  Superclass:    android/app/ListActivity
  Major version: 0x33
  Minor version: 0x0
    = target 1.7
  Access flags:  0x21
    = public class com.bqt.test.MainActivity extends android.app.ListActivity

Interfaces (count = 0):

Constant Pool (count = 268):
  - Integer [17367043]
  - Integer [2130968617]
  - Integer [2131361821]
  - String []
  - String [
類的限定類名:]
  - String [  
...
193K+ 行

mapping.txt

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

com.bqt.test.MainActivity -> com.bqt.test.MainActivity:
    char[] HEX_DIGITS -> a
    void <init>() -> <init>
    void onCreate(android.os.Bundle) -> onCreate
    void onListItemClick(android.widget.ListView,android.view.View,int,long) -> onListItemClick
    android.os.Bundle getMetaData(android.content.Context) -> a
    java.lang.String getAppSignatureSHA1(android.content.Context) -> b
    android.content.pm.Signature[] getAppSignature(android.content.Context) -> c
    java.lang.String encryptSHA1ToString(byte[]) -> a
    java.lang.String bytes2HexString(byte[]) -> b
    byte[] encryptSHA1(byte[]) -> c
    void <clinit>() -> <clinit>
...
6K+ 行

seeds.txt

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

com.bqt.test.MainActivity
com.bqt.test.MainActivity: MainActivity()
com.bqt.test.R$anim: int abc_fade_in
com.bqt.test.R$color: int material_grey_300
...
4K+ 行

usage.txt

列出從 APK 移除的代碼

android.arch.core.BuildConfig
android.arch.core.R
...
3K+ 行

resources.txt

診斷文件,包括哪些資源引用了其他資源,以及使用或移除了哪些資源等詳情。

例如,要了解 APK 為何包含 @drawable/icon3,在 resources.txt 中搜索該文件名,會發現,有其他資源引用了它:

layout:layout_a:2131296283 => [drawable:icon3:2131099733]

而搜索另一個文件 @drawable/icon2,會發現,因為沒有其他資源引用它,所以最后被移除了(Skipped 跳過):

Skipped unused resource res/drawable/icon2.png: 5720 bytes (replaced with small dummy file of size 67 bytes)

2018-7-22


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM