Android 項目的代碼混淆,Android proguard 使用說明
簡單介紹
Java代碼是非常easy反編譯的。
為了非常好的保護Java源碼,我們往往會對編譯好的class文件進行混淆處理。
ProGuard是一個混淆代碼的開源項目。它的主要作用就是混淆,當然它還能對字節碼進行縮減體積、優化等,但那些對於我們來說都算是次要的功能。
官網地址:http://proguard.sourceforge.net/
原理
Java 是一種跨平臺的、解釋型語言,Java 源碼編譯成中間”字節碼”存儲於 class 文件裏。因為跨平臺的須要,Java 字節碼中包括了非常多源碼信息,如變量名、方法名,而且通過這些名稱來訪問變量和方法,這些符號帶有很多語義信息,非常easy被反編譯成 Java 源碼。
為了防止這樣的現象,我們能夠使用 Java 混淆器對 Java 字節碼進行混淆。
混淆就是對公布出去的程序進行又一次組織和處理,使得處理後的代碼與處理前代碼完畢同樣的功能,而混淆後的代碼非常難被反編譯,即使反編譯成功也非常難得出程序的真正語義。被混淆過的程序代碼,仍然遵照原來的檔案格式和指令集,執行結果也與混淆前一樣。僅僅是混淆器將代碼中的全部變量、函數、類的名稱變為簡短的英文字母代號。在缺乏對應的函數名和程序凝視的況下,即使被反編譯。也將難以閱讀。同一時候混淆是不可逆的,在混淆的過程中一些不影響正常執行的信息將永久丟失。這些信息的丟失使程序變得更加難以理解。
混淆器的作用不僅僅是保護代碼。它也有精簡編譯後程序大小的作用。因為以上介紹的縮短變量和函數名以及丟失部分信息的原因。 編譯後 jar 文件體積大約能降低25% ,這對當前費用較貴的無線網絡傳輸是有一定意義的。
語法
-include {filename} 從給定的文件裏讀取配置參數 -basedirectory {directoryname} 指定基礎文件夾為以後相對的檔案名稱 -injars {class_path} 指定要處理的應用程序jar,war,ear和文件夾 -outjars {class_path} 指定處理完後要輸出的jar,war,ear和文件夾的名稱 -libraryjars {classpath} 指定要處理的應用程序jar,war,ear和文件夾所須要的程序庫文件 -dontskipnonpubliclibraryclasses 指定不去忽略非公共的庫類。 -dontskipnonpubliclibraryclassmembers 指定不去忽略包可見的庫類的成員。 保留選項 -keep {Modifier} {class_specification} 保護指定的類文件和類的成員 -keepclassmembers {modifier} {class_specification} 保護指定類的成員。假設此類受到保護他們會保護的更好 -keepclasseswithmembers {class_specification} 保護指定的類和類的成員,但條件是全部指定的類和類成員是要存在。 -keepnames {class_specification} 保護指定的類和類的成員的名稱(假設他們不會壓縮步驟中刪除) -keepclassmembernames {class_specification} 保護指定的類的成員的名稱(假設他們不會壓縮步驟中刪除) -keepclasseswithmembernames {class_specification} 保護指定的類和類的成員的名稱,假設全部指定的類成員出席(在壓縮步驟之後) -printseeds {filename} 列出類和類的成員-keep選項的清單,標準輸出到給定的文件 壓縮 -dontshrink 不壓縮輸入的類文件 -printusage {filename} -dontwarn 假設有警告也不終止 -whyareyoukeeping {class_specification} 優化 -dontoptimize 不優化輸入的類文件 -assumenosideeffects {class_specification} 優化時假設指定的方法。沒有不論什麽副作用 -allowaccessmodification 優化時同意訪問並改動有修飾符的類和類的成員 混淆 -dontobfuscate 不混淆輸入的類文件 -printmapping {filename} -applymapping {filename} 重用映射添加混淆 -obfuscationdictionary {filename} 使用給定文件裏的keyword作為要混淆方法的名稱 -overloadaggressively 混淆時應用侵入式重載 -useuniqueclassmembernames 確定統一的混淆類的成員名稱來添加混淆 -flattenpackagehierarchy {package_name} 又一次包裝全部重命名的包並放在給定的單一包中 -repackageclass {package_name} 又一次包裝全部重命名的類文件裏放在給定的單一包中 -dontusemixedcaseclassnames 混淆時不會產生形形色色的類名 -keepattributes {attribute_name,...} 保護給定的可選屬性,比如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses. -renamesourcefileattribute {string} 設置源文件裏給定的字符串常量
Android Eclipse開發環境與ProGuard
在Android 2.3曾經。混淆Android代碼僅僅能手動加入proguard來實現代碼混淆,非常不方便。
而2.3以後,Google已經將這個工具加入到了SDK的工具集裏。詳細路徑:SDK\tools\proguard。當創建一個新的Android工程時。在工程文件夾的根路徑下,會出現一個proguard的配置文件proguard.cfg。也就是說。我們能夠通過簡單的配置,在我們的elipse工程中直接使用ProGuard混淆Android工程。
詳細混淆的步驟非常簡單。首先,我們須要在工程描寫敘述文件project.properties中。加入一句話,啟用ProGuard。例如以下所看到的:
# This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must be checked in Version Control Systems. # # To customize properties used by the Ant build system edit # "ant.properties", and override values to adapt the script to your # project structure. # # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. target=android-19
這樣,Proguard就能夠使用了。當我們正常通過Android Tools導出Application Package時(或者使用ant執行release打包)。Proguard就會自己主動啟用。優化混淆你的代碼。
導出成功後。你能夠反編譯看看混淆的效果。一些類名、方法名和變量名等。都變成了一些無意義的字母或者數字。證明混淆成功!
實例(proguard 文件代碼解讀)
-optimizationpasses 7 #指定代碼的壓縮級別 0 - 7 -dontusemixedcaseclassnames #是否使用大寫和小寫混合 -dontskipnonpubliclibraryclasses #假設應用程序引入的有jar包,而且想混淆jar包裏面的class -dontpreverify #混淆時是否做預校驗(可去掉加快混淆速度) -verbose #混淆時是否記錄日誌(混淆後生產映射文件 map 類名 -> 轉化後類名的映射 -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* #淆採用的算法 -keep public class * extends android.app.Activity #全部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 com.android.vending.licensing.ILicensingService #指定詳細類不要去混淆 -keepclasseswithmembernames class * { native <methods>; #保持 native 的方法不去混淆 } -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.app.Activity { public void *(android.view.View); #保持指定規則的方法不被混淆(Android layout 布局文件裏為控件配置的onClick方法不能混淆) } -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*(...); } -keepclassmembers enum * { #保持枚舉 enum 不被混淆 public static **[] values(); public static ** valueOf(java.lang.String); } -keep class * implements android.os.Parcelable { #保持 Parcelable 不被混淆(aidl文件不能去混淆) public static final android.os.Parcelable$Creator *; } -keepnames class * implements java.io.Serializable #須要序列化和反序列化的類不能被混淆(註:Java反射用到的類也不能被混淆) -keepclassmembers class * implements java.io.Serializable { #保護實現接口Serializable的類中。指定規則的類成員不被混淆 static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; !static !transient <fields>; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); } -keepattributes Signature #過濾泛型(不寫可能會出現類型轉換錯誤,普通情況把這個加上就是了) -keepattributes *Annotation* #假如項目中實用到註解,應加入這行配置 -keep class **.R$* { *; } #保持R文件不被混淆,否則。你的反射是獲取不到資源id的 -keep class **.Webview2JsInterface { *; } #保護WebView對HTML頁面的API不被混淆 -keepclassmembers class * extends android.webkit.WebViewClient { #假設你的項目中用到了webview的復雜操作 ,最好加入 public void *(android.webkit.WebView,java.lang.String,android.graphics.Bitmap); public boolean *(android.webkit.WebView,java.lang.String); } -keepclassmembers class * extends android.webkit.WebChromeClient { #假設你的項目中用到了webview的復雜操作 。最好加入 public void *(android.webkit.WebView,java.lang.String); } #對WebView的簡單說明下:經過實戰檢驗,做騰訊QQ登錄,假設引用他們提供的jar。若不加防止WebChromeClient混淆的代碼,oauth認證無法回調,反編譯基代碼後可看到他們實用到WebChromeClient,加入此代碼就可以。 -keepclassmembernames class com.cgv.cn.movie.common.bean.** { *; } #轉換JSON的JavaBean,類成員名稱保護。使其不被混淆 ################################################################## # 以下都是項目中引入的第三方 jar 包。第三方 jar 包中的代碼不是我們的目標和關心的對象,故而對此我們全部忽略不進行混淆。 ################################################################## -libraryjars libs/android-support-v4.jar -dontwarn android.support.v4.** -keep class android.support.v4.** { *; } -keep interface android.support.v4.** { *; } -keep public class * extends android.support.v4.** -keep public class * extends android.app.Fragment -libraryjars libs/gson-2.3.1-sources.jar -libraryjars libs/gson-2.3.1.jar -dontwarn com.google.gson.** -keep class sun.misc.Unsafe { *; } -keep class com.google.gson.** { *; } -libraryjars libs/alipaySDK-20150602.jar -dontwarn com.alipay.** -dontwarn com.ta.utdid2.** -dontwarn com.ut.device.** -keep class com.alipay.** { *; } -keep class com.ta.utdid2.** { *; } -keep class com.ut.device.** { *; } -libraryjars libs/android-async-http-1.4.6.jar -dontwarn com.loopj.android.http.** -keep class com.loopj.android.http.** { *; } -libraryjars libs/baidumapapi_v2_4_1.jar -dontwarn com.baidu.** -keep class com.baidu.** {*; } -keep class assets.** {*; } -keep class vi.com.gdi.bgl.** {*; } -libraryjars libs/libammsdk.jar -dontwarn com.tencent.** -keep class com.tencent.** { *; } -libraryjars libs/locSDK_4.1.jar -dontwarn com.baidu.location.** -keep class com.baidu.location.** { *; } -libraryjars libs/mframework.jar -dontwarn m.framework.** -keep class m.framework.** { *; } -libraryjars libs/mta-sdk-1.6.2.jar -dontwarn com.tencent.stat.** -keep class com.tencent.stat.** { *; } -libraryjars libs/nineoldandroids-library-2.4.0.jar -dontwarn com.nineoldandroids.** -keep class com.nineoldandroids.** { *; } -libraryjars libs/open_sdk_r4889.jar -dontwarn com.tencent.** -keep class com.tencent.** { *; } -libraryjars libs/ShareSDK-Core-2.5.9.jar -dontwarn cn.sharesdk.framework.** -keep class cn.sharesdk.framework.** { *; } -libraryjars libs/ShareSDK-ShortMessage-2.5.9.jar -dontwarn cn.sharesdk.system.text.** -keep class cn.sharesdk.system.text.** { *; } -libraryjars libs/ShareSDK-SinaWeibo-2.5.9.jar -dontwarn cn.sharesdk.sina.weibo.** -keep class cn.sharesdk.sina.weibo.** { *; } -libraryjars libs/ShareSDK-Wechat-2.5.9.jar -dontwarn cn.sharesdk.wechat.friends.** -keep class cn.sharesdk.wechat.friends.** { *; } -libraryjars libs/ShareSDK-Wechat-Core-2.5.9.jar -dontwarn cn.sharesdk.wechat.utils.** -keep class cn.sharesdk.wechat.utils.** { *; } -libraryjars libs/ShareSDK-Wechat-Favorite-2.5.9.jar -dontwarn cn.sharesdk.wechat.favorite.** -keep class cn.sharesdk.wechat.favorite.** { *; } -libraryjars libs/ShareSDK-Wechat-Moments-2.5.9.jar -dontwarn cn.sharesdk.wechat.moments.** -keep class cn.sharesdk.wechat.moments.** { *; } -libraryjars libs/universal-image-loader-1.9.2-SNAPSHOT-with-sources.jar -dontwarn com.nostra13.universalimageloader.** -keep class com.nostra13.universalimageloader.** { *; } -libraryjars libs/weibosdkcore.jar -dontwarn com.sina.weibo.sdk.** -keep class com.sina.weibo.sdk.** { *; }
關於怎樣配置忽略第三方jar,附上一個圖進行說明。
說明一下,第三方jar包中假設有.so文件,不用去理會。引入的第三方jar文件不要混淆。否則可能會報異常。
文件
在release模式下打包apk時會自己主動執行ProGuard,這裏的release模式指的是通過ant release命令或eclipse project->android tools->export signed(unsigned) application package生成apk。
在debug模式下為了更快調試並不會調用proguard。
假設是ant命令打包apk,proguard信息文件會保存於工程代碼下的/bin/proguard文件夾內;
假設用eclipse export命令打包,會在/proguard文件夾內。當中包括以下文件:
mapping.txt
表示混淆前後代碼的對比表,這個文件非常重要。假設你的代碼混淆後會產生bug的話,log提示中是混淆後的代碼。希望定位到源碼的話就能夠依據mapping.txt反推。
每次公布都要保留它方便該版本號出現故障時調出日誌進行排查,它能夠依據版本號號或是公布時間命名來保存或是放進代碼版本號控制中。
dump.txt
描寫敘述apk內全部class文件的內部結構。
seeds.txt
列出了沒有被混淆的類和成員。
usage.txt
列出了源碼中被刪除在apk中不存在的代碼。
不能混淆的代碼
顧名思義,不能混淆代碼假設被混淆了,就會出現錯誤。
1、須要反射的代碼
2、系統接口
3、Jni接口
4、須要序列號和反序列化的代碼(即實現Serializable接口的JavaBean)
5、與服務端進行元數據交互的JavaBean(JSON、XML中對應的類)
……
常見錯誤
1) Proguard returned with error code 1. See console
> 更新proguard版本號
> android-support-v4 不進行混淆
> 加入缺少對應的庫
2) 使用gson包解析數據時,出現 missing type parameter 異常
> 在 proguard-project.txt 中加入
-dontobfuscate
-dontoptimize
> 在 proguard-project.txt 中加入
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# 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.** { *; }
3) 類型轉換錯誤
> 在 proguard-project.txt 中加入
-keepattributes Signature
4) 空指針異常
> 混淆過濾掉相關類與方法
5) java.lang.reflect.UndeclaredThrowableException
> -keep interface com.dev.impl.**
6) Error: Unable to access jarfile ..libproguard.jar
> 路徑問題
7) java.lang.NoSuchMethodError
> 這也是最常見的問題。因為找不到相關方法,方法被混淆了。混淆過濾掉相關方法便可。
----------------------
(完)
Android 項目的代碼混淆,Android proguard 使用說明