android打包混淆及語法規則詳解

前言

在使用Android Studio混淆打包時,該IDE自身集成了Java語言的ProGuard作爲壓縮,優化和混淆工具,配合Gradle構建工具使用很簡單。只需要在工程應用目錄的gradle文件中設置minifyEnabled爲true即可。然後我們就可以到proguard-rules.pro文件中加入我們的混淆規則了。

ProGuard作用

壓縮(Shrinking):默認開啓,用以減小應用體積,移除未被使用的類和成員,並且會在優化動作執行之後再次執行(因爲優化後可能會再次暴露一些未被使用的類和成員)。如果想要關閉壓縮,在proguard-rules.pro文件中加入:

# 關閉壓縮
-dontshrink 

優化(Optimization):默認開啓,在字節碼級別執行優化,讓應用運行的更快。同上,如果想要關閉優化,在proguard-rules.pro文件中加入:

# 關閉優化
-dontoptimize
-optimizationpasses n 表示proguard對代碼進行迭代優化的次數,Android一般爲5

混淆(Obfuscation):默認開啓,增大反編譯難度,類和類成員會被隨機命名,除非用keep保護。

# 關閉混淆
-dontobfuscate

混淆後默認會在工程目錄app/build/outputs/mapping/release下生成一個mapping.txt文件,這就是混淆規則,我們可以根據這個文件把混淆後的代碼反推回源本的代碼,所以這個文件很重要,注意保護好。原則上,代碼混淆後越亂越無規律越好,但有些地方我們是要避免混淆的,否則程序運行就會出錯,所以就有了下面要教大家的,如何讓自己的部分代碼避免混淆從而防止出錯。

實現代碼混淆

使用Android Studio正式打包時默認是不開啓代碼混淆的,如果需要開啓代碼混淆,可以在 app 模塊下的build.gradle 文件中修改 minifyEnabled false 爲 true。代碼結構如下:

android {
    …………
    buildTypes {
        release {
            // 是否進行混淆
            minifyEnabled true
            // 混淆文件的位置
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            ···
        }
    }
}

修改完這裏,需要打開app模塊下的 proguard-rules.pro 文件來定義項目打包的混淆選項:(借鑑別人的一個混淆模板,大部分通用,有個別不需要的可以去掉)

#--------------------------1.實體類---------------------------------
# 如果使用了Gson之類的工具要使被它解析的JavaBean類即實體類不被混淆。(這裏填寫自己項目中存放bean對象的具體路徑)
-keep class com.php.soldout.bean.**{*;}

#--------------------------2.第三方包-------------------------------

#Gson
-keepattributes Signature
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.examples.android.model.** { *; }
-keep class com.google.gson.* { *;}
-dontwarn com.google.gson.**

#butterknife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }

#-------------------------3.與js互相調用的類------------------------


#-------------------------4.反射相關的類和方法----------------------


#-------------------------5.基本不用動區域--------------------------
#指定代碼的壓縮級別
-optimizationpasses 5

#包明不混合大小寫
-dontusemixedcaseclassnames

#不去忽略非公共的庫類
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers

#混淆時是否記錄日誌
-verbose

#優化  不優化輸入的類文件
-dontoptimize

#預校驗
-dontpreverify

# 保留sdk系統自帶的一些內容 【例如:-keepattributes *Annotation* 會保留Activity的被@override註釋的onCreate、onDestroy方法等】
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod

# 記錄生成的日誌數據,gradle build時在本項根目錄輸出
# apk 包內所有 class 的內部結構
-dump proguard/class_files.txt
# 未混淆的類和成員
-printseeds proguard/seeds.txt
# 列出從 apk 中刪除的代碼
-printusage proguard/unused.txt
# 混淆前後的映射
-printmapping proguard/mapping.txt


# 避免混淆泛型
-keepattributes Signature
# 拋出異常時保留代碼行號,保持源文件以及行號
-keepattributes SourceFile,LineNumberTable

#-----------------------------6.默認保留區-----------------------
# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclassmembers 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*(***);
}

#保持 Serializable 不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# 保持自定義控件類不被混淆
-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);
}

# 保持枚舉 enum 類不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

# 不混淆R文件中的所有靜態字段,我們都知道R文件是通過字段來記錄每個資源的id的,字段名要是被混淆了,id也就找不着了。
-keepclassmembers class **.R$* {
    public static <fields>;
}

#如果引用了v4或者v7包
-dontwarn android.support.**

# 保持哪些類不被混淆
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Fragment
-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.preference.Preference

-keep class com.zhy.http.okhttp.**{*;}
-keep class com.wiwide.util.** {*;}

# ============忽略警告,否則打包可能會不成功=============
-ignorewarnings

混淆的一些基本規則:

先看如下兩個比較常用的命令,很多童鞋可能會比較迷惑以下兩者的區別。

-keep class cn.hadcn.test.**
-keep class cn.hadcn.test.*

一顆星 
  表示只是保持該包下的類名,而子包下的類名還是會被混淆; 
兩顆星 
  表示把本包和所含子包下的類名都保持;用以上方法保持類後,你會發現類名雖然未混淆,但裏面的具體方法和變量命名還是變了。 
   
這時如果既想保持類名,又想保持裏面的內容不被混淆,我們就需要以下方法了:

-keep class cn.hadcn.test.* {*;}

在此基礎上,我們也可以使用Java的基本規則來保護特定類不被混淆,比如我們可以用extends,implements等這些Java規則。如下例子就避免所有繼承Activity的類被混淆

-keep public class * extends android.app.Activity

如果我們要保留一個類中的內部類不被混淆則需要用$符號,如下例子表示保持ScriptFragment內部類JavaScriptInterface中的所有public內容不被混淆。

-keepclassmembers class cc.ninty.chat.ui.fragment.ScriptFragment$JavaScriptInterface {
   public *;
}



再者,如果一個類中你不希望保持全部內容不被混淆,而只是希望保護類下的特定內容,就可以使用

<init>;     //匹配所有構造器
<fields>;   //匹配所有域
<methods>;  //匹配所有方法方法



你還可以在<fields>或<methods>前面加上private 、public、native等來進一步指定不被混淆的內容,如

-keep class cn.hadcn.test.One {
    public <methods>;
}



表示One類下的所有public方法都不會被混淆,當然你還可以加入參數,比如以下表示用JSONObject作爲入參的構造函數不會被混淆

-keep class cn.hadcn.test.One {
   public <init>(org.json.JSONObject);
}

有時候你是不是還想着,我不需要保持類名,我只需要把該類下的特定方法保持不被混淆就好,那你就不能用keep方法了,keep方法會保持類名,而需要用keepclassmembers ,如此類名就不會被保持,爲了便於對這些規則進行理解,官網給出了以下表格:

 

# -keep關鍵字
# keep:包留類和類中的成員,防止他們被混淆
# keepnames:保留類和類中的成員防止被混淆,但成員如果沒有被引用將被刪除
# keepclassmembers :只保留類中的成員,防止被混淆和移除。
# keepclassmembernames:只保留類中的成員,但如果成員沒有被引用將被刪除。
# keepclasseswithmembers:如果當前類中包含指定的方法,則保留類和類成員,否則將被混淆。
# keepclasseswithmembernames:如果當前類中包含指定的方法,則保留類和類成員,如果類成員沒有被引用,則會被移除。

注意事項

1、自己測試的時候使用的是Android Studio 3.2.1 版本,在混淆打包時,每次構建時 ProGuard 都會輸出下列文件:

這些文件保存在 /build/outputs/mapping/release/ 中。(所以筆者認爲上邊的混淆模板中的記錄生成的日誌數據沒有添加的必要,因爲IDE在編譯時自動已經輸出了)

2、jni方法不可混淆,因爲這個方法需要和native方法保持一致;

# 保持native方法不被混淆 

-keepclasseswithmembernames class * {    
    native <methods>;
}

3、反射用到的類不混淆(否則反射可能出現問題);

4、AndroidMainfest中的類不混淆,所以四大組件和Application的子類和Framework層下所有的類默認不會進行混淆,自定義的View默認也不會被混淆。所以像網上貼的很多排除自定義View,或四大組件被混淆的規則在Android Studio中是無需加入的。

5、與服務端交互時,使用GSON、fastjson等框架解析服務端數據時,所寫的JSON對象類不混淆,否則無法將JSON解析成對應的對象;

6、使用第三方開源庫或者引用其他第三方的SDK包時,如果有特別要求,也需要在混淆文件中加入對應的混淆規則;

7、有用到WebView的JS調用也需要保證寫的接口方法不混淆,原因和第一條一樣;

8、Parcelable的子類和Creator靜態成員變量不混淆,否則會產生Android.os.BadParcelableException異常;

# 保持Parcelable不被混淆    
-keep class * implements Android.os.Parcelable {         
    public static final Android.os.Parcelable$Creator *;
}


9、使用enum類型時需要注意避免以下兩個方法混淆,因爲enum類的特殊性,以下兩個方法會被反射調用,見第二條規則。

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


最後

發佈一款應用除了設minifyEnabled爲ture,你也應該設置zipAlignEnabled爲true,像Google Play強制要求開發者上傳的應用必須是經過zipAlign的,zipAlign可以讓安裝包中的資源按4字節對齊,這樣可以減少應用在運行時的內存消耗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章