1. 程式人生 > >使用混淆ProGuard壓縮程式碼和資源/減少方法數量

使用混淆ProGuard壓縮程式碼和資源/減少方法數量

ProGuard介紹

ProGuard是一個Java類檔案壓縮器,優化器,混淆器和預先檔案驗證器。 壓縮步驟檢測和刪除未使用的類,欄位,方法和屬性。 優化步驟分析和優化方法的位元組碼。 混淆步驟使用短無意義的名稱重新命名剩餘的類,欄位和方法。 這些步驟使程式碼更小,更高效,更難以進行逆向工程。 最終的預驗證步驟將預驗證資訊新增到類,這是Java Micro Edition和Java 6及更高版本所必需的。

所以我們可以通過開啟 ProGuard 來去除未使用的類、欄位、方法和屬性,包括自帶程式碼庫中的未使用項,這樣就以變通方式解決了 64k 限制的問題。同時ProGuard 還可優化位元組碼,移除未使用的程式碼指令,以及用短名稱混淆其餘的類、欄位和方法。這樣也在一定程度上減少了Apk的大小。

ShrinkResources資源壓縮通過 Android Plugin for Gradle 提供,該外掛會移除應用中未使用的資源,包括庫中未使用的資源。同樣也可減少Apk的大小。

如何啟用壓縮和混淆?

先上程式碼,在build.gradle中配置如下:

android {
    buildTypes {
        release {
            minifyEnabled true //啟用程式碼壓縮
            shrinkResources true //啟用資源壓縮
            proguardFiles getDefaultProguardFile(‘proguard-android
.txt'), 'proguard-rules.pro'//混淆規則檔案配置 } } }
  • getDefaultProguardFile(‘proguard-android.txt’)
    這個配置的意思是從 Android SDK tools/proguard/ 資料夾獲取預設 ProGuard 設定。使用 proguard-android-optimize.txt 可以進一步的壓縮程式碼還包括其他在位元組碼一級(方法內和方法間)執行分析的優化,可進一步減小 APK 大小和幫助提高其執行速度。
  • proguard-rules.pro
    可以在這個檔案中新增自定義的規則,該檔案和build.gradle在同級目錄下。

ProGuard的輸出檔案及用處:
混淆過程完成後會生成如下幾個檔案,
dump.txt
說明 APK 中所有類檔案的內部結構。
mapping.txt
提供原始與混淆過的類、方法和欄位名稱之間的轉換。
seeds.txt
列出未進行混淆的類和成員。
usage.txt
列出從 APK 移除的程式碼。
這些檔案儲存在 /build/outputs/mapping/release/目錄下。

這裡需要特別注意:每次release新版編譯後,最好將這些檔案加入git版本庫一起儲存,因為每次Release編譯後這些檔案都會被覆蓋。

ProGuard配置說明

保留配置(即對配置的內容不進行處理)

-keep [,modifier,…] class_specification
指定要保留的類和類成員(欄位和方法)。

-keepclassmembers [,modifier,…] class_specification
指定要保留的類成員,如果它們的類也被保留。 例如,你可能希望保留實現Serializable介面的所有序列化欄位和類的方法。

-keepclasseswithmembers [,modifier,…] class_specification
指定要保留的類和類成員,條件是所有指定的類成員都存在。 例如,你可能希望保留所有具有main方法的應用程式,而無需顯式列出。

-keepnames class_specification
-keep的縮寫,允許class_specification
指定要儲存的類和類成員的名稱(如果它們在壓縮階段不被刪除)。 例如,你可能希望保留實現Serializable介面的類的所有類名,以便處理的程式碼與任何原始的序列化類保持相容。 根本不使用的類仍然可以刪除。 只適用於混淆。

-keepclassmembernames class_specification
–keepclassmembers的縮寫,allowhrinking class_specification
指定要儲存其名稱的類成員,如果它們在壓縮階段未被刪除。 例如,當處理由JDK 1.2或更舊版本編譯的庫時,你可能希望保留內部類$ 方法的名稱,因此當處理使用已處理庫的應用程式時,混淆器可以再次檢測它(儘管ProGuard本身不需要 這個)。 只適用於混淆。

-keepclasseswithmembernames class_specification
–keepclasseswithmembernames的縮寫,允許class_specification
指定要保留其名稱的類和類成員,條件是所有指定的類成員在收縮階段之後都存在。 例如,你可能希望保留所有本機方法名稱和其類的名稱,以便處理後的程式碼仍然可以連結到本機庫程式碼。 根本不使用的本地方法仍然可以刪除。 如果使用一個類檔案,但是它的本機方法都不是,它的名字仍然會被模糊。 只適用於混淆。

-printseeds [filename]
指定徹底列出由各種保護選項匹配的類和類成員。 列表列印到標準輸出或給定檔案。 該列表可用於驗證是否真正找到預期的類成員,特別是如果你使用萬用字元。 例如,你可能希望列出所有應用程式或所有保留的小程式。

壓縮

-dontshrink
指定不壓縮的類檔案。
-printusage {filename}
指定需要列出dead code的類檔案。 可以列出應用程式的未使用的程式碼。 只適用於壓縮。
-whyareyoukeeping {class_specification}
指定列印關於給定類和類成員在壓縮步驟中keep的原因的詳細資訊。 只適用於壓縮。

優化

-dontoptimize
指定不優化輸入類檔案。 預設情況下,啟用優化; 所有方法都以位元組碼級別進行了優化。

-optimizations optimization_filter
指定要在更細粒度的級別啟用和禁用的優化。 僅適用於優化。 這是一個專家選項。

-optimizationpasses n
指定要執行的優化通過次數。 預設情況下,執行單程。 多次通過可能會導致進一步的改進。 如果在優化通過後沒有找到改進,則優化結束。 僅適用於優化。

-assumenosideeffects class_specification
指定沒有任何副作用的方法(可能返回值除外)。 在優化步驟中,ProGuard會刪除對這些方法的呼叫,如果可以確定不使用返回值。例如,你可以指定方法System.out(),以便 用該選項來刪除日誌記錄程式碼。 注意,該選項 僅適用於優化。 除非你很清楚這個方法的作用,否則請勿使用這個選項!

-allowaccessmodification
指定在處理過程中可以擴充套件類和類成員的訪問修飾符。 這可以改善優化步驟的結果。

-mergeinterfacesaggressively
指定介面可能被合併,即使它們的實現類不實現所有介面方法。 這可以通過減少類的總數來減少輸出的大小。

混淆

-dontobfuscate 不混淆輸入的類檔案
-obfuscationdictionary {filename} 使用給定檔案中的關鍵字作為要混淆方法的名稱
-overloadaggressively 混淆時應用侵入式過載
-useuniqueclassmembernames 確定統一的混淆類的成員名稱來增加混淆
-flattenpackagehierarchy {package_name} 重新包裝所有重新命名的包並放在給定的單一包中
-repackageclass {package_name} 重新包裝所有重新命名的類檔案中放在給定的單一包中
-dontusemixedcaseclassnames 混淆時不會產生形形色色的類名
-keepattributes {attribute_name,…} 保護給定的可選屬性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses.
-renamesourcefileattribute {string} 設定原始檔中給定的字串常量

在配置過程中可以使用萬用字元,關於萬用字元的說明如下,

萬用字元 說明
? 匹配任何單字元。
* 匹配除了包分隔符或目錄分隔符之外的任何字元。
** 匹配任何字元的名稱

自定義要保留的程式碼

通常來說,預設 ProGuard 配置檔案 (proguard-android.txt) 足以滿足需要,ProGuard 會移除所有(並且只會移除)未使用的程式碼。但是對許多情況進行正確分析時,可能會移除有用的程式碼。
以下幾種情況下它可能錯誤移除有用程式碼:

  • 當應用引用的類只來自 AndroidManifest.xml 檔案時
  • 當應用呼叫的方法來自 Java 原生介面 (JNI) 時
  • 當應用在執行時(例如使用反射或自檢)操作程式碼時
  • 當使用Parcelable的子類和Creator靜態成員變數時
  • 當使用GSON、fastjson等框架時,所寫的JSON物件類如果進行混淆會造成無法將JSON解析成對應的物件
  • 當使用第三方開源庫或者引用其他第三方的SDK包時
  • 當有用到WEBView的JS呼叫時

在進行應用測試時應該能夠發現這些因不當移除而導致的錯誤,具體也可檢視 usage.txt 的內容來檢查移除了哪些程式碼。
例如類似下面這樣的錯誤,我們就需要配置keep這些類不被混淆

10:47:34.635 [ERROR] [system.err] Warning: com.kennyc.view.MultiStateView$1: can't find referenced method 'com.kennyc.view.MultiStateView$ViewState[] values()' in program class com.kennyc.view.MultiStateView$ViewState
10:47:34.636 [ERROR] [system.err] Warning: com.kennyc.view.MultiStateView$1: can't find referenced field 'com.kennyc.view.MultiStateView$ViewState LOADING' in program class com.kennyc.view.MultiStateView$ViewState

配置內容如下即可解決問題:

-keep class com.kennyc.**

在AndroidMainfest中的類不會混淆,四大元件和Application的子類和Framework層下所有的類預設不會進行混淆,android提供的預設配置中都有。以下就是Android SDK中預設的ProGuard配置,即proguard-android.txt的內容

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html

-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>(...);
}

列舉一些庫的混淆配置

##---------------Begin: proguard configuration for Twitter  ----------
-dontwarn com.squareup.okhttp.**
-dontwarn com.google.appengine.api.urlfetch.**
-dontwarn rx.**
-dontwarn retrofit.**
-keepattributes Signature
-keepattributes *Annotation*
-keep class com.squareup.okhttp.** { *; }
-keep interface com.squareup.okhttp.** { *; }
-keep class retrofit.** { *; }
-keepclasseswithmembers class * {
    @retrofit.http.* <methods>;
}
##---------------End: proguard configuration for Twitter  ----------
##---------------Begin: proguard configuration for Gson  ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# For using GSON @Expose annotation
-keepattributes *Annotation*

# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }

# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }

# Prevent proguard from stripping interface information from TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
##---------------End: proguard configuration for Gson  ----------
##---------------Begin: proguard configuration for Glide  ----------
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
  **[] $VALUES;
  public *;
}
##---------------End: proguard configuration for Glide  ----------
##---------------Begin: proguard configuration for EventBus  ----------
-keepattributes *Annotation*
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

# Only required if you use AsyncExecutor
#-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
#    <init>(java.lang.Throwable);
#}
##---------------End: proguard configuration for EventBus  ----------
##---------------Begin: proguard configuration for Paper  ----------
#1.Keep data classes:
-keep class com.bloomsky.android.model.** { *; }
#2.alternatively you can implement Serializable for all your data classes and keep all of them using:
#-keep class * implements java.io.Serializable { *; }
##---------------End: proguard configuration for Paper  ----------
##---------------Begin: proguard configuration for retrofit2  ----------
# Platform calls Class.forName on types which do not exist on Android to determine platform.
-dontnote retrofit2.Platform
# Platform used when running on Java 8 VMs. Will not be used at runtime.
-dontwarn retrofit2.Platform$Java8
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
-keepattributes Exceptions
##---------------End: proguard configuration for retrofit2  ----------

移除除錯log程式碼的配置

移除Log類列印各個等級日誌的程式碼,打正式包的時候可以做為禁log使用,這裡可以作為禁止log列印的功能使用,另外的一種實現方案是通過BuildConfig.DEBUG的變數來控制

-assumenosideeffects class android.util.Log {  
    public static *** v(...);  
    public static *** i(...);  
    public static *** d(...);  
    public static *** w(...);  
    public static *** e(...);  
} 

恢復混淆前的堆疊資訊

當混淆後的程式碼輸出一個堆疊資訊時,方法名是不可識別的,這使得除錯變得很困難,甚至是不可能的。幸運的是,當ProGuard執行時,它都會輸出一個/build/outputs/mapping/release/mapping.txt檔案,而這個檔案中包含了原始的類,方法和欄位名被對映成的混淆名字。
retrace.bat指令碼(Window)或retrace.sh指令碼(Linux,Mac OS X)可以將一個被混淆過的堆疊跟蹤資訊還原成一個可讀的資訊。它位於/tools/proguard資料夾中。執行retrace工具的語法如下:
retrace.bat|retrace.sh [-verbose] mapping.txt []
例如:
./retrace.sh -verbose mapping.txt obfuscated_trace.txt
如果你沒有指定,retrace工具會從標準輸入讀取。