Android 代碼混淆規則

1. Proguard介紹

Android SDK自帶了混淆工具Proguard。它位於SDK根目錄\tools\proguard下面。 ProGuard是一個免費的Java類文件收縮,優化,混淆和預校驗器。它能夠檢測並刪除未使用的類,字段,方法和屬性。它能夠優化字節碼,並刪除未使用的指令。它能夠將類、字段和方法使用短無心義的名稱進行重命名。最後,預校驗的Java6或針對Java MicroEdition的所述處理後的碼。 若是開啓了混淆,Proguard默認狀況下會對全部代碼,包括第三方包都進行混淆,但是有些代碼或者第三方包是不能混淆的,這就須要咱們手動編寫混淆規則來保持不能被混淆的部分。java

2. Proguard做用

Android中的「混淆」能夠分爲兩部分,一部分是 Java 代碼的優化與混淆,依靠 proguard 混淆器來實現;另外一部分是資源壓縮,將移除項目及依賴的庫中未被使用的資源(資源壓縮嚴格意義上跟混淆沒啥關係,但通常咱們都會放一塊兒講)。android

2.1 代碼混淆

壓縮(Shrinking):默認開啓,用以減少應用體積,移除未被使用的類和成員,而且會在優化動做執行以後再次執行(由於優化後可能會再次暴露一些未被使用的類和成員)。web

-dontshrink 關閉壓縮
複製代碼

優化(Optimization):默認開啓,在字節碼級別執行優化,讓應用運行的更快。算法

-dontoptimize  關閉優化
    -optimizationpasses n 表示proguard對代碼進行迭代優化的次數,Android通常爲5
複製代碼

混淆(Obfuscation):默認開啓,增大反編譯難度,類、函數、變量名會被隨機命名成無心義的代號形如:a,b,c...之類的,除非用keep保護。windows

-dontobfuscate 關閉混淆
複製代碼

上面這幾個功能都是默認打開的,要關閉他們只需配置對應的規則便可。 混淆後默認會在工程目錄app/build/outputs/mapping/release下生成一個mapping.txt文件,這就是混淆規則,咱們能夠根據這個文件把混淆後的代碼反推回源本的代碼,因此這個文件很重要,注意保護好。原則上,代碼混淆後越亂越無規律越好,但有些地方咱們是要避免混淆的,不然程序運行就會出錯。bash

2.2 資源壓縮

資源壓縮將移除項目及依賴的庫中未被使用的資源,這在減小 apk 包體積上會有不錯的效果,通常建議開啓。具體作法是在 build.grade 文件中,將 shrinkResources 屬性設置爲 true。須要注意的是,只有在用minifyEnabled true開啓了代碼壓縮後,資源壓縮纔會生效。 資源壓縮包含了「合併資源」和「移除資源」兩個流程。 「合併資源」流程中,名稱相同的資源被視爲重複資源會被合併。須要注意的是,這一流程不受shrinkResources屬性控制,也沒法被禁止, gradle 必然會作這項工做,由於假如不一樣項目中存在相同名稱的資源將致使錯誤。gradle 在四處地方尋找重複資源:markdown

  • src/main/res/ 路徑
  • 不一樣的構建類型(debug、release等等)
  • 不一樣的構建渠道
  • 項目依賴的第三方庫 合併資源時按照以下優先級順序:
依賴 -> main -> 渠道 -> 構建類型
複製代碼

舉個例子,假如重複資源同時存在於main文件夾和不一樣渠道中,gradle 會選擇保留渠道中的資源。 同時,若是重複資源在同一層次出現,好比src/main/res/src/main/res2/,則 gradle 沒法完成資源合併,這時會報資源合併錯誤。 「移除資源」流程則見名知意,須要注意的是,相似代碼,混淆資源移除也能夠定義哪些資源須要被保留,這點在下文給出。網絡

3. Proguard規則

3.1 基本指令

  • -ignorewarning:是否忽略警告
  • -optimizationpasses n:指定代碼的壓縮級別(在0~7之間,默認爲5)
  • -dontusemixedcaseclassnames:是否使用大小寫混合(windows大小寫不敏感,建議加入)
  • -dontskipnonpubliclibraryclasses:是否混淆非公共的庫的類
  • -dontskipnonpubliclibraryclassmembers:是否混淆非公共的庫的類的成員
  • -dontpreverify:混淆時是否作預校驗(Android不須要預校驗,去掉能夠加快混淆速度)
  • -verbose:混淆時是否記錄日誌(混淆後會生成映射文件)
  • -obfuscationdictionary dictionary_path:指定外部模糊字典
  • -classobfuscationdictionary dictionary_path:指定class模糊字典
  • -packageobfuscationdictionary dictionary_path:指定package模糊字典
  • -optimizations !code/simplification/arithmetic,!field/,!class/merging/,!code/allocation/variable:混淆時所採用的算法(谷歌推薦算法)
  • -libraryjars libs(*.jar;):添加支持的jar(引入libs下的全部jar包)
  • -renamesourcefileattribute SourceFile:將文件來源重命名爲「SourceFile」字符串
  • -keepattributes Annotation:保持註解不被混淆
  • -keep class * extends java.lang.annotation.Annotation {*;}:保持註解不被混淆
  • -keep interface * extends java.lang.annotation.Annotation { *; }:保持註解不被混淆
  • -keepattributes Signature:保持泛型不被混淆
  • -keepattributes EnclosingMethod:保持反射不被混淆
  • -keepattributes Exceptions:保持異常不被混淆
  • -keepattributes InnerClasses:保持內部類不被混淆
  • -keepattributes SourceFile,LineNumberTable:拋出異常時保留代碼行號

3.2 保留選項

  • -keep [,modifier,...] class_specification:指定須要保留的類和類成員(做爲公共類庫,應該保留全部可公開訪問的public方法)
  • -keepclassmembers [,modifier,...] class_specification:指定須要保留的類成員:變量或者方法
  • -keepclasseswithmembers [,modifier,...] class_specification:指定保留的類和類成員,條件是所指定的類成員都存在(既在壓縮階段沒有被刪除的成員,效果和keep差很少)
  • -keepnames class_specification:指定要保留名稱的類和類成員,前提是在壓縮階段未被刪除,僅用於模糊處理。[-keep allowshrinking class_specification 的簡寫]
  • -keepclassmembernames class_specification:指定要保留名稱的類成員,前提是在壓縮階段未被刪除,僅用於模糊處理。[-keepclassmembers allowshrinking class_specification 的簡寫]
  • -keepclasseswithmembernames class_specification:指定要保留名稱的類成員,前提是在壓縮階段後所指定的類成員都存在,僅用於模糊處理。[-keepclasseswithmembers allowshrinking class_specification 的簡寫]
  • -printseeds [filename]:指定詳盡列出由各類-keep選項匹配的類和類成員。列表打印到標準輸出或給定文件。 該列表可用於驗證是否真的找到了預期的類成員,特別是若是您使用通配符。

4. Keep命令說明

命令 做用
-keep 保持類和類成員,防止被移除或者被重命名
-keepnames 保持類和類成員,防止被重命名
-keepclassmembers 保持類成員,防止被移除或者被重命名
-keepclassmembernames 保持類成員,防止被重命名
-keepclasseswithmembers 保持擁有該成員的類和成員,防止被移除或者被重命名
-keepclasseswithmembernames 保持擁有該成員的類和成員,防止被重命名

保持元素不參與混淆的規則的命令格式:app

[保持命令] [類] {
    [成員]
}
複製代碼

「類」表明類相關的限定條件,它將最終定位到某些符合該限定條件的類。它的內容可使用:ide

  • 具體的類
  • 訪問修飾符(public、protected、private
  • 通配符*,匹配任意長度字符,但不含包名分隔符(.)
  • 通配符**,匹配任意長度字符,而且包含包名分隔符(.)
  • extends,便可以指定類的基類
  • implement,匹配實現了某接口的類
  • $,內部類 「成員」表明類成員相關的限定條件,它將最終定位到某些符合該限定條件的類成員。它的內容可使用:
  • <init> 匹配全部構造器
  • <fields> 匹配全部域
  • <methods> 匹配全部方法
  • 通配符*,匹配任意長度字符,但不含包名分隔符(.)
  • 通配符**,匹配任意長度字符,而且包含包名分隔符(.)
  • 通配符***,匹配任意參數類型
  • ,匹配任意長度的任意類型參數。好比void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 這些方法。
  • 訪問修飾符(public、protected、private

4.1 不混淆某個類

-keep public class com.android.proguard.example.Test { *; }
複製代碼

4.2 不混淆某個包全部的類

-keep class com.android.proguard.example.** { *; }
複製代碼

4.3 不混淆某個類的子類

-keep public class * extends com.android.proguard.example.Test { *; }
複製代碼

4.4 不混淆全部類名中包含了「model」的類及其成員

-keep public class **.*model*.** {*;}
複製代碼

4.5 不混淆某個接口的實現

-keep class * implements com.android.proguard.example.TestInterface { *; }
複製代碼

4.6 不混淆某個類的構造方法

-keepclassmembers class com.android.proguard.example.Test {
        public <init>();
    }
複製代碼

4.7 不混淆某個類的特定的方法

-keepclassmembers class com.android.proguard.example.Test {
        public void test(java.lang.String);
    }
複製代碼

4.8 不混淆某個類的內部類

-keep class com.android.proguard.example.Test$* {
            *;
     }
複製代碼

5. Proguard注意事項

5.1 保持基本組件不被混淆

-keep public class * extends android.app.Fragment
    -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
複製代碼

5.2 保持 Google 原生服務須要的類不被混淆

-keep public class com.google.vending.licensing.ILicensingService
    -keep public class com.android.vending.licensing.ILicensingService
複製代碼

5.3 Support包規則

-dontwarn android.support.**
    -keep public class * extends android.support.v4.**
    -keep public class * extends android.support.v7.**
    -keep public class * extends android.support.annotation.**
複製代碼

5.4 保持 native 方法不被混淆

-keepclasseswithmembernames class * { ####
        native <methods>;
    }
複製代碼

5.5 保留自定義控件(繼承自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);
    }
複製代碼

5.6 保留指定格式的構造方法不被混淆

-keepclasseswithmembers class * {
        public <init>(android.content.Context, android.util.AttributeSet);
        public <init>(android.content.Context, android.util.AttributeSet, int);
    }
複製代碼

5.7 保留在Activity中的方法參數是view的方法(避免佈局文件裏面onClick被影響)

-keepclassmembers class * extends android.app.Activity {
        public void *(android.view.View);
    }
複製代碼

5.8 保持枚舉 enum 類不被混淆

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

5.9 保持R(資源)下的全部類及其方法不能被混淆

-keep class **.R$* { *; }
複製代碼

5.10 保持 Parcelable 序列化的類不被混淆(注:aidl文件不能去混淆)

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

5.11 須要序列化和反序列化的類不能被混淆(注:Java反射用到的類也不能被混淆)

-keepnames class * implements java.io.Serializable
複製代碼

5.12 保持 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();
    }
複製代碼

5.13 保持 BaseAdapter 類不被混淆

-keep public class * extends android.widget.BaseAdapter { *; }
複製代碼

5.14 保持 CusorAdapter 類不被混淆

-keep public class * extends android.widget.CusorAdapter{ *; }
複製代碼

5.15 保持反射用到的類和與JavaScript進行交互的類不被混淆

6. 自定義資源保持規則

6.1 keep.xml

shrinkResources true開啓資源壓縮後,全部未被使用的資源默認被移除。假如你須要定義哪些資源必須被保留,在res/raw/路徑下建立一個xml文件,例如keep.xml。 經過一些屬性的設置能夠實現定義資源保持的需求,可配置的屬性有:

  • tools:keep 定義哪些資源須要被保留(資源之間用「,」隔開)
  • tools:discard 定義哪些資源須要被移除(資源之間用「,」隔開)
  • tools:shrinkMode 開啓嚴格模式 當代碼中經過 Resources.getIdentifier() 用動態的字符串來獲取並使用資源時,普通的資源引用檢查就可能會有問題。例如,以下代碼會致使全部以「img_」開頭的資源都被標記爲已使用。
String name = String.format("img_%1d", angle + 1);
    res = getResources().getIdentifier(name, "drawable", getPackageName());
複製代碼

咱們能夠設置 tools:shrinkModestrict 來開啓嚴格模式,使只有確實被使用的資源被保留。 以上就是自定義資源保持規則相關的配置,舉個例子:

<?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@drawable/img_*,@drawable/ic_launcher,@layout/layout_used*" tools:discard="@layout/layout_unused" tools:shrinkMode="strict"/>
複製代碼

6.2 移除替代資源

一些替代資源,例如多語言支持的 strings.xml,多分辨率支持的 layout.xml 等,在咱們不須要使用又不想刪除掉時,可使用資源壓縮將它們移除。 咱們使用 resConfig 屬性來指定須要支持的屬性,例如

android {
        defaultConfig {
            ...
            resConfigs "en", "zh"
        }
    }
複製代碼

其餘未顯式聲明的語言資源將被移除。

7. Proguard使用

7.1 開啓混淆

在項目的可執行工程Module中打開build.gradle文件進行編輯:

android {
    ......
    defaultConfig {
        ......
    }
    buildTypes {
        release {
            minifyEnabled true      // 開啓代碼混淆
            zipAlignEnabled true    // 開啓Zip壓縮優化
            shrinkResources true    // 移除未被使用的資源
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    ......
}
複製代碼
  • minifyEnabled:是否進行代碼混淆
  • zipAlignEnabled:是否進行Zip壓縮優化
  • shrinkResources:是否移除未被使用的資源
  • proguardFiles:混淆規則配置文件
    • proguard-android.txt:AndroidStudio默認自動導入的規則,這個文件位於Android SDK根目錄\tools\proguard\proguard-android.txt。這裏面是一些比較常規的不能被混淆的代碼規則。
    • proguard-rules.pro:針對本身的項目須要特別定義的混淆規則,它位於項目每一個Module的根目錄下面,裏面的內容須要咱們本身編寫。

7.2 編寫混淆規則

# --------------------------------------------基本指令區--------------------------------------------#
-ignorewarning                                      # 是否忽略警告
-optimizationpasses 5                               # 指定代碼的壓縮級別(在0~7之間,默認爲5)
-dontusemixedcaseclassnames                         # 是否使用大小寫混合(windows大小寫不敏感,建議加入)
-dontskipnonpubliclibraryclasses                    # 是否混淆非公共的庫的類
-dontskipnonpubliclibraryclassmembers               # 是否混淆非公共的庫的類的成員
-dontpreverify                                      # 混淆時是否作預校驗(Android不須要預校驗,去掉能夠加快混淆速度)
-verbose                                            # 混淆時是否記錄日誌(混淆後會生成映射文件)

#指定外部模糊字典
-obfuscationdictionary dictionary1.txt
#指定class模糊字典
-classobfuscationdictionary dictionary1.txt
#指定package模糊字典
-packageobfuscationdictionary dictionary2.txt

# 混淆時所採用的算法(谷歌推薦算法)
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*,!code/allocation/variable

# 添加支持的jar(引入libs下的全部jar包)
-libraryjars libs(*.jar;)

# 將文件來源重命名爲「SourceFile」字符串
-renamesourcefileattribute SourceFile

# 保持註解不被混淆
-keepattributes *Annotation*
-keep class * extends java.lang.annotation.Annotation {*;}

# 保持泛型不被混淆
-keepattributes Signature
# 保持反射不被混淆
-keepattributes EnclosingMethod
# 保持異常不被混淆
-keepattributes Exceptions
# 保持內部類不被混淆
-keepattributes Exceptions,InnerClasses
# 拋出異常時保留代碼行號
-keepattributes SourceFile,LineNumberTable

# --------------------------------------------默認保留區--------------------------------------------#
# 保持基本組件不被混淆
-keep public class * extends android.app.Fragment
-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

# 保持 Google 原生服務須要的類不被混淆
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# Support包規則
-dontwarn android.support.**
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**

# 保持 native 方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

# 保留自定義控件(繼承自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);
}

# 保留指定格式的構造方法不被混淆
-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

# 保留在Activity中的方法參數是view的方法(避免佈局文件裏面onClick被影響)
-keepclassmembers class * extends android.app.Activity {
    public void *(android.view.View);
}

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

# 保持R(資源)下的全部類及其方法不能被混淆
-keep class **.R$* { *; }

# 保持 Parcelable 序列化的類不被混淆(注:aidl文件不能去混淆)
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

# 須要序列化和反序列化的類不能被混淆(注:Java反射用到的類也不能被混淆)
-keepnames class * implements java.io.Serializable

# 保持 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();
}

# 保持 BaseAdapter 類不被混淆
-keep public class * extends android.widget.BaseAdapter { *; }
# 保持 CusorAdapter 類不被混淆
-keep public class * extends android.widget.CusorAdapter{ *; }

# --------------------------------------------webView區--------------------------------------------#
# WebView處理,項目中沒有使用到webView忽略便可
# 保持Android與JavaScript進行交互的類不被混淆
-keep class **.AndroidJavaScript { *; }
-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.WebChromeClient {
     public void *(android.webkit.WebView,java.lang.String);
}

# 網絡請求相關
-keep public class android.net.http.SslError

# --------------------------------------------刪除代碼區--------------------------------------------#
# 刪除代碼中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.與JS交互的類-----------------------------



#--------------------------------------------------------------------------

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



#--------------------------------------------------------------------------

#---------------------------------2.第三方依賴--------------------------------



#--------------------------------------------------------------------------


複製代碼
相關文章
相關標籤/搜索