Android Proguard 混淆详解

1. 混淆概述

(1). 概念
混淆维基百科的解释
代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为。

(2). 目的
混淆的目的是为了加大反编译的成本,但是并不能彻底防止反编译,比如Android App反编译后虽然代码很难读懂,但依旧是可以读懂的,只是比较费劲;相比较java语言写的程序,C/C++程序反编译后就更难读懂了。

(3). 优缺点
Proguard混淆让代码更加安全, 但是这也使收集的崩溃变得更加难以读懂, 为了解决这个问题我们用 retrace 工具来将混淆后的 StackTrace 还原成混淆之前的信息. retrace脚本 Android 开发环境默认带着retrace脚本,一般情况下路径为./tools/proguard/bin/retrace.sh mapping映射表 Proguard进行混淆之后,会生成一个映射表,文件名为mapping.txt。

(4). Java 代码混淆工具
Android中使用的是Proguard来进行代码混淆的,proguard Java官网对Proguard的定义 Proguard是一个集文件压缩,优化,混淆和校验等功能的工具 它检测并删除无用的类,变量,方法和属性 它优化字节码并删除无用的指令. 它通过将类名,变量名和方法名重命名为无意义的名称实现混淆效果. 最后它还校验处理后的代码,从而使代码更小、更高效、更难破解读懂。
混淆后默认会在工程目录app/build/outputs/mapping/release(debug)下生成一个mapping.txt文件,这就是混淆规则,通过这个文件把混淆后的代码反推回源本的代码,对解决线上出现的bug很有帮助。

(5). 影响元素 代码混淆影响到的元素有
包名
类名
变量名
方法名
其他元素

2. 混淆规则及使用

开启混淆
项目路径下 app 目录下的 build.gradle 文件, 设置minifyEnabled = true 即可

buildTypes {
    release {
        minifyEnabled true
        signingConfig signingConfigs.release
        proguardFiles 'proguard-android-optimize.txt', 'proguard-rules.pro'
}

Proguard keep关键字详解:

Proguard keep 关键字 功能描述
-keep 保留类和类中的成员,防止被混淆或移除
-keepnames 保留类和类中的成员防止被混淆,但成员如果没有被引用将被删除
-keepclassmembers 只保留类中的成员,防止被混淆和移除
-keepclassmembernames 只保留类中的成员,但如果成员没有被引用将被删除
-keepclasseswithmembers 如果当前类中包含指定的方法,则保留类和类成员,否则将被混淆
-keepclasseswithmembernames 如果当前类中包含指定的方法,则保留类和类成员,如果类成员没有被引用,则会被移除

(1) . *号的用法:

-keep com.squareup.okhttp3.*
-keep com.squareup.okhttp3.**

末尾一颗星表示只是保持该包下的类名,而子包下的类名还是会被混淆;
末尾两颗星表示把本包和所含子包下的类名都保持。
通过这两种方式保持类后,类名不会未混淆,但里面的具体方法和变量命名还是会被混淆。

(2) . {*;}的用法

-keep com.squareup.okhttp3.*{*;}
-keep com.squareup.okhttp3.**{*;}

第一行既可以保持该包下的类名,又可以保持类里面的内容不被混淆;
第二行既可以保持该包及子包下的类名,又可以保持类里面的内容不被混淆。

(3) . 保持某个类名不被混淆,但是内部内容会被混淆

-keep class com.android.vending

(4) . 保持某个类的类名及内部的所有内容不会混淆

-keep class com.android.vending.** { *; }

(5). 保持类名不被混淆,并且类中特定内容不混淆

-keep class com.jl.test{
    public void myTest();
     private static final java.lang.String name;
 }

其他常用的一些Proguard 关键字

Proguard 关键字 功能描述
-dontwarn 忽视警告,是一个和keep可以说是形影不离,尤其是处理引入的library时.引入的library可能存在一些无法找到的引用和其他问题,在build时可能会发出警告,如果我们不进行处理,通常会导致build中止.因此为了保证build继续,我们需要使用dontwarn处理这些我们无法解决的library的警告。比如关闭Twitter sdk的警告,我们可以这样做-dontwarn com.twitter.sdk.**
-dontpreverify 不做预校验的操作
-optimizationpasses 代码混淆的压缩比例,值在0-7之间,一般为5
-keepattributes SourceFile,LineNumberTable 抛出异常时保留代码行号
-dontusemixedcaseclassnames 混淆后类名都为小写
-dontskipnonpubliclibraryclasses 指定不去忽略非公共的库的类
-dontskipnonpubliclibraryclassmembers 指定不去忽略非公共的库的类的成员
-verbose -printmapping proguardMapping.txt 生成原类名和混淆后的类名的映射文件
-keepattributes Signature 避免混淆泛型
-keepattributes Annotation,InnerClasses 不混淆Annotation
-optimizations !code/simplification/cast,!field/,!class/merging/ 指定混淆是采用的算法

3. 常用的Android 混淆原则

  1. 反射用到的类不混淆, 需要保证类名方法不变
  2. JNI 方法调用的 java 方法不混淆
  3. Java 的 native 方法
  4. js 调用的 java 方法
  5. AndroidMainfest 中的类不混淆,四大组件和 Application 的子类和Framework层下所有的类默认不会进行混淆, 四大组件声明必须在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.view.View

  6. Parcelable的子类和Creator静态成员变量不混淆,否则会产生android.os.BadParcelableException异常

  7. 使用GSON、fastjson等框架时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象;生成的bean实体对象,内部大多是通过反射来生成, 不能混淆

  8. 使用第三方开源库或者引用其他第三方的SDK包时,需要在混淆文件中加入对应的混淆规则; 这些第三库的文档中 一般会给出相应的混淆规则, 复制过来即可

  9. 有用到WEBView的JS调用也需要保证写的接口方法不混淆

  10. 注解不能混淆 注解在Android平台中使用的越来越多,常用的有ButterKnife和Otto.很多场景下注解被用作在运行时反射确定一些元素的特征. 为了保证注解正常工作,我们不应该对注解进行混淆.Android工程默认的混淆配置已经包含了下面保留注解的配置
    -keep attributes Annotation

  11. 枚举 enum 不要混淆
    使用 enum 类型时需要注意避免以下两个方法混淆,因为enum类的特殊性,以下两个方法会被反射调用

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

4.混淆模板

#指定代码的压缩级别
-optimizationpasses 5

#包名不混合大小写
-dontusemixedcaseclassnames

#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses

#优化  不优化输入的类文件
-dontoptimize

#预校验
-dontpreverify

#混淆时是否记录日志
-verbose

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

#不混淆注解
-keepattributes *Annotation*

#保持哪些类不被混淆
-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
-keep public class com.android.vending.licensing.ILicensingService
#如果有引用v4包可以添加下面这行
-keep public class * extends android.support.v4.app.Fragment

#忽略警告
-ignorewarning

#如果引用了v4或者v7包
dontwarn android.support.**

-keep public class * extends android.view.View {
    public (android.content.Context);
    public (android.content.Context, android.util.AttributeSet);
    public (android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
}

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

#保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

#保持 Serializable 不被混淆
-keepnames class * implements java.io.Serializable

#保持 Serializable 不被混淆并且enum 类也不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient ;
    !private ;
    !private ;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

#不混淆资源类
-keepclassmembers class **.R$* {
    public static ;
}

#避免混淆泛型 如果混淆报错建议关掉
–keepattributes Signature

#移除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(...);
}

你可能感兴趣的:(Android,基础详解,android,java)