關於proguard,你須要知道的所有

proguard流程javascript

proguard分爲4個步驟:

  • 壓縮(shrink)
    移除未使用的類、方法、字段等;
  • 優化(optimize)
    優化字節碼、簡化代碼等操做;
  • 混淆(obfuscate)
    使用簡短的、無心義的名稱重全名類名、方法名、字段等;
  • 預校驗(preverify)
    爲class添加預校驗信息。

1、4個步驟中的常量配置

1. 壓縮(shrink)

-dontshrink
聲明不進行壓縮操做,默認狀況下,除了-keep配置(下詳)的類及其直接或間接引用到的類,都會被移除。java

2. 優化(optimize)

-dontoptimize
不對class進行優化,默認開啓優化。
注意:因爲優化會進行類合併、內聯等多種優化,-applymapping可能沒法徹底應用,需使用熱修復的應用,建議使用此配置關閉優化。android

-optimizationpassesn
執行優化的次數,默認1次,屢次能達到更好的優化效果。web

-optimizationsoptimization_filter
優化配置,可進行字段優化、內聯、類合併、代碼簡化、算法指令精簡等操做。正則表達式

#只進行 移除未使用的局部變量、算法指令精簡
-optimizations code/removal/variable,code/simplification/arithmetic

#進行除 算法指令精簡、字段、類合併外的全部優化
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

3. 混淆(obfuscate)

-dontobfuscate
不進行混淆,默認開啓混淆。除-keep指定的類及成員外,都會被替換成簡短、隨機的名稱,以達到混淆的目的。算法

-applymappingfilename
根據指定的mapping映射文件進行混淆。數組

-obfuscationdictionaryfilename
指定字段、方法名的混淆字典,默認狀況下使用abc等字母組合,好比根據本身的喜愛指定中文、特殊字符進行混淆命名。app

-classobfuscationdictionaryfilename
指定類名混淆字典。ide

-packageobfuscationdictionaryfilename
指定包名混淆字典。測試

-useuniqueclassmembernames
指定相同的混淆名稱對應不一樣類的相同成員,不一樣的混淆名稱對應不一樣的類成員。在沒有指定這個選項時,不一樣類的不一樣方法均可能映射到a,b,c。
有一種狀況,好比兩個不一樣的接口,擁有相同的方法簽名,在沒有指定這個選項時,這兩個接口的方法可能混淆成不一樣的名稱。但若是新增一個類同時實現這兩個接口,而且利用-applymapping指定以前的mapping映射文件時,這兩個接口的方法必須混淆成相同的名稱,這時就和以前的mapping衝突了。
在某此熱修復場景下須要指定此選項。

-dontusemixedcaseclassnames
指定不使用大小寫混用的類名,默認狀況下混淆後的類名可能同時包含大寫小字母。這在某些對大小寫不敏感的系統(如windowns)上解壓時,可能存在文件被相互覆蓋的狀況。

-keeppackagenames[package_filter]
指定不混淆指定的包名,多個包名能夠用逗號分隔,可使用? * **通配符,而且可使用否認符(!)。

-keepattributes[attribute_filter]
指定保留屬性,多個屬性能夠用多個-keepattributes配置,也能夠用逗號分隔,可使用? * **通配符,而且可使用否認符(!)。
好比,在混淆ibrary庫時,應該至少keep Exceptions, InnerClasses, Signature;若是在追蹤代碼,還須要keep符號表;使用到註解時也須要keep。

-keepattributes Exceptions,InnerClasses,Signature
-keepattributes SourceFile,LineNumberTable
-keepattributes *Annotation*

-keepparameternames
指定keep已經被keep的方法的參數類型和參數名稱,在混淆library庫時很是有用,可供IDE幫助用戶進行信息提示和代碼自動填充。

4. 預校驗(preverify)

-dontpreverify
指定不對class進行預校驗,默認狀況下,在編譯版本爲micro或者1.6或更高版本時是開啓的。但編譯成Android版本時,預校驗是沒必要須的,配置這個選項能夠節省一點編譯時間。
(Android會把class編譯成dex,並對dex文件進行校驗,對class進行預校驗是多餘的。)

2、keep配置

-keep[,modifier,...] class_specification
指定類及類成員做爲代碼入口,保護其不被proguard,如:

-keep class com.rush.Test
-keep interface com.rush.InterfaceTest
-keep class com.rush.** {
    <init>;
    public <fields>;
    public <methods>;
    public *** get*();
    void set*(***);
}
  • class表示keep類或接口
  • interface僅表示keep接口

類名 通配符以下:

| 通配符 | 含義 |
| --- |
| ? | 匹配單個字符,包名分隔符(.)除外 |
| * | 匹配除(.)外的任意字符 |
| ** | 匹配任意字符(包含.),如com.rush.**匹配com.rush包下的全部類及其全部子包的類。 |

字段和方法 通配符以下:

| 通配符 | 含義 |
| --- |
| <init> | 匹配全部構造方法 |
| <fields> | 匹配全部字段 |
| <methods> | 匹配全部方法 |
| ? | 匹配單個字符,包名分隔符(.)除外 |
| * | 匹配除(.)外的任意字符 |

類型 通配符以下:

| 通配符 | 含義 |
| --- |
| % | 匹配原始類型,如int, boolean等 |
| ? | 匹配任意單個字符 |
| * | 匹配除包名分隔符(.)外的任意字符 |
| ** | 匹配任意字符,包括包名分隔符(.) |
| *** | 匹配任意類型(原始類型、非原始類型、數組或非數組類型)|
| ... | 匹配任意參數個數,任意參數類型 |

其中類配置完整定義以下,其中[]表示可選:

[@annotationtype] [[!]public|final|abstract|@ ...] [!]interface|class|enum classname
    [extends|implements [@annotationtype] classname]
[{
    [@annotationtype] [[!]public|private|protected|static|volatile|transient ...] <fields> |
                                                                      (fieldtype fieldname);
    [@annotationtype] [[!]public|private|protected|static|synchronized|native|abstract|strictfp ...] <methods> |
                                                                                           <init>(argumenttype,...) |
                                                                                           classname(argumenttype,...) |
                                                                                           (returntype methodname(argumenttype,...));
    [@annotationtype] [[!]public|private|protected|static ... ] *;
    ...
}]

keep過於簡單粗暴,proguard提供了6種不一樣的配置:

| 保留 | 防止被移除或重命名 | 防止被重命名(未使用的會被移除) |
| --- | --- |
| 類和類成員 | -keep | -keepnames |
| 僅類成員 | -keepclassmembers | -keepclassmembernames |
| 如類含有某成員,保留類及其成員 | -keepclasseswithmembers | -keepclasseswithmembernames |

3、其它經常使用配置

-verbose
指定在混淆過程當中輸出更多信息,配置這個選項後,在遇到異常時,將輸出完整的堆棧,而不只僅是異常消息。

-dontnote[class_filter]
指定不輸出潛在的錯誤或者遺漏,好比拼寫錯誤或者缺失了有用的信息。class_filter是一個正則表達式,匹配到類將不輸出這些信息。

-dontwarn[class_filter]
指定一組類,不警告這些類中找不到引用或其它重要的問題。這個選項是很危險的,好比,找不到引用的錯誤可能致使代碼不能正常work。
(在引用一些存在警告的jar包,這個選項比較有用。)

-ignorewarnings
指定輸出因此警告信息,但繼續進行混淆。同上一選項,慎用。

-printconfiguration[filename]
指定輸出整個過程當中的全部配置,輸出到標準輸出流或者指定文件中。這有時候在調度配置時有用。

-dump[filename]
指定在任一處理過程後,輸出class文件的結構,能夠輸出到標準輸出流或者指定文件中。

4、proguard配置示例

4.1 Android默認推薦配置

在IDE自動生成的project.properties文件中,有這樣一行:

#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

Android Studio默認生成的build.gradle文件有以下配置:

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

其中getDefaultProguardFile('proguard-android.txt')獲取的也是tools/proguard/proguard-android.txt。下面看一下這個文件的配置:

# 不使用大小寫混合類名
-dontusemixedcaseclassnames
# 不路過引用庫中的非public類
-dontskipnonpubliclibraryclasses
# 輸出更多信息
-verbose

# 不進行優化
-dontoptimize
# 不進行預校驗
-dontpreverify

# keep註解
-keepattributes *Annotation*
#keep google license服務接口
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# keep native方法及其所屬類
-keepclasseswithmembernames class * {
    native <methods>;
}

# keep自定義view的get/set方法
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# keep繼續自Activity中全部包含public void *(android.view.View)簽名的方法,如onClick
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# keep枚舉中的values和valueOf方法
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# keep Parcelable的CREATOR成員
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

# keep R文件的靜態字段
-keepclassmembers class **.R$* {
    public static <fields>;
}

# 不輸出support包中的警告
-dontwarn android.support.**

4.2 一個典型library庫的配置

示例引用自官方文檔samples

# 保存mapping映射文件到out.map
-printmapping out.map 

# keep已keep方法的參數類型及參數名稱
-keepparameternames 
# 這個配置未弄清楚,待測試
-renamesourcefileattribute SourceFile 
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
                SourceFile,LineNumberTable,*Annotation*,EnclosingMethod 

# keep全部類的protected成員
-keep public class * { 
      public protected *; 
} 

# keep在jdk 1.2中編譯器插入的代碼
-keepclassmembernames class * { 
    java.lang.Class class$(java.lang.String); 
    java.lang.Class class$(java.lang.String, boolean); 
} 

# keep native方法
-keepclasseswithmembernames,includedescriptorclasses class * { 
    native <methods>; 
} 

# keep枚舉中的values和valueOf方法
-keepclassmembers,allowoptimization enum * { 
    public static **[] values(); 
    public static ** valueOf(java.lang.String); 
} 

# keep系列化相關方法
-keepclassmembers class * implements java.io.Serializable { 
    static final long serialVersionUID; 
    private static final java.io.ObjectStreamField[] serialPersistentFields; 
    private void writeObject(java.io.ObjectOutputStream); 
    private void readObject(java.io.ObjectInputStream); 
    java.lang.Object writeReplace(); 
    java.lang.Object readResolve(); 
}

4.3 一個典型Android App的配置

示例引用自官方文檔samples

-dontpreverify 
-repackageclasses '' 
-allowaccessmodification 
# 不優化算法指令
-optimizations !code/simplification/arithmetic 
-keepattributes *Annotation* 

# keep繼承自系統組件的類
-keep public class * extends android.app.Activity 
-keep public class * extends android.app.Application 
-keep public class * extends android.app.Service 
-keep public class * extends android.content.BroadcastReceiver 
-keep public class * extends android.content.ContentProvider
 
# keep自定義view及其構造方法、set方法
-keep public class * extends android.view.View { 
      public <init>(android.content.Context); 
      public <init>(android.content.Context, android.util.AttributeSet); 
      public <init>(android.content.Context, android.util.AttributeSet, int); 
      public void set*(...); 
} 

-keepclasseswithmembers class * { 
    public <init>(android.content.Context, android.util.AttributeSet); 
} 

-keepclasseswithmembers class * { 
    public <init>(android.content.Context, android.util.AttributeSet, int); 
} 

-keepclassmembers class * extends android.content.Context { 
    public void *(android.view.View); 
    public void *(android.view.MenuItem); 
} 

-keepclassmembers class * implements android.os.Parcelable { 
    static ** CREATOR; 
} 

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

# keep javascript註釋的方法,使用到webview js回調方法的須要添加此配置
-keepclassmembers class * { 
    @android.webkit.JavascriptInterface <methods>; 
}

5、關於反射

並非全部會被反射引用的類都必須keep,在progurad過程當中能直接分析到引用的類會被proguard作相應的處理:

# Class.forName的類名"SomeClass"被混淆後自動替換
Class.forName("SomeClass")
SomeClass.class
# 如下字段和方法名都會在被混淆後自動替換
SomeClass.class.getField("someField")
SomeClass.class.getDeclaredField("someField")
SomeClass.class.getMethod("someMethod", new Class[] {})
SomeClass.class.getMethod("someMethod", new Class[] { A.class })
SomeClass.class.getMethod("someMethod", new Class[] { A.class, B.class })
SomeClass.class.getDeclaredMethod("someMethod", new Class[] {})
SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class })
SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class, B.class })
AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")
AtomicLongFieldUpdater.newUpdater(SomeClass.class, "someField")
AtomicReferenceFieldUpdater.newUpdater(SomeClass.class, SomeType.class, "someField")

寫個demo驗證下:

Class<?> clazz = Class.forName("com.rush.test.SimpleClass1");
clazz.getDeclaredMethod("Test1");
SimpleClass2.class.getDeclaredField("mTestField");
SimpleClass2.class.getDeclaredMethod("Test2");

對以上代碼編譯並proguard,結果以下:

Class.forName("com.rush.a.a").getDeclaredMethod("Test1", new Class[0]);
b.class.getDeclaredField("a");
b.class.getDeclaredMethod("a", new Class[0]);
  • 經過Class.forName反射的class com.rush.test.SimpleClass1"被自動替換成了"com.rush.a.a";
  • 但經過Class.forName獲取的class再去反射方法沒有正確處理;
  • 經過完整class.getDeclaredField或者getDeclaredMethod反射時可以把字段名和方法名自動替換掉。

從結果看,反射並非你們想像的那樣必須keep,proguard能自動分析到引用的狀況都能正確處理。但有些類是在配置文件裏配置,或者動態拼接類名反射的,這些狀況須要作好keep。

爲了問題追蹤的方便,建議全部會被反射引用的代碼和library public接口都作好keep。

6、關於proguard配置的一些建議

  • 全部會被反射引用的類都作好keep(建議,雖然有些反射能被正確處理)。
    如native方法,四大組件,接口model,枚舉,序列化類等。

  • 只keep必須保留的內容,不要過分keep

  • 使用熱修復的App,添加-dontoptimize配置

7、參考文檔:

做者:rushjs 連接:https://www.jianshu.com/p/d768f6d1d93b 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

相關文章
相關標籤/搜索