一些关于混淆的好文章
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
(一般是反射引起的)
通过混淆之后可以看到com.google.gson.b
,我们不知道到底是哪个类引起的,这个时候我们就可以到mapping.txt文件去查找,如下图:
然后就知道了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 ;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep ;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep (...);
}
所以只需使用@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 ;
}
# 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 ;
}
# Preserve annotated Javascript interface methods.
-keepclassmembers class * {
@android.webkit.JavascriptInterface ;
}
# 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 ;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep ;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep (...);
}