作为Android开发者,如果你不想开源你的应用,那么在应用发布前,就需要对代码进行混淆处理,从而让我们代码即使被反编译,也难以阅读。混淆概念虽然容易,但很多初学者也只是网上搜一些成型的混淆规则粘贴进自己项目,并没有对混淆有个深入的理解。本篇文章的目的就是让一个初学者在看完后,能在不进行任何帮助的情况下,独立写出适合自己代码的混淆规则。
1. 什么是混淆
混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。
用一个词概括什么是混淆:重命名
2. 混淆的好处
(1)降低代码阅读性,保护源码
(2)精简编译后的程序大小
缺点:重命名可能导致运行出错,若测试不充分,可能会影响部分功能。
3. 打开混淆(app/build.gradle)
这里我们直接用Android Studio来说明如何进行混淆,Android Studio自身集成Java语言的ProGuard作为压缩,优化和混淆工具,配合Gradle构建工具使用很简单,只需要在工程应用目录的gradle文件中设置minifyEnabled为true即可。然后我们就可以到proguard-rules.pro文件中加入我们的混淆规则了。
android {
...
buildTypes {
release {
minifyEnabled true //打开混淆
zipAlignEnabled true //排列压缩apk
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
发布一款应用除了设minifyEnabled为ture,建议你也应该设置zipAlignEnabled为true,像Google Play强制要求开发者上传的应用必须是经过zipAlign的,zipAlign可以让安装包中的资源按4字节对齐,这样可以减少应用在运行时的内存消耗。
4. 配置自定义的混淆文件(app/proguard-rules.pro)
(1)Android自带的混淆规则文件
Androidsdk目录\tools\proguard\proguard-android.txt
一般Android自带的混淆规则文件是不够用的,它只定义了部分混淆规则,例如组件不混淆、View不混淆等,完全不够实际项目的使用,所以我们还是需要自己编辑自定义的混淆文件:app/proguard-rules.pro。
(2)混淆基本语法:
#:代表行注释符
-:表示一条规则的开始
keep 保留,例如-keepattributes Signature :表示保留泛型不混淆;例如-keep class :表示保留类不混淆
dont 不要,例如-dontwarn retrofit2.**:表示不要提示retrofit2包下的所有警告,-dontwarn命令主要用在第三方包编译时候的警告报错
(3)保留不混淆的命令(举例说明):
-keep class com.appname.test.*:只保持该包下的类名不会被混淆,子包下的类名还是会被混淆(类的内容还是会混淆)
-keep class com.appname.test.**:只保持该包下的类名和子包下的类名不会被混淆(类的内容还是会混淆)
-keep class com.appname.test.* {*;}:既保持类名,也保持类里面的内容不会被混淆
再者,如果一个类中你不希望保持全部内容不被混淆,而只是希望保护类下的特定内容,就可以使用
<init>; //匹配所有构造器
<fields>; //匹配所有域
<methods>; //匹配所有方法方法
你还可以在<fields>或<methods>前面加上private 、public、native等来进一步指定不被混淆的内容,如
-keep class com.appname.test.One { public <methods>;
}
表示One类下的所有public方法都不会被混淆,当然你还可以加入参数,比如以下表示用JSONObject作为入参的构造函数不会被混淆
-keep class com.appname.test.One { public <init>(org.json.JSONObject);
}
有时候你是不是还想着,我不需要保持类名,我只需要把该类下的特定方法保持不被混淆就好,那你就不能用keep方法了,keep方法会保持类名,而需要用keepclassmembers ,如此类名就不会被保持,为了便于对这些规则进行理解,官网给出了以下表格
注:具体的语法请阅读ProGuard手册来了解更多关于混淆配置文件的信息
5. 混淆注意事项
- jni方法不可混淆,方法需要和native方法保持一致
- 反射用到的类不混淆(否则反射可能出现问题)
- 四大组件和Application的类不混淆(会导致manifest文件找不到对应的类)
- View及其子类不能被混淆(xml布局文件解析时可能会出错)
- 保留R类不混淆
- 注解相关的类不混淆
- GSON、fastjson等解析的bean数据不可混淆
- WebView中JS调用写的接口方法不混淆,方法名需要与JS中的方法保持一致
- 枚举enum类中的values和valuesof这2个方法不能混淆(会被发射调用)
- 继承Parceable和Serializable等可序列化的类不能被混淆
- 第三方包的混淆:请参考第三方提供的混淆规则(若第三方没有提供混淆规则,建议第三方包全部不混淆)
6. mapping文件
(1)mapping文件目录:
工程目录\app\build\outputs\mapping\发布渠道\release\
(2)mapping目录下的文件:
dump.txt:描述.apk文件中所有类文件间的内部结构
seeds.txt:列出了未被混淆的类和成员
usage.txt:列出了从.apk中删除的代码
mapping.txt:列出了原始的类,方法和字段名与混淆后代码间的映射(即重命名前后的映射表)。这个文件很重要,当你从release版本中收到一个bug报告时,可以用它来翻译被混淆的代码,将混淆的日志转化成未混淆的日志。
(3)mapping还原工具: <Android-sdk>/tools/proguard/bin/proguardgui.bat
注意:每次发布版本的时候最好都保留mapping文件,与apk版本文件一同保留。
7. 导入混淆的时机
工程代码加入混淆的时机越早越好,越早测试就能越早地发现问题,测试不充分可能导致某些功能不能使用。假设第N个版本为release的正式版本,导入混淆的版本建议在:第1~(N-4)个版本(至少预留4个版本来保证测试)。
一般都是在Release模式才加入混淆, 之所以不在Debug模式下加,是因为混淆后的代码会使得调试变得很累赘。
8. 最后附上项目中最终版本的混淆文件(proguard-rules.pro)
备注:该项目的主包名是com.appname.test,直接使用的话请修改文件内的主包名
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# 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 *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#指定压缩级别
-optimizationpasses 5
# 混合时不使用大小写混合,混合后的类名为小写
-dontusemixedcaseclassnames
# 指定不去忽略非公共库的类
-dontskipnonpubliclibraryclasses
#不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers
# 包含有类名->混淆后类名的映射关系
-verbose
#不做预校验,加快混淆速度
-dontpreverify
#混淆时采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#把混淆类中的方法名也混淆了
-useuniqueclassmembernames
#优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification
#将文件来源重命名为“SourceFile”字符串
#-renamesourcefileattribute SourceFile
#不混淆泛型
-keepattributes Signature
#保留注解
-keepattributes *Annotation*
#保留行号
-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();
}
# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**
# 通常不混肴的类
-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.preference.Preference
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.annotation.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
#保留R类
-keep class **.R$* {
*;
}
#保持native方法不混淆
-keepclasseswithmembernames class * {
native <methods>;
}
#保持自定义控件类不混淆
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#保持枚举enum类不被混淆
-keepclasseswithmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#保持parcelable不混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#保持serializable不混淆
-keep class * implements java.io.Serializable{
*;
}
#保持自定义的类不混淆
-keep class com.appname.test.view.** { *; }
-keep class com.appname.test.beans.** { *; }
-keep class com.appname.test.modules.data.remote.beans.** { *; }
-dontwarn com.appname.**
-dontwarn org.joda.time**
-keep public class com.appname.test.R$*{
public static final int *;
}
####################################第三方库 Start####################################
# Retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
# butterknife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
# rxjava
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
-dontnote rx.internal.util.PlatformDependent
# gson
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
# okhttp3
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}
-dontwarn okio.**
-dontwarn javax.annotation.Nullable
-dontwarn javax.annotation.ParametersAreNonnullByDefault
# Okio
-dontwarn com.squareup.**
-dontwarn okio.**
-keep public class org.codehaus.* { *; }
-keep public class java.nio.* { *; }
# glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# for DexGuard only
#-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
#不混淆cordova插件
#-repackageclasses ''
#-allowaccessmodification
#-optimizations !code/simplification/arithmetic
#-keepattributes *Annotation*
-keep public class * extends org.apache.cordova.CordovaPlugin
-keep public class org.apache.cordova.**{*;}
#greendao
-keep class org.greenrobot.greendao.**{*;}
-keep public interface org.greenrobot.greendao.**
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
-keep class net.sqlcipher.database.**{*;}
-keep public interface net.sqlcipher.database.**
-dontwarn net.sqlcipher.database.**
-dontwarn org.greenrobot.greendao.**
# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use RxJava:
-dontwarn rx.**
#EventBus
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(Java.lang.Throwable);
}
#Umeng App analytics
-keepclassmembers class * {
public <init> (org.json.JSONObject);
}
-keep public class com.appname.test.R$*{
public static final int *;
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
####################################第三方库 End####################################