在开发中混淆是app瘦身不可缺少的一部分。
官方说明

混淆其实是包括了代码压缩、代码混淆以及资源压缩等的优化过程。依靠 ProGuard,混淆流程将主项目以及依赖库中未被使用的类、类成员、方法、属性移除,这有助于规避64K方法数的瓶颈;同时,将类、类成员、方法重命名为无意义的简短名称,增加了逆向工程的难度。而依靠 Gradle 的 Android 插件,我们将移除未被使用的资源,可以有效减小 apk 安装包大小。

下面我就粗略的说下:
一. 混淆配置

一般情况下,app module 的 build.gradle 文件默认会有如下结构:

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

因为开启混淆会使编译时间变长,所以debug 模式下不应该开启。我们需要做的是:

1.将release 下minifyEnabled 的值改为true ,打开混淆;

2.加上shrinkResources true,打开资源压缩。

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

二. 自定义混淆规则

在 app module 下默认生成了项目的自定义混淆规则文件 proguard-rules.pro ,多方调研后,一份适用于大部分项目的混淆规则最佳实践如下:

#指定压缩级别
-optimizationpasses 5

#不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers

#混淆时采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

#把混淆类中的方法名也混淆了
-useuniqueclassmembernames

#优化时允许访问并修改有修饰符的类和类的成员 
-allowaccessmodification

#将文件来源重命名为“SourceFile”字符串
-renamesourcefileattribute SourceFile
#保留行号
-keepattributes SourceFile,LineNumberTable

#保持所有实现 Serializable 接口的类成员
-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();
}

#Fragment不需要在AndroidManifest.xml中注册,需要额外保护下
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment

# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**

真正通用的、需要添加的就是上面这些,除此之外,需要每个项目根据自身的需求添加一些混淆规则:

  • 第三方库所需的混淆规则。正规的第三方库一般都会在接入文档中写好所需混淆规则,使用时注意添加。
  • 在运行时动态改变的代码,例如反射。比较典型的例子就是会与 json 相互转换的实体类。假如项目命名规范要求实体类都要放在model 包下的话,可以添加类似这样的代码把所有实体类都保持住:-keep public class .Model. {*;}
  • JNI 中调用的类。
  • WebView 中JavaScript 调用的方法
  • Layout 布局使用的View 构造函数、android:onClick 等。

这里我以百度地图为例,你混淆的时候注意按照第三方的规则,切记不要混淆jar包
所以要加上

-keep class com.baidu.mapapi.** {*;}
-keep class com.baidu.** {*;}
-keep class vi.com.** {*;}
-dontwarn com.baidu.**  //不混淆第三方jar

三. 检查混淆结果

混淆过的包必须进行检查,避免因混淆引入的bug。

一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在 /build/outputs/mapping/release/ 目录下会输出以下文件:

  • dump.txt
    描述APK文件中所有类的内部结构
  • mapping.txt
    提供混淆前后类、方法、类成员等的对照表
  • seeds.txt
    列出没有被混淆的类和成员
  • usage.txt
    列出被移除的代码

我们可以根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据 usage.txt 文件查看是否有被误移除的代码。

另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。

注意事项:

1) 所有在 AndroidManifest.xml 涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。(很多老的混淆文件里会加,现在已经没必要)

2) proguard-android.txt 已经存在一些默认混淆规则,没必要在 proguard-rules.pro 重复添加