文章目录
- 混淆的基本概念
- 开启混淆
- 混淆规则理解
-
- 1、有一些固定的混淆规则不需要更改:
- 2、理解通配符
- 3、保证指定包名下的所有类及子包中所有的类不被混淆
- 4、保证指定的类不被混淆
- 5、不混淆指定类的子类
- 6、指定接口不混淆
- 7、指定接口的实现类不混淆
- 8、指定类的内部类不混淆
- 9、构造函数不混淆
- 10、指定类的属性和方法不被混淆
- 11、不混淆类中所有的 public 方法
- 12、不混淆类中所有的 public 字段
- 13、不混淆构造函数
- 13、不混淆 bean 对象里面的 set 、get 方法
- 常见不混淆的类和属性
- 混淆后的项目目录资源
- 实战1
- aar自动混淆
- 参考资料
混淆的基本概念
- 什么是混淆?
代码混淆亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为。
- 混淆的目的
1、混淆的目的是为了加大反编译的成本,但是并不能彻底防止反编译.
2、压缩apk 资源文件
开启混淆
一般我们做项目的时候,都是分为 release 和 debug 版本,release 版本混淆,debug 版本不混淆。设置如下:
buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }
讯享网
混淆规则理解
1、有一些固定的混淆规则不需要更改:
讯享网#指定代码的压缩级别 -optimizationpasses 5 #包明不混合大小写 -dontusemixedcaseclassnames #不去忽略非公共的库类 -dontskipnonpubliclibraryclasses #优化 不优化输入的类文件 -dontoptimize #预校验 -dontpreverify #混淆时是否记录日志 -verbose # 混淆时所采用的算法 -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* #保护注解 -keepattributes *Annotation*
2、理解通配符
-keep class cn.hadcn.test. -keep class cn.hadcn.test.*
很多同学都搞不懂下面两个的区别,一颗星表示只是保持该包下的类名,而子包下的类名还是会被混淆;两颗星表示把本包和所含子包下的类名都保持;用以上方法保持类后,你会发现类名虽然未混淆,但里面的具体方法和变量命名还是变了,这时如果既想保持类名,又想保持里面的内容不被混淆,我们就需要以下方法了:
讯享网-keep class cn.hadcn.test.* {*;}
3、保证指定包名下的所有类及子包中所有的类不被混淆
- 例子1:com.lib.manager 包下面的类不混淆
-keep class com.lib.manager.{*;}
- 例子2:v4包下面的所有类不混淆
-keep class android.support.v4. { *; }
4、保证指定的类不被混淆
- 例子1:YiBaWiFiActivity 类不被混淆, YiBaWiFiActivity 完整包名:com.yiba.wifi.sdk.lib.activity.YiBaWiFiActivity
-keep class com.yiba.wifi.sdk.lib.activity.YiBaWiFiActivity{*;}
- 例子2:v4 包下面的 ActivityCompat 类不被混淆
-keep class android.support.v4.app.ActivityCompat{*;}
5、不混淆指定类的子类
-keep class * extends pp.lib.User { *; }
User 类的子类不混淆
6、指定接口不混淆
Callback 是一个接口,完整包名为:com.lib.impl.Callback
讯享网-keep interface com.lib.impl.Callback{ * ; }
7、指定接口的实现类不混淆
Callback 是一个接口,完整包名为:com.lib.impl.Callback . 所有的实现类都不会混淆。
-keep class * implements com.lib.impl.Callback { *; }
8、指定类的内部类不混淆
PhoneUtil 的源码如下:
讯享网package com.lib.manager; import android.content.Context; import android.os.AsyncTask; / * Created by yiba_zyj on 2017/4/5. */ public class PhoneUtil { public final static int APPID = 100 ; public PhoneUtil( Context context ){ } class MyTask extends AsyncTask { @Override protected Object doInBackground(Object[] params) { return null; } } }
PhoneUtil 有一个常量、一个构造函数、一个内部类.
混淆规则:
-keep class com.lib.manager.PhoneUtil$* { * ; }
混淆后的结果如下:

讯享网
可以看到混淆后的 PhoneUtil ,常量和 构造函数 都被移除了,只留下了 内部类 MyTask 。
9、构造函数不混淆
PhoneUtil 的源码如所示:
讯享网package com.lib; / * Created by yiba_zyj on 2017/4/7. */ public class PhoneUtil { public static final String aa = "1222" ; public void run(){ } }
混淆规则:PhoneUtil 的无参构造函数不混淆 。PhoneUtil 里面没有用到的 属性和方法将会被移除。
-keep class com.lib.PhoneUtil { public <init>(); }
那么有参的构造函数的混淆规则是怎么样的?
混淆规则:PhoneUtil 的 参数为 Context 的构造函数不混淆
讯享网-keep class com.lib.PhoneUtil { public <init>( android.content.Context ); }
10、指定类的属性和方法不被混淆
- 原始类 PhoneUtil 代码如下,完整的包名为:
com.lib.manager.PhoneUtilpackage com.lib.manager;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;/
- Created by yiba_zyj on 2017/4/5.
*/
public class PhoneUtil {
public static final int AppID = 100 ; / * 获得屏幕高度 * @param context * @return */ public static int getScreenWidth(Context context) { WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.widthPixels; } / * 获得屏幕宽度 * @param context * @return */ public static int getScreenHeight(Context context) { WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.heightPixels; }}
- Created by yiba_zyj on 2017/4/5.
PhoneUtil 类中包含1个常量及2个方法.
- 类中的变量不被混淆:
-keep class com.lib.manager.PhoneUtil{
public static final int AppID ;
} - 混淆后的效果如下:
package com.lib.manager;
讯享网
public PhoneUtil() { }}
可以看到混淆后的类中,AppID 常量被保存下来了,getScreenWidth 、 getScreenHeight 这两个方法被移除了。 为什么会有方法被移除? 因为在混淆时,Android 会默认移除没有使用的的方法和资源,保证apk 合理的被压缩。
- 保留类中的方法不被混淆
-keep class com.lib.manager.PhoneUtil{
public static int getScreenWidth(android.content.Context);
} - 混淆后的方法如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//package com.lib.manager;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;public class PhoneUtil {
public PhoneUtil() {
}public static int getScreenWidth(Context var0) { WindowManager var1 = (WindowManager)var0.getSystemService("window"); DisplayMetrics var2 = new DisplayMetrics(); var1.getDefaultDisplay().getMetrics(var2); return var2.widthPixels; }}
11、不混淆类中所有的 public 方法
讯享网# <methods> 匹配所有的方法 -keep class cn.hadcn.test.One { public <methods>; }
表示One类下的所有public方法都不会被混淆
12、不混淆类中所有的 public 字段
-keep class pp.lib.PhoneUtil{ public <fields> ; }
表示 PhoneUtil 类中所有的 public 属性将保留,其他类型的字段
13、不混淆构造函数
讯享网-keep class pp.lib.PhoneUtil{ <init>(*) ; <init>(* , * ) ; }
不混淆 PhoneUtil 类中所有 一个参数 和 两个参数的 的构造函数。

13、不混淆 bean 对象里面的 set 、get 方法
User 对象如下图所示:
package pp.lib; / * Created by ${zhaoyanjun} on 2017/4/10. */ public class User { private String name ; private String age ; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } }
混淆规则:不混淆 User 类中所有的 set 和 get 方法,* 代表 通配符
讯享网-keep class pp.lib.User{ void set*( * ) ; * get*() ; }
常见不混淆的类和属性
- 不混淆四大组件 和 Application
因为四大组件和 Application 需要在 AndroidManifest.xml 中注册,不能混淆,否则就会报类找不到异常。
-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
特殊情况:
如果 BroadcastReceiver 是动态注册的,则是可以加入混淆的。
- 不混淆任何包含native方法的类的类名以及native方法名
-keepclasseswithmembernames class * {
native ;
} - 不混淆任何一个View中的setXxx()和getXxx()方法,因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。
-keepclassmembers public class * extends android.view.View {
void set*();
* get();
} - 不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了。
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
} - 不混淆枚举中的values()和valueOf()方法
-keepclassmembers enum * {
public static [] values();
public static valueOf(java.lang.String);
} - 不混淆Parcelable实现类中的CREATOR字段,毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
} - 不混淆R文件中的所有静态字段,我们都知道R文件是通过字段来记录每个资源的id的,字段名要是被混淆了,id也就找不着了。
-keepclassmembers class *.R$ {
public static ;
}
混淆后的项目目录资源
混淆完以后会在 build/outputs/mapping/release 目录下生成4个文件。如下图所示:

- mapping.txt :代表源码的 包名/类名/变量名/方法名 和混淆后 的转换关系。
混淆规则
讯享网-keep class com.lib.manager.PhoneUtil{ public static int getScreenWidth(android.content.Context); }
混淆后的 mapping 文件如下图所示:

从上面的 mapping 文件可以看出:
PhoneUtil 转换为 PhoneUtil , 名字没有发生变化,相当于没有混淆。
getScreenWidth 转换为 getScreenWidth , 名字没有发生变换,相当于没有混淆。
getScreenHeight 转换位 a , 名字已经变换了,增加了反编译的难度。
混淆后 PhontUtil 源码如下图所示:

- usage.txt : 代表本次混淆过程中被移除的类或者方法。
举例说明

从上图中可以看出,本次混淆过程中 , 移除了很多没用的类。
com.lib.BuildConfig //移除了 com.lib 包下的 BuildConfig 类 com.lib.activity.A //移除了 com.lib.activity 包下的 A 类 com.lib.manager.PhoneUtil: public static final int AppID //移除了 com.lib.manager 包下的 PhoneUtil 类 中的 AppID 常量 com.lib.manager 包下的 PhoneUtil 类 中的 getScreenHeight 方法 。 com.lib.manager.SS //移除了 com.lib.manager 包下的 SS 类 com.lib.manager.Util //移除了 com.lib.manager 包下的 Util 类 com.lib.manager.hj.PP //移除了 com.lib.manager.hj 包下的 PP 类 com.lib.manager.hj.PP$1
实战1
User 类源码:
讯享网package pp.lib; / * Created by ${zhaoyanjun} on 2017/4/10. */ public class User { private String name ; private String age ; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } }
User1 类源码:
package pp.lib; / * Created by ${zhaoyanjun} on 2017/4/10. */ public class User1 extends User { private String id ; public void run(){ } }
混淆规则:
讯享网-keep class * extends pp.lib.User { *; }
混淆结果: User 类的 类名已经被混淆了,字段没有混淆

User1 类,类名和字段都没有混淆

aar自动混淆
解决第三方SDK的混淆配置管理问题.
尽管有些SDK提供方非常靠谱的给了混淆规则, 但是毕竟要添加到自己项目的proguard配置里, 以后如果混淆规则改了, 配置还得同步改, 对于那些用maven仓库管理的SDK, 非常不友好.
也许有人注意到了, Android归档文件, 也就是俗称的aar文件里可以携带一个proguard.txt文件, 这似乎表明aar文件可以自带混淆配置.
事实也是如此, aar在构建时, 可以通过consumerProguardFiles属性指定一个proguard配置, 这个配置会被打入aar, 它和proguardFiles属性指定的proguard配置不同, proguardFiles是用于构建aar的混淆规则, consumerProguardFiles则是aar的接入方在构建时会使用的混淆规则.
第一步定义两个 .pro 文件 proguard-rules.pro, consumer-rules.pro
proguard-rules.pro : 输出aar 时需要的混淆规则,一般是 keep 暴露给接入方的类、方法、接口
consumer-rules.pro:接入方输出 apk 时需要的混淆规则,一般是 keep 一些不能混淆的 bean 对象。
最后一步:在 aar 的 build.gradle 中添加 consumerProguardFiles 属性
android { defaultConfig { ... consumerProguardFiles 'consumer-rules.pro' } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } }
最后输出的 aar 包会带有 consumer-rules.pro 文件,最后会作用于构建 apk 的混淆过程中。
参考资料
- 腾讯 Bugly Android 混淆那些事儿
- 常用的反编译工具 http://git.oschina.net/zyj1609/AndroidReverseProject)
- Android安全攻防战,反编译与混淆技术完全解析(上)
- Android安全攻防战,反编译与混淆技术完全解析(下)
- 知乎:关于混淆的思考
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/74385.html