Android的混淆
1,是什么
不想开源自己的项目而在应用发布之前对相关的代码进行混淆,从而达到即使被反编译也难以阅读的效果,用以提高应用的安全性和性能(属于开发周期偏后期的技术)。
一般的混淆通常指的是Proguard
由于Java是一种跨平台,解释型的语言,所以会将Java源码编译成中间“字节码”存储于class文件中。由于跨平台的需要,Java自己吗中又包含了很多源码信息:变量名,方法名,并且通过这些名称来访问变量和方法,这些符号有许多予以信息,容易被反编译成Java源代码。而为了防止此现象,就可以使用Java混淆器对Java字节码进行混淆。
所谓混淆就是对发布出去的程序进行重新组织以及处理,是的处理后的代码与吹前的代码完全像用的功能,但是难以被反编译,及时反编译成功也很难得出程序真正的语义。被混淆的程序代码,仍然遵照原来的档案格式和指令集,执行结果也与混淆之前一样,只是混淆器将代码中的所有的变量,函数,类的名称变为简单的英文字母代号,在缺乏相应的函数名和程序注释的情况下,及时被反编译,也难以阅读。同时混淆也是不可逆的,在混淆的过程中一些不影响正常运行的信息将被永久丢失,也使得程序变得难以理解。
在AS中对其进行混淆:
//对release版本进行混淆处理
android{
···
buildTypes{
release{
//是否进行混淆
minifyEnabled true
//proguard-android.txt为SDK默认混淆配置
//proguard-rules.pro 模块下的混淆配置
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
}
}
2,Proguard作用
压缩(Shrinking):
默认开启,用以减小应用的体积大小,移除没有被使用的类以及成员,并且会在优化动作执行完毕之后再次执行(由于优化后会再次暴露一些没有被适用过的类以及成员)
-dontshrink//关闭压缩
优化(Optimization):
默认开启,在季节妈妈级别执行优化,让系统运行的更快
-dontoptimize //关闭优化
-optimizationpasses n //表示proguard对代码进行迭代优化的次数,Android一般为5
混淆(Obfuscation):
默认开启,增大反编译难度,类和类成员会被随机命名,除非使用keep进行保护
-dontobfuscate //关闭混淆
混淆之后就会工程目录(app/build/outputs/mapping/release)下产生一个mapping.txt文件,即为混淆规则。我们可以根据这个文件将混淆后的代发反推回源本的代码,所以要保护好。而原则上,代码越无规律越乱越好,但是有些地方还是要避免进行混淆的,否则程序在运行的时候就会出错。
3,基本语法使用
定义一些不需要被混淆的源代码,即在编译期间会对未定义的源代码进行混淆
3.1.1,保留类名,方法名
#保留位于View类中的get和set方法
-keepclassmembers public class * extends android.view.View{
void set*(***);
*** get*();
}
#保留在Activity中以View为参数的方法不变
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
#保留实现了Parcelable的类名不变,
-keep class * implements android.os.Parcelable{
public static final android.os.Parcelable$Creator *;
}
#保留R$*类中静态成员的变量名
-keepclassmembers class **.R$*{
public static <fields>;
}
1,* * 和*
//1,表示只保持该包下的类名,而子包下的类名还是会被混淆
-keep class com.example.test.*;
//2,表示把本包和所含子包下的类名都进行保持
-keep class com.example.test.**;
以上方法虽然没有对类名进行混淆,但是里面的具体方法以及便令明明明还是改变了,此时如果想要保持类名,又要保持里面的内容不被混淆,就需要使用一下的方法:
-keep class com.example.text.* {*;}
在此基础上也可以使用Java的基本规则来保护特定的类不会被混淆,比如可以使用extend,implement等。
2,其他使用
如果要保留一个类中的内部类不被混淆需要用到**$**符号,如下例所示:
-keepclassmembers class com.example.test.ui.fragment.UserFragment$JavaScriptInterface{
public *;
}
如果一个类中不希望保持全部的内容不被混淆,而是希望保护类下的特定内容的话可以使用:
<init> //匹配所有的构造器
<fields> //匹配所有的域
<methods> //匹配所有的方法
还可以在,前面加上权限修饰符(private,public)以及native来进行进一步指定不被混淆的内容
-keep class aom.example.test.One{
public <methods>;
}
/*
表示One类下所有的public方法都不会背混淆,也可以加入参数:
public <init>(org.json.JSONObject);
表示使用JSONObject作为入参的构造函数也不会被混淆
*/
如果说不需要保持类名,而是只需要把该类下特定的方法保持不被混淆,就不需要再使用keep(会保持类名),而是使用keepclassmembers:
保留 | 防止移除或被重命名 | 防止被重命名 |
---|---|---|
类和类成员 | -keep | -keepnames |
仅类成员 | -keepclassmembers | -keepclassmembernames |
如果拥有谋划成员,保留类和类成员 | -keepclasseswithmembers | -keepclasseswithmembernames |
- 移除指的是在压缩是是否会被删除。
3.1.2,去除日志信息
通过配置Proguard,将android.util.Log置为无效,就可去除apk中打印日志的代码。
两种方式:
-
发布版本的时候封装一个log wrapper
public static void e(String tag, String msg){ if(LOG_LEVEL > ERROR) Log.e(tag,msg); } public static void w(String tag, String msg){ if(LOG_LEVEL > WARN) Log.e(tag,msg); } public static void i(String tag, String msg){ if(LOG_LEVEL > INFO) Log.e(tag,msg); }
-
直接删除打印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(...);
}
-assumenosideeffects class com.example.Log.Logger{
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
- 需要打开优化开关
- 默认使用proguard-android-optimize.txt
3.1.3,注意事项
1,JNI方法不可混淆,这个方法需要和native方法保持一致
-keepclasseswithmembernames class *{
# 保持native方法不会被混淆
native <methods>
}
2,反射用到的类不能够被混淆(否则反射可能会出问题)
3,Mainfest中的类不可以被混淆,所以四大组件以及Application对的子类以及Framework层下所有的类默认不会进行混淆。自定义View默认也不会被混淆。
4,使用第三方开源库或者引用其他第三方的SDK包时,如果有特别的要求,需要在混淆文件中加入对应的混淆规则
5,与服务端进行交互的时候,使用GSON,fastjson等框架解析服务端数据的时候,缩写的JSON对象不混淆,否则将无法将JSON解析成对应的工具
6,有用到WebView的JS调用也需要保证写的接口犯法不混淆
7,使用Parcelable进行序列化的时候Parcelable的子类和Creator景天成员变量也不进行混淆,否则会产生Android.os.BadParcelableException异常
-keep class * implements Android.os.Parcelable{
# 保持Parcelable不被混淆
public static final Android.os.Parcelable$Creator *;
}
8,使用enum类型时需要注意避免以下两个方法混淆,因为enum类的特殊性,以下两个方法会被反射调用
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}