Android 中使用 Proguard 混淆 Java 代码

容易被反编译是 Java 、C# 这类解释型、托管型语言的通病,为了保障代码安全,通常采取的措施主要有两种:

  1. 核心逻辑采用难于被反编译的 native 语言(C / C++)编写,而 C# 通过 DllImport 等方式、Java 通过 JNI 都可调用 native 代码。

  2. 对代码进行混淆,C# 有 VS 自带的工具 Dotfuscator ,第三方工具有 DNGuard HVM 、IL Protected 等;Java 这方面工具也比较多,如 Jmangle 、JODE 等。

Proguard 是 SourceForge 上的一个开源项目,可用来混淆、压缩和优化 Java 字节码文件,Android 官方提供了对它的支持。

使用 Proguard 的方式很简单,只需要编写一个文本文件,然后在 project.properties 文件中的 proguard.config 字段声明其路径就 OK 了,详见 Android 官方文档

处理后的 Java 字节码文件体积可以达到混淆和瘦身的效果,但是有些代码是不能混淆的,否则在打包时会报错:

  1. 系统组件及其子类,如 ActivityServiceBroadcastReceiverContentProvider

  2. 官方 Support 包下面的组件及其子类,如 FragmentFragmentActivity

  3. native 方法,因为 JNI 要求方法名必须和 native 代码中保留完全一致;

  4. 注解、枚举成员和 protected 成员;

  5. 和数据解析相关的 JSON 、ParcelableSerializable 子类,还有自定义的实体类,否则会出现解析错误;

  6. WebView 中定义的与 JS 交互的类,否则无法与 JS 进行数据交互;

  7. View 子类的构造方法;

  8. 所有 R.java 中的成员,否则程序运行会找不到资源文件;

示例代码如下:

-verbose

#忽略警告:
-ignorewarnings

#不预校验:
-dontpreverify

#不使用混合的类名:
-dontusemixedcaseclassnames

#不要跳过非公共类库:
-dontskipnonpubliclibraryclasses

#优化:
-optimizationpasses 5
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

#保留注解:
-keepattributes *Annotation*

#避免使用泛型的位置混淆后出现类型转换错误:
-keepattributes Signature

#保留本地方法:
-keepclasseswithmembers class * {
    native <methods>;
}

#保留枚举类型成员的方法:
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

#保留protected方法:
-keep public class * {  
    public protected *;  
} 

#保留系统组件及其子类:
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.backup.BackupAgentHelper
-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 public class com.android.vending.licensing.ILicensingService
-keepclassmembers class * extends android.content.Context {  
   public void *(android.view.View);  
   public void *(android.view.MenuItem);  
}

#去除support-v4包的警告,保留相关API:
-dontwarn android.support.v4.**
-keep class android.support.v4.** { *; }
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.support.v4.app.FragmentActivity  
-keep public class android.support.v4.accessibilityservice.** { *; }
-keep public class android.support.v4.app.** { *; }
-keep public class android.support.v4.os.** { *; }
-keep public class android.support.v4.view.** { *; }
-keep public class android.support.v4.widget.** { *; }

#保留View子类读取XML的构造方法:
-keep 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*(...);  
}
-keepclasseswithmembers class * {
	public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

#保留JSON、Parcelable、Serailizable相关API:
-keepclassmembers class * {
   public <init>(org.json.JSONObject);
}
-keep class * implements android.os.Parcelable {
	public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class * implements java.io.Serializable {  
    static final long serialVersionUID;  
    private static final java.io.ObjectStreamField[] serialPersistentFields;  
    private void writeObject(java.io.ObjectOutputStream);  
    private void readObject(java.io.ObjectInputStream);  
    java.lang.Object writeReplace();  
    java.lang.Object readResolve();  
}  

# 保留WebView中定义的与JS交互的类:
-keepattributes JavascriptInterface
-keep public class com.mypackage.MyClass$MyJavaScriptInterface
-keep public class * implements com.mypackage.MyClass$MyJavaScriptInterface
-keepclassmembers class com.mypackage.MyClass$MyJavaScriptInterface { 
    <methods>; 
}

#去除调试日志,将所有Log.d()改为Log.i():
-assumenosideeffects class android.util.Log{
	public static *** d(...); 
	public static *** i(...);
}

#保留资源文件
-keepclassmembers class **.R$* {  
    public static <fields>;  
}

#保留实体类:
-keep class com.rincliu.library.entity.**{ *; }

#导入第三方库:
-libraryjars libs/android-support-v4.jar

如果使用了其他第三方类库,也会有相关成员不能混淆,具体可查看对应的官方文档说明,以下为示例:

#保留Google GSON相关API:
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.** { *;}

#保留微信相关API:
-keep class com.tencent.mm.sdk.openapi.WXMediaMessage {*;}
-keep class com.tencent.mm.sdk.openapi.** implements com.tencent.mm.sdk.openapi.WXMediaMessage$IMediaObject {*;}

#保留友盟相关API:
-keep public class com.umeng.fb.ui.ThreadView {
}