关于Android混淆的一些经验

一些关于混淆的好文章

Android混淆从入门到精通

写给Android开发者的混淆使用手册

读懂 Android 中的代码混淆

1. 哪些不应该混淆

反射中使用的元素

如果一些被混淆使用的元素(属性,方法,类,包名等)进行了混淆,可能会出现问题,如NoSuchFiledException或者NoSuchMethodException等

Jni接口和java的native方法

因为这个方法需要和native方法保持一致,Android工程默认的混淆配置默认混淆,见下方附录。

与服务端交互时,使用GSON、fastjson等框架解析服务端数据时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象.

抽象内部类
2018.3.28日更新
近期混淆时遇到了 java.lang.AbstractMethodError
这个错误是由于一些类中的抽象类匿名对象的方法名被混淆

abstract class Flying{
  void fly();
class Animal{
   Flying f=new Flying(){
      void fly(){
          //fly

如果你只是keep了Animal类

-keep class com.ditclear.demo.Animal{*;}

那么混淆之后就会如下所示

class Animal{
   Flying f=new Flying(){
      void a(){
      //方法名被混淆了
          //fly

方法名被混淆了,就会出现java.lang.AbstractMethodError
解决的方法和内部类一样

-keep class com.ditclear.demo.Animal{*;}
-keep class com.ditclear.demo.Animal$*{*;}

四大组件不混淆

  • 四大组件声明必须在manifest中注册,如果混淆后类名更改,而混淆后的类名没有在manifest注册,是不符合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 * extends android.view.View
    -keep public class com.android.vending.licensing.ILicensingService
    

    使用第三方开源库或者引用其他第三方的SDK包

    如果有特别要求,也需要在混淆文件中加入对应的混淆规则,优秀的开源库一般都会附上自己的混淆规则

    有反射相关的东西 values()valueOf(),Android工程默认的混淆配置默认混淆,见下方附录。

    很多场景下注解被用作在运行时反射确定一些元素的特征.为了保证注解正常工作,我们不应该对注解进行混淆。

    Android工程默认的混淆配置默认混淆

    -keepattributes *Annotation*
    

    js调用java的方法

    同native,在proguard.pro中有这样一段话

    # If your project uses WebView with JS, uncomment the following
    # and specify the fully qualified class name to the JavaScript interface class:
    -keepclassmembers class fqcn.of.javascript.interface.for.webview {
       public *;
    

    Parcelable的子类和Creator静态成员变量

    否则会产生Android.os.BadParcelableException异常,Android工程默认的混淆配置默认混淆

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

    2. 解决混淆之后的bug

    混淆完之后,需要进行详细的测试,确保没有bug,遇到bug就解决它。所以了解混淆之后的代码就很有必要

    进行混淆打包后在 /build/outputs/mapping/release/ 目录下会输出以下文件:

  • dump.txt
    描述APK文件中所有类的内部结构

  • mapping.txt
    提供混淆前后类、方法、类成员等的对照表(很有用)

    如果你的代码混淆后会产生bug的话,log提示中是混淆后的代码,希望定位到源代码的话就可以根据mapping.txt反推。

    每次发布都要保留它方便该版本出现问题时调出日志进行排查,它可以根据版本号或是发布时间命名来保存或是放进代码版本控制中。

  • seeds.txt
    列出没有被混淆的类和成员

  • usage.txt
    列出被移除的代码

  • 现在有一个NoSuchFieldException(一般是反射引起的)
    mapping.png

    然后就知道了com.google.gson.b对应的是com.google.gson.ExclusionStrategy,只要保证它们不被混淆就可以了

    -keep class com.google.**{ *; }
    

    这只是一个简单的例子,大多数情况都可以依样画葫芦。

    在Dubug模式下开启混淆

    一般混淆是用在正式打包的时候的,但我们在配置混淆的时候需要不断的尝试混淆的配置,不可能打一个正式包,去验证一下对不对,那就太麻烦了,所以我一般都在debug的时候开启,调试好了再关闭。

    android {
        buildTypes {
            release {
                minifyEnabled true
                debuggable false
                shrinkResources true /*压缩资源文件*/
                /*zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗*/
                zipAlignEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            debug {
                minifyEnabled true
                debuggable true
                shrinkResources true
                zipAlignEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    

    使用@keep注解
    除了在自定义的混淆配置中写-keepxxx之外,也可以使用@keep注解来使文件避免被混淆,在Android默认的混淆配置下有这样的代码

    # Understand the @Keep support annotation.
    -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>(...);
    

    所以只需使用@keep注解之后就可以避免被混淆

      @Keep
      public class LoginEvent {    
              @Keep
    

    一些替代资源,例如多语言支持的 strings.xml,多分辨率支持的 layout.xml 等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。

    我们使用 resConfig 属性来指定需要支持的属性,例如

    android {
        defaultConfig {
            resConfigs "en", "fr"
    

    其他未显式声明的语言资源将被移除。蛮有用的。

    一些专业的网站

    https://www.guardsquare.com/en/proguard/manual/usage 用户手册 https://proguard.herokuapp.com/ 可以根据你输入的库,自动创建相应的Proguard文件(有些用,像eventbus就不是最新版本)

    混淆的话,我觉得在配置好通用的规则之后,就是不断尝试,遇到异常,就去了解原因,避免混淆文件。

  • Android工程默认的混淆配置 proguard-android.txt,在自己的混淆文件中的一样的东西就不必再写一遍了
  • # This is a configuration file for ProGuard.
    # http://proguard.sourceforge.net/index.html#manual/usage.html
    # Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
    # the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
    # will be ignored by new version of the Android plugin for Gradle.
    # Optimizations: If you don't want to optimize, use the
    # proguard-android.txt configuration file instead of this one, which
    # turns off the optimization flags.  Adding optimization introduces
    # certain risks, since for example not all optimizations performed by
    # ProGuard works on all versions of Dalvik.  The following flags turn
    # off various optimizations known to have issues, but the list may not
    # be complete or up to date. (The "arithmetic" optimization can be
    # used if you are only targeting Android 2.0 or later.)  Make sure you
    # test thoroughly if you go this route.
    -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
    -optimizationpasses 5
    -allowaccessmodification
    -dontpreverify
    # The remainder of this file is identical to the non-optimized version
    # of the Proguard configuration file (except that the other file has
    # flags to turn off optimization).
    -dontusemixedcaseclassnames
    -dontskipnonpubliclibraryclasses
    -verbose
    # Preserve some attributes that may be required for reflection.
    -keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod
    -keep public class com.google.vending.licensing.ILicensingService
    -keep public class com.android.vending.licensing.ILicensingService
    -keep public class com.google.android.vending.licensing.ILicensingService
    -dontnote com.android.vending.licensing.ILicensingService
    -dontnote com.google.vending.licensing.ILicensingService
    -dontnote com.google.android.vending.licensing.ILicensingService
    # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
    -keepclasseswithmembernames class * {
        native <methods>;
    # Keep setters in Views so that animations can still work.
    -keepclassmembers public class * extends android.view.View {
        void set*(***);
        *** get*();
    # We want to keep methods in Activity that could be used in the XML attribute onClick.
    -keepclassmembers class * extends android.app.Activity {
        public void *(android.view.View);
    # For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
    -keepclassmembers enum * {
        public static **[] values();
        public static ** valueOf(java.lang.String);
    -keepclassmembers class * implements android.os.Parcelable {
        public static final ** CREATOR;
    -keepclassmembers class **.R$* {
        public static <fields>;
    # Preserve annotated Javascript interface methods.
    -keepclassmembers class * {
        @android.webkit.JavascriptInterface <methods>;
    # The support libraries 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.
    -dontnote android.support.**
    -dontwarn android.support.**
    # Understand the @Keep support annotation.
    -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>;