1. 程式人生 > >android原始碼使用proguard混淆編譯及錯誤總結

android原始碼使用proguard混淆編譯及錯誤總結

關於混淆編譯也主要是從網上學習的,決定對網上的進行總結,供大家一起學習,研究,東西主要都是網友們寫的,我這裡借鑑了,文章最後是網友的原文地址;


1,什麼是混淆編譯


ProGuard是一個免費的java類檔案壓縮,優化,混淆器.它探測並刪除沒有使用的類,欄位,方法和屬性.它刪除沒有用的說明並使用位元組碼得到最大優化.它使用無意義的名字來重新命名類,欄位和方法.
ProGuard的使用是為了:
1.建立緊湊的程式碼文件是為了更快的網路傳輸,快速裝載和更小的記憶體佔用.
2.建立的程式和程式庫很難使用反向工程.
3.所以它能刪除來自原始檔中的沒有呼叫的程式碼
4.充分利用java6的快速載入的優點來提前檢測和返回java6中存在的類檔案.


ProGuard支援那些種類的優化:
除了在壓縮操作刪除的無用類,欄位和方法外,ProGuard也能在位元組碼級提供效能優化,內部方法有:
1 常量表達式求值
2 刪除不必要的欄位存取
3 刪除不必要的方法呼叫
4 刪除不必要的分支
5 刪除不必要的比較和instanceof驗證
6 刪除未使用的程式碼
7 刪除只寫欄位
8 刪除未使用的方法引數
9 像push/pop簡化一樣的各種各樣的peephole優化
10 在可能的情況下為類新增static和final修飾符
11 在可能的情況下為方法新增private, static和final修飾符
12 在可能的情況下使get/set方法成為內聯的
13 當介面只有一個實現類的時候,就取代它
14 選擇性的刪除日誌程式碼
實際的優化效果是依賴於你的程式碼和執行程式碼的虛擬機器的。簡單的虛擬機器比有複雜JIT編譯器的高階虛擬機器更有效。無論如何,你的位元組碼會變得更小。


需要優化的不被支援技術: 
1 使非final的常量欄位成為內聯
2 像get/set方法一樣使其他方法成為內聯
3 將常量表達式移到迴圈之外
4 Optimizations that require escape analysis 


2,啟動android中的混淆功能(關於eclipse中混淆,網上一大堆,這裡不做解釋)
     


在需要混淆的工程目錄下(package/apps/下的工程)新增proguard.flags檔案,該檔案即為網路傳說中的proguard.cfg,只是命名不一樣而已,然後再Android.mk中新增如下兩句:
LOCAL_PROGUARD_ENABLED := full
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
上面的full 也可以是custom,如果不寫這句,那還得新增如下一句:
TARGET_BUILD_VARIANT := user或者TARGET_BUILD_VARIANT := userdebug
這樣後在工程目錄下執行mm便可以看到在out目錄下生成了形如proguard.classes.jar的東東,這就說明已在編譯中啟動了proguard
但反編譯一看,並未出現網路雲說的abcd替代符號,其實程式碼並未真正混淆:
android在編譯時預設關閉了混淆選項,有去研究build/core目錄的同志會發現這裡也有個proguard.flags檔案,其實在proguard的過程中,編譯器會呼叫包括本地目錄下和系統定義了的多個proguard.flags檔案,而在這個檔案中混淆的選項被禁止了,故而編譯出來的apk仍未混淆。因此將如下句子註釋掉便可實現真正的混淆編譯:
# Don't obfuscate. We only need dead code striping.
-dontobfuscate(將該句加個#號註釋掉)


好奇的同志還可以繼續看看,為什麼TARGET_BUILD_VARIANT := user和LOCAL_PROGUARD_ENABLED := full二選一即可,詳見build/core/package.mk:


LOCAL_PROGUARD_ENABLED:=$(strip $(LOCAL_PROGUARD_ENABLED))
ifndef LOCAL_PROGUARD_ENABLED
ifneq ($(filter user userdebug, $(TARGET_BUILD_VARIANT)),)
# turn on Proguard by default for user & userdebug build
LOCAL_PROGUARD_ENABLED :=full
endif
endif
ifeq ($(LOCAL_PROGUARD_ENABLED),disabled)
# the package explicitly request to disable proguard.
LOCAL_PROGUARD_ENABLED :=
endif
proguard_options_file :=
ifneq ($(LOCAL_PROGUARD_ENABLED),custom)
ifneq ($(all_resources),)
proguard_options_file := $(package_expected_intermediates_COMMON)/proguard_options
endif # all_resources
endif # !custom
LOCAL_PROGUARD_FLAGS := $(addprefix -include ,$(proguard_options_file)) $(LOCAL_PROGUARD_FLAGS)


3,如何書寫proguard.flags檔案


引數解釋:
-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}
-whyareyoukeeping {class_specification}    
優化
-dontoptimize    不優化輸入的類檔案
-assumenosideeffects {class_specification}    優化時假設指定的方法,沒有任何副作用
-allowaccessmodification    優化時允許訪問並修改有修飾符的類和類的成員
混淆
-dontobfuscate    不混淆輸入的類檔案
-printmapping {filename}
-applymapping {filename}    重用對映增加混淆
-obfuscationdictionary {filename}    使用給定檔案中的關鍵字作為要混淆方法的名稱
-overloadaggressively    混淆時應用侵入式過載
-useuniqueclassmembernames    確定統一的混淆類的成員名稱來增加混淆
-flattenpackagehierarchy {package_name}    重新包裝所有重新命名的包並放在給定的單一包中
-repackageclass {package_name}    重新包裝所有重新命名的類檔案中放在給定的單一包中
-dontusemixedcaseclassnames    混淆時不會產生形形色色的類名
-keepattributes {attribute_name,...}    保護給定的可選屬性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses.
-renamesourcefileattribute {string}    設定原始檔中給定的字串常量


4,例項(SkyvideoPlayer)
-optimizationpasses 3
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
-dontpreverify
-verbose
# 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
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#by jiang
# -dontoptimize  #是否對類內部程式碼進行優化,預設優化
# Don't obfuscate. We only need dead code striping.
# -dontobfuscate  #不進行優化
#專案使用到的第三方jar包
-libraryjars ../libs/fastjson-1.1.23.jar


# removes such information by default, so configure it to keep all of it.
-keepattributes Signature   #不優化泛型和反射


#android預設項
-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 com.android.vending.licensing.ILicensingService


# for skyworth fastjson
-keep class com.alibaba.fastjson.** { *; }


#以下為android預設項
-keepclasseswithmembernames class * {
    native ;
}


-keepclasseswithmembernames class * {
    public (android.content.Context, android.util.AttributeSet);
}


-keepclasseswithmembernames class * {
    public (android.content.Context, android.util.AttributeSet, int);
}


-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}


-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}


5,錯誤總結


問題一:使用gson包解析資料時,出現missing type parameter異常
程式中用到了gson的new typeToken,結果打包成apk釋出時,發現丟擲異常,但不通過打包apk時發現一切正常,百思不得其解,最初懷疑沒有將gson-1.7.1.JAR打包進去,後來經過測試發現gson的其他方法經過打包也能正常執行,最後上網找了2天,終於在google gson論壇中找到了解決方法。
第一種:在 proguard.cfg中新增
-dontobfuscate
-dontoptimize


第二種:在 proguard.cfg中新增
# 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.** { *; }
這兩種方法都測試可行,第一個方法沒有混淆編譯,第二個方法能夠混淆編譯
問題二:
反射類不能進行混淆編譯,需加入
-keep class com.test.model.response.** {*;}
問題三:
android輔助jar包異常,在proguard.cfg中加入
-dontwarn android.support.v4.**
-keep class android.support.v4.** {*;}
問題四:(同1)
型別轉換錯誤,因為我用的泛型,所以在呼叫某些方法的時候,會出現這種錯誤,後面在混淆配置檔案加了一個過濾泛型的語句,如下。


-keepattributes Signature
過後,就沒有出現類似的型別轉換錯誤。


問題五:空指標異常,這個錯誤是我對比前面的錯誤來說,所用的時間比較短,開始是找不到方法到底是哪個(原因是上面提到的混淆後方法名相同),所以就把這個類裡面的所有方法都過濾掉,這樣我沒用多少時間,也就找到了具體的方法,可還是不明白原因,後面發現了其中的一個if判斷,我利用反射篩選方法,關鍵字是“get”,突然我就震精了,大叫一聲——soga,原來我 model的 set/get方法名全部都被混淆了,所以篩選不到方法,返回的也就是null值,自然下面用到這個方法的返回值就會丟擲空指標異常。


解決方法:把 model包下面的所有類,全部過濾掉。


總結:如要用到反射,反射一般就會利用到泛型,所以必須要把泛型的全部過濾掉,如果有根據變數名或者方法名判斷的,記得所在的類需過濾掉,之中還有用到 annotation的地方,要加入一行程式碼,如下:


-keepattributes *Annotation*
這樣就能過濾掉所有的annotation,否則也會丟擲空指標異常。


推薦文章,內容都是他們寫的,我只是整理了一下,呵呵
http://blog.csdn.net/hehe9737/article/details/8152330


http://charles-tanchao.diandian.com/post/2012-05-24/20118715


http://www.cnitblog.com/zouzheng/archive/2011/01/12/72639.html


http://www.eoeandroid.com/thread-173733-1-1.html