轉載請標明出處:http://blog.csdn.net/shensky711/article/details/52770993 本文出自: 【HansChen的博客】
- ProGuard簡介和工作流程
- PrgGuard環境配置和使用
- PrgGuard配置文件使用
- Entry points的概念
- 會用到的指令參數說明
- 基本指令
- -basedirectory directoryname
- -injars class_path
- -outjars class_path
- -libraryjars class_path
- -skipnonpubliclibraryclasses
- dontskipnonpubliclibraryclasses
- -dontskipnonpubliclibraryclassmembers
- -keepdirectories directory_filter
- -target version
- -forceprocessing
- -keep modifier class_specification
- -keepclassmembers modifier class_specification
- -keepclasseswithmembers modifier class_specification
- -keepnames class_specification
- -keepclassmembernames class_specification
- -keepclasseswithmembernames class_specification
- -printseeds filename
- -dontshrink
- -printusage filename
- -dontoptimize
- -optimizations
- -optimizationpasses n
- -assumenosideeffects class_specification
- dontobfuscate
- -printmapping filename
- -applymapping filename
- -obfuscationdictionary filename
- -classobfuscationdictionary filename
- -packageobfuscationdictionary filename
- useuniqueclassmembernames
- -dontusemixedcaseclassnames
- -keeppackagenames package_filter
- -flattenpackagehierarchy package_name
- -repackageclasses package_name
- -keepattributes attribute_filter
- -dontpreverify
- -verbose
- -dontnote class_filter
- -dontwarn class_filter
- -ignorewarnings
- -printconfiguration filename
- -dump filename
- 反射的處理
- PrgGuard的基本使用demo
ProGuard簡介和工作流程
ProGuard能夠通過壓縮、優化、混淆、預檢等操作,檢測並刪除未使用的類,字段,方法和屬性,分析和優化字節碼,使用簡短無意義的名稱來重命名類,字段和方法。從而使代碼更小、更高效、更難進行逆向工程。
上圖就是ProGuard的工作流程,分別會經過四個階段:
壓縮(Shrink)
:在壓縮處理這一步中,用於檢測和刪除沒有使用的類,字段,方法和屬性優化(Optimize)
:在優化處理這一步中,對字節碼進行優化,並且移除無用指令混淆(Obfuscate)
:在混淆處理這一步中,使用a,b,c等無意義的名稱,對類,字段和方法進行重命名預檢(Preveirfy)
:在預檢這一步中,主要是在Java平台上對處理后的代碼進行預檢
以上四個步驟都是可選的,我們可以通過配置腳本來決定其中的哪幾個步驟。比如我們可以配置只壓縮和混淆,不進行優化,不進行預檢。 ProGuard的官網有使用指導:http://proguard.sourceforge.net/
PrgGuard環境配置和使用
運行PrgGuard需要以下依賴:
- proguard.jar或者proguardgui.jar。proguardgui提供了一個簡單的配置界面(如下圖),可以在上面進行配置,而progua.jar則是使用配置文件進行處理
- Java運行環境
如何運行ProGuard
ProGuard可以通過命令行調用,如:
- java -jar proguardgui.jar:啟動圖形化配置界面
- java -jar proguard.jar @config.file –options …:通過配置文件進行ProGuard處理
執行成功后,用jd-gui打開處理后的jar文件:
可以發現,類已經被混淆處理了。
PrgGuard配置文件使用
Entry points的概念
這里,我們引入Entry points的概念。Entry points是在ProGuard過程中不會處理的類或者方法。 在Shrink的步驟中,ProGuard會遞歸遍歷,搜索使用了哪些類和成員,對於沒有使用的類和類成員,就會在壓縮階段丟棄。 接下來在Optimize階段,那些非Entry points的類、方法都會被設置為private、static或者final,沒有使用的參數會被移除,此外,有些方法會被標記為內聯。 在Obfuscate的步驟中,ProGuard會對非Entry points的類和方法進行重命名。
會用到的指令參數說明
Modifier
Includedescriptorclasses
:一般用於保證native方法名,確保方法的參數類型不會重命名,確保方法簽名不會被改變,這樣才能跟native libraries相匹配。Allowshrinking
:允許壓縮Allowoptimization
:允許優化Allowobfuscation
:允許混淆名稱
Class Specifications
Class Specifications是用來描述類和方法的模板,下面是這個模板的格式:
其中,[]中的內容是可選,名稱可以使用通配符,匹配構造函數、匹配成員、匹配方法,詳細請參考:http://proguard.sourceforge.net/manual/usage.html#keepoptionmodifiers
基本指令
-basedirectory directoryname:
在配置文件中出現的相對路徑均是相對於該路徑,如圖:
-injars class_path
指定處理的jar包(或者aars, wars, ears, zips, apks, directories)等,這個jar包里面的類將會被ProGuard處理並寫入到輸出的jar包里去。一般非class文件會不做任何處理直接直接復制到輸出文件中,injars可以多次使用,引入不同的需要處理的文件。 注意,該選項可以指定一個目錄,那么該目錄下所有文件都會被當作input file處理。
-outjars class_path
設置處理完成后的輸出文件路徑
-libraryjars class_path
指定要處理應用程序的jar(或者aars, wars, ears, zips, apks, directories),這些文件不會包含到輸出文件中。一般是指被處理文件所依賴的一些jar包,而那些jar包是不需要被處理以及寫入到輸出文件的。比如:
-skipnonpubliclibraryclasses
忽略library里面非public修飾的類。從而加快ProGuard的處理速度和降低ProGuard的使用內存。一般而言,library里的非公開類是不能被程序使用的,忽略掉這些類可以加快混淆速度。但是請注意,有一種特殊情況:有些人編寫的代碼與類庫中的類在同一個包下,而且對該包的非public類進行了使用,在這種情況下,就不能使用該選項了。
–dontskipnonpubliclibraryclasses
不忽略library里面非public修飾的類
-dontskipnonpubliclibraryclassmembers
指定不忽略非public類里面的成員和方法。ProGuard默認會忽略類庫里非public類里的成員和方法,但是由於一些3.2.5里面的一些原因,應用程序里可能會用到這些,這時候就需要這個選項來指定不忽略它們。
-keepdirectories [directory_filter]
指定要保留在輸出文件內的目錄。默認情況下,目錄會被移除。這會減少輸出文件的大小,但如果你的代碼引用到它們時可能會導致程序崩潰(如mypackage.MyCalss.class.getResource(“”))。這時就需要指定-keepdirectories mypackage。-keepdirectories mydirectory匹配 mydirectory 目錄;-keepdirectories mydirectory/匹配 mydirectory 的直接子目錄;-keepdirectorie mydirectory/*匹配所有子目錄,如果沒有指定過濾器,所有目錄會被保留。
-target version
指定被處理class文件所使用的java版本,可選的有: 1.0, 1.1, 1.2, 1.3, 1.4, 1.5 (or just 5), 1.6 (or just 6), 1.7 (or just 7), or 1.8 (or just 8).
-forceprocessing
強制輸出,即使輸出文件已經是最新狀態
-keep [,modifier,…] class_specification
指定該類以及類的成員和方法為entry points,不被ProGuard混淆。默認只會保持類名不被混淆,如果需要保持類中的方法和成員不被混淆,需要在keep選項中做指定,如:
# 指定Keep類名不被混淆,類中的方法和成員仍然會被混淆 -keep class site.hanschen.proguard.Keep
- 1
- 2
- 1
- 2
# 指定Keep類名不被混淆,且Keep的sayHello方法和成員helloStr不被混淆 -keep class site.hanschen.proguard.Keep { public void sayHello(); private static final java.lang.String helloStr; }
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
-keepclassmembers [,modifier,…] class_specification
指定類的某些成員不被混淆,注意類名還是會被混淆,如:
# 指定Keepclassmembers的sayHello方法不被混淆,注意Keepclassmembers的類名仍然會被混淆 -keepclassmembers class site.hanschen.proguard.Keepclassmembers { public void sayHello(); }
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
和keep
選項的區別是,keepclassmembers
只保持屬性不被混淆,會混淆類名
-keepclasseswithmembers [,modifier,…] class_specification
通過成員來指定哪些類不被混淆處理。保持匹配到的類的類名和指定的方法不被混淆,其他未指定的方法仍然會被混淆。可以用來保留包含main方法的類:
# 通過成員來指定哪些類的類名和成員不被混淆 -keepclasseswithmembers public class site.hanschen.proguard.keepclasseswithmembers { public static void main(java.lang.String[]); }
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
# 如果指定了多條規則,如下,那么必須同時包含 sayHello 和 main 兩個方法的類才會被保留 -keepclasseswithmembers public class site.hanschen.proguard.keepclasseswithmembers { public static void main(java.lang.String[]); public void sayHello(); }
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
-keepnames class_specification
-keep,allowshrinking class_specification的別名,允許該類被壓縮,未被使用的元素將會在壓縮階段被移除,此選項會保持對應的類名和指定的成員不被混淆(未指定的成員依然會被混淆)。比如指定了 sayHello 這個方法
# 指定 keepnames 類名以及 sayHello 方法不被混淆,但需要注意的是,若 sayHello 方法未被使用,會在壓縮階段被移除掉 -keepnames public class site.hanschen.proguard.keepnames { public void sayHello(); }
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
-keepclassmembernames class_specification
-keepclasseswithmembers,allowshrinking class_specification的別名,但是未使用的類和成員可能會在壓縮階段被移除
-keepclasseswithmembernames class_specification
-keepclasseswithmembers,allowshrinking class_specification的別名,未使用的類和成員可能會在壓縮階段被移除
-printseeds [filename]
把keep匹配的類和方法輸出到文件中,可以用來驗證自己設定的規則是否生效.
-dontshrink
指定不進行壓縮.
-printusage [filename]
把沒有使用的代碼輸出到文件中,方便查看哪些代碼被壓縮丟棄了。
-dontoptimize
指定不對輸入代碼進行優化處理。優化選項是默認打開的。
-optimizations
指定混淆是采用的算法,后面的參數是一個過濾器,這個過濾器是谷歌推薦的算法,一般不做更改
-optimizationpasses n
指定優化的級別,在0-7之間,默認為5.
-assumenosideeffects class_specification
可以指定移除哪些方法沒有副作用,如在Android開發中,如想在release版本可以把所有log輸出都移除,可以配置:
# 在優化階段移除相關方法的調用 -assumenosideeffects class android.util.Log { public static boolean isLoggable(java.lang.String, int); public static int v(...); public static int i(...); public static int w(...); public static int d(...); public static int e(...); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
那么所有log代碼將會在優化階段被去除。
–dontobfuscate
指定不進行混淆
-printmapping [filename]
生成map文件,記錄混淆前后的名稱對應關系,注意,這個比較重要,因為混淆后運行的名稱會變得不可讀,只有依靠這個map文件來還原。
-applymapping filename
主要是用來維持兩次混淆公用一份mapping,確保相同的代碼前后兩次混淆后是一樣的命名
-obfuscationdictionary filename
指定外部模糊字典
-classobfuscationdictionary filename
指定class模糊字典.
-packageobfuscationdictionary filename
指定package模糊字典
–useuniqueclassmembernames
類和成員混淆的時候,使用唯一的名字
-dontusemixedcaseclassnames
不使用大小寫混合類名,注意,windows用戶必須為ProGuard指定該選項,因為windows對文件的大小寫是不敏感的,也就是比如a.java和A.java會認為是同一個文件。如果不這樣做並且你的項目中有超過26個類的話,那么ProGuard就會默認混用大小寫文件名,導致class文件相互覆蓋。
-keeppackagenames [package_filter]
保持packagename 不混淆
-flattenpackagehierarchy [package_name]
指定重新打包,所有包重命名,這個選項會進一步模糊包名,將包里的類混淆成n個再重新打包到一個個的package中
-repackageclasses [package_name]
將包里的類混淆成n個再重新打包到一個統一的package中,會覆蓋flattenpackagehierarchy選項
-keepattributes [attribute_filter]
混淆時可能被移除下面這些東西,如果想保留,需要用該選項,對於一般注解處理如 -keepattributes Annotation。
attribute_filter :
- Exceptions,
- Signature,
- Deprecated,
- SourceFile,
- SourceDir,
- LineNumberTable,
- LocalVariableTable,
- LocalVariableTypeTable,
- Synthetic,
- #EnclosingMethod,
- RuntimeVisibleAnnotations,
- RuntimeInvisibleAnnotations,
- RuntimeVisibleParameterAnnotations,
- RuntimeInvisibleParameterAnnotations,
- AnnotationDefault.
-dontpreverify
指定不執行預檢
-verbose
把所有信息都輸出,而不僅僅是輸出出錯信息
-dontnote [class_filter]
不輸出指定類的錯誤信息.
-dontwarn [class_filter]
不打印指定類的警告信息
-ignorewarnings
遇到警告的時候,忽略警告繼續執行ProGuard,不建議添加此項。
-printconfiguration [filename]
輸出當前ProGuard所使用的配置
-dump [filename]
指定輸出所處理的類的結構
反射的處理
在代碼中,如果用到了反射,混淆會改變類和成員的名字,導致反射找不到相應的類或者方法,所以開發者在混淆的時候,必須把用到了反射的類保留,不進行混淆。一般而言,使用反射一般會有以下方式,可以搜索代碼,找到相關的類,然后在混淆配置里面進行保留:
- 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”)
# reflectClass類使用了反射,保留該類 -keep class package.reflectClass { *; }
PrgGuard的基本使用demo
#代碼混淆壓縮比,在0~7之間,默認為5,一般不做修改
-optimizationpasses 5 #指定混淆是采用的算法,后面的參數是一個過濾器,這個過濾器是谷歌推薦的算法,一般不做更改 -optimizations !code/simplification/cast,!field/*,!class/merging/* #混合時不使用大小寫混合,混合后的類名為小寫,windows下必須使用該選項 -dontusemixedcaseclassnames #指定不去忽略非公共庫的類和成員 -dontskipnonpubliclibraryclasses -dontskipnonpubliclibraryclassmembers #輸出詳細信息 -verbose #輸出類名->混淆后類名的映射關系 -printmapping map.txt #不做預校驗,preverify是proguard的四個步驟之一,Android不需要preverify,去掉這一步能夠加快混淆速度。 -dontpreverify #保留Annotation不混淆 -keepattributes *Annotation*,InnerClasses #避免混淆泛型 -keepattributes Signature #拋出異常時保留代碼行號 -keepattributes SourceFile,LineNumberTable #保留本地native方法不被混淆 -keepclasseswithmembernames class * { native <methods>; } #保留枚舉類不被混淆 -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } #保留Serializable序列化的類不被混淆 -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(); }
另外,由於Android平台在使用混淆的時候,還要特別注意要添加以下一些配置:
#保留我們使用的四大組件,自定義的Application等等這些類不被混淆
-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 public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
#保留support下的所有類及其內部類
-keep class android.support.** {*;}
#保留R下面的資源
-keep class **.R$* {*;}
#保留在Activity中的方法參數是view的方法,
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
#保留我們自定義控件(繼承自View)不被混淆
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#保留Parcelable序列化類不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#對於帶有回調函數的onXXEvent的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
}
#在我們的app中使用了webView需要進行特殊處理
-keepclassmembers class * extends android.webkit.webViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
public void *(android.webkit.webView, jav.lang.String);
}
# 在app中與HTML5的JavaScript的交互進行特殊處理,如
# package com.ljd.example;
#
# public class JSInterface {
# @JavascriptInterface
# public void callAndroidMethod(){
# // do something
# }
# }
#我們需要確保這些js要調用的原生方法不能夠被混淆,於是我們需要做如下處理
-keepclassmembers class com.ljd.example.JSInterface {
<methods>;
}
#內嵌類經常被混淆,結果在調用的時候就崩潰了,如果需要保留內嵌類,則用以下方法來保留內嵌類,如暴力MyClass里面的內嵌類,$就是用來分割內嵌類和母體的標志
-keep class com.test.MyClass$* {*;}
#-----------以下處理反射類---------------