天天看點

Android 代碼混淆

什麼是代碼混淆

Java 是一種跨平台的、解釋型語言,Java 源代碼編譯成中間”位元組碼”存儲于 class 檔案中。由于跨平台的需要,Java 位元組碼中包括了很多源代碼資訊,如變量名、方法名,并且通過這些名稱來通路變量和方法,這些符号帶有許多語義資訊,很容易被反編譯成 Java 源代碼。為了防止這種現象,我們可以使用 Java 混淆器對 Java 位元組碼進行混淆。

混淆就是對釋出出去的程式進行重新組織和處理,使得處理後的代碼與處理前代碼完成相同的功能。

混淆後的代碼很難被反編譯,即使反編譯成功也很難得出程式的真正語義。

被混淆過的程式代碼,仍然遵照原來的檔案格式和指令集,執行結果也與混淆前一樣,隻是混淆器将代碼中的所有變量、函數、類的名稱變為簡短的英文字母代号,在缺乏相應的函數名和程式注釋的況下,即使被反編譯,也将難以閱讀。

同時,混淆是不可逆的,在混淆的過程中一些不影響正常運作的資訊将永久丢失,這些資訊的丢失使程式變得更加難以了解。

混淆器的作用不僅僅是保護代碼,它也有精簡編譯後程式大小的作用。

由于以上介紹的縮短變量和函數名以及丢失部分資訊的原因, 編譯後 jar 檔案體積大約能減少25% ,這對目前費用較貴的無線網絡傳輸是有一定意義的。

混淆檔案 proguard.cfg 參數詳解 

  1. # 指定代碼的壓縮級别  
  2. -optimizationpasses 5  
  3. # 是否使用大小寫混合  
  4. -dontusemixedcaseclassnames  
  5. # 是否混淆第三方jar  
  6. -dontskipnonpubliclibraryclasses  
  7. # 混淆時是否做預校驗  
  8. -dontpreverify  
  9. # 混淆時是否記錄日志  
  10. -verbose  
  11. # 混淆時所采用的算法  
  12. -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*  
  13. # 保持哪些類不被混淆  
  14. -keep public class * extends android.app.Activity                                 
  15. -keep public class * extends android.app.Application                              
  16. -keep public class * extends android.app.Service                                  
  17. -keep public class * extends android.content.BroadcastReceiver                    
  18. -keep public class * extends android.content.ContentProvider                      
  19. -keep public class * extends android.app.backup.BackupAgentHelper                 
  20. -keep public class * extends android.preference.Preference                        
  21. -keep public class com.android.vending.licensing.ILicensingService      
  22. # 保持 native 方法不被混淆  
  23. -keepclasseswithmembernames class * { native <methods>;}  
  24. # 保持自定義控件類不被混淆  
  25. -keepclasseswithmembers class * {  
  26.  public <init>(android.content.Context, android.util.AttributeSet);  
  27. }  
  28.  public <init>(android.content.Context, android.util.AttributeSet, int);   
  29. -keepclassmembers class * extends android.app.Activity {   
  30.  public void *(android.view.View);  
  31. # 保持枚舉 enum 類不被混淆  
  32. -keepclassmembers enum * {   
  33.  public static **[] values();  
  34.  public static ** valueOf(java.lang.String);  
  35. # 保持 Parcelable 不被混淆  
  36. -keep class * implements android.os.Parcelable {   
  37.  public static final android.os.Parcelable$Creator *;  
  38. # 保持自己定義的類不被混淆  
  39. -keep class MyClass;                                                              

注(本人測試所得):1. 當使用此類配置後,再導出APK時通常會報許多紅色的警告提示,此時,使用-ignorewarnings 忽略提示。

2. 能正常導出成功,卻不能運作的,通過檢視日志,查找是哪些類引用出錯,如以下錯誤資訊 

  1. E/AndroidRuntime(19866): FATAL EXCEPTION: Thread-103  
  2. E/AndroidRuntime(19866): java.lang.ExceptionInInitializerError  
  3. E/AndroidRuntime(19866):    at com.a.a.a.b.b.a(Unknown Source)  
  4. E/AndroidRuntime(19866):    at tv.hisense.android.mps.service.ay.run(Unknown Source)  
  5. E/AndroidRuntime(19866): Caused by: b.a.a.c.b: The chosen LogFactory implementation does not extend LogFactory.  
  6.  Please check your configuration.   
  7. (Caused by java.lang.ClassCastException:   
  8. The application has specified that a custom LogFactory implementation should be used but Class  
  9. org.apache.commons.logging.impl.LogFactoryImpl cannot be converted to 'b.a.a.c.c'.   
  10. Please check the custom implementation.   
  11. Help can be found @http://commons.apache.org/logging/troubleshooting.html.)  
  12. E/AndroidRuntime(19866):    at b.a.a.c.c.a(Unknown Source)  
  13. E/AndroidRuntime(19866):    at b.a.a.c.e.run(Unknown Source)  
  14. E/AndroidRuntime(19866):    at java.security.AccessController.doPrivileged(AccessController.java:45)  
  15. E/AndroidRuntime(19866):    at b.a.a.c.c.b(Unknown Source)  
  16. E/AndroidRuntime(19866):    at b.a.a.b.p.<clinit>(Unknown Source)  
  17. E/AndroidRuntime(19866):    ... 2 more  

提示以上資訊可知

org.apache.commons.logging.impl.LogFactoryImpl

類引用出錯,查找到此類所在的jar包,通過以下三句解決此問題  

  1. -libraryjars libs/commons-logging-1.1.1.jar    #忽略jar包  
  2. -dontwarn org.apache.**                        #不警告此包  
  3. -keep class org.apache.** {*;}                 #保留此包下代碼不進行混淆   

切記,一定要根據日志資訊來确定忽略哪些jar包和保留哪些類,沒出錯的包就不要忽略和保留了,否則可能會産生OutOfMemoryError

代碼混淆的方法

根據 SDK 的版本不同有 2 中不同的代碼混淆方式,以上的 proguard.cfg 參數詳解中所涉及到的資訊是在較低版本SDK下的混淆腳本,事實上在高版本的SDK下混淆的原理和參數也與低版本的相差無幾,隻是在不同SDK版本的環境下引入混淆腳本的方式有所不同。具體方法如下:

  • 低版本 SDK 下,項目中同時包含 proguard.cfg 和 project.properties 檔案,則隻需在project.properties 檔案末尾添加 proguard.config=proguard.cfg再将項目Export即可。
  • 高版本SDK下,項目中同時包含proguard-project.txt 和 project.properties 檔案,這時需要在proguard-project.txt檔案中進行如下資訊的配置,然後再将項目Export即可。下面以真實的檔案進行示範說明。 
  1. # This file is automatically generated by Android Tools.  
  2. # Do not modify this file -- YOUR CHANGES WILL BE ERASED!  
  3. #  
  4. # This file must be checked in Version Control Systems.  
  5. # To customize properties used by the Ant build system edit  
  6. # "ant.properties", and override values to adapt the script to your  
  7. # project structure.  
  8. # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):  
  9. #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt  
  10. proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt  
  11. # 還可以使用項目内的配置檔案  
  12. # proguard.config=proguard-project.txt  
  13. # Project target.  
  14. target=android-19  

以上的配置資訊即是 project.properties 檔案中内容,藍色文字為我們在代碼混淆過程中需要添加的配置資訊,其中:sdk.dir 為你在目前機器上 SDK 的安裝路徑。如果想保留某個包下的檔案不被混淆,可以在 proguard-project.txt 檔案中加入保留對應包名的語句即可。