Java 混淆那些事(六):Android 混淆的那些瑣事

QuincySx發表於2019-03-24

本文已授權微信公眾號「玉剛說」獨家釋出。

今天我們這一篇是「Java 混淆那些事」系列的第六篇,我們們針對 Android 平臺來寫。非 Android 開發者可以跳過此篇文章。

前提準備

我們這個系列都是基於 ProGuard 6.0 的,而 Android SDK 提供的 ProGuard 比較低。新版本加上的幾個操作符無法使用。如果你一定想用的話,可以下載最新版本的 ProGuard 去替換 SDK 裡面的 Proguard 以支援新特性。SDK 中 Proguard 的路徑是 ANDROID_SDK\tools\proguard,但是要注意保留 proguard-android.txtproguard-android-optimize.txtproguard-project.txt 檔案不要刪除。

我們來說一下 ProGuard 6.0 的新特性。

  1. 新增 -addconfigurationdebugging 操作符。
  2. 新增 -if 操作符。
  3. 新增 -android 操作符。
  4. 可以通過 -target 操作符,可以將 Java 8 程式碼處理為早期版本的 Java,特別是相容了 lambda 表示式。
  5. 可以通過 -target 操作符,可以將 Java 9 程式碼處理為早期版本的 Java,特別對字串拼接進行優化。
  6. 改進優化類合併規則。

Android 預設自帶的規則

我們在 App 的 build.gradle 檔案中有這麼一句

buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 
'proguard-rules.pro'    
    }
}

// 還可以寫成,proguardFiles 可以連寫,可以寫多個。
buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt')
        proguardFiles 'proguard-rules.pro'
    }
}
複製程式碼

我們在上面看到 getDefaultProguardFile('proguard-android.txt') 配置了一個 proguard-android.txt 檔案當作預設的混淆規則,那麼這個檔案是哪裡來的呢?我們可以從官方文件得知 getDefaultProguardFile() 方法是獲取 ANDROID_SDK\tools\proguard 目錄下的檔案。那麼上述命令就是獲取 ANDROID_SDK\tools\proguard\proguard-android.txt 這個檔案。如果你想進一步壓縮程式碼就可以改使用 getDefaultProguardFile("proguard-android-optimize.txt") ,但是會更加耗時。

如果自己寫的混淆配置就放在 proguard-rules.pro 即可,可以建立多個混淆檔案,使用 proguardFiles 指定即可。

proguard-android.txt 檔案內容

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose

-dontoptimize
-dontpreverify

-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

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

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

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

-keepclassmembers class **.R$* {
    public static <fields>;
}

-dontwarn android.support.**

-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}
複製程式碼

我相信大家都能看明白這個預設配置中寫的是什麼,如果不明白什麼意思請參考「Java 混淆那些事」系列的第五篇。我們說一個有意思的。大家都知道在 Android 中可以使用 @Keep 註解,在類上寫 @Keep 註解整個類以及類成員都不會被混淆,在方法以及構造方法上寫 @Keep 能保證方法以及構造方法不被混淆,在欄位上使用 @Keep 直接可以保證欄位不被混淆,實現原理就是上面的預設配置檔案。如果大家對 proguard-android-optimize.txt 感興興趣,可以自己去看一看。

proguard-android-optimize.txt 檔案內容變化

proguard-android-optimize.txt 與 proguard-android.txt 的差別不大。

// 刪除了關閉優化指令
# -dontoptimize

// 新增以下規則
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-allowaccessmodification
-dontpreverify
複製程式碼

多渠道包單獨的混淆規則

可以在渠道的配置上單獨指定混淆規則。

productFlavors {
        flavor1 {
        }
        flavor2 {
            proguardFile 'flavor2-rules.pro'
        }
    }
複製程式碼

ProGuard 的 mapping 如何配置

我們在前面講除錯的時候說 mapping.txt,seeds.txt,usage.txt 都需要自己單獨配置,那麼在安卓中是預設配置好的,那麼他們在哪呢?開啟 專案\模組\build\outputs\mapping\(debug、release) 你就可以看到了。記住每次打包發版記得保留 mapping.txt 檔案。

在 Google Play 上 APK 的時候通常可以同時上傳 mapping.txt 檔案,那麼 Google Play 就會反饋真實的報錯資訊,只不過我們沒有這個待遇了。

Android 中的資源壓縮

Android 中資源壓縮就與 ProGuard 沒有什麼關係了。使用如下程式碼開啟資源壓縮。

android {
    ...
    buildTypes {
        release {
            shrinkResources true
            ...
        }
    }
}
複製程式碼

使用 tools:keep 屬性中指定每個要保留的資源,使用 tools:discard 屬性指定要捨棄的資源。如果需要制定多個可以使用 ,(逗號)隔開,並且支援使用 * 作為萬用字元。* 可以匹配多個。 我們看到 tools:discard 這個比較滑稽,用不到刪了就完了,還特殊指定一下。是不是閒的,其實這個主要是多渠道打包準備的,多渠道包資源肯定有差別,所以可以通過 tools:discard 刪除有差異的資源。

<?xml version="1.0" encoding="utf-8"?><resources xmlns:tools="http://schemas.android.com/tools"    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"    tools:discard="@layout/unused2" />
複製程式碼

Android 混淆的誤區

網上很多現成並且成熟的混淆規則,大家參考一下即可,但是不要盲目照找,我們現在大家理解每個混淆規則是什麼意思,自己參照的時候注意一下。 舉個例子,我們現在開發 Android 我們肯定會引用 support-v4 、support-v7 ,但是 support 包裡面的功能我們不可能全部都使用到。而網上有很多部落格中寫了 keep 掉 v7 v4 包,其實我們是沒有必要的。要知道 v7 包足足有 15000 個左右的方法呢!進行混淆是很有必要的。

小結

Android 的程式碼混淆與直接使用 ProGuard 沒什麼區別,只不過 Android 中有一些配置好的混淆規則和配置,我們只要瞭解了 ProGuard 混淆原理,就可以一通百通。Android 資源壓縮還有一些其他的第三方方案,大家可以位元組瞭解一下。

相關文章