Java 是一种跨平台的、解释型语言,Java 源代码编译成中间”字节码”存储于 class 文件中。Java 字节码中包括了很多源代码信息,如变量名、方法名,很容易被反编译成 Java 源代码。所以需要对java代码进行混淆。混淆就是对发布出去的程序进行重新组织和处理,混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,反编译后将难以阅读。

同时混淆的时候会遍历代码以发现没有被调用的代码,从而将其在打包成apk时剔除,最终一定程度上降低了apk的大小,比如编译后 jar 文件体积大约能减少25% 。

基础回顾

常用通配符

  • [] 表示其中内容可选
  • | 表示区分前后选项
  • <> 属性、类属性,方法、类方法,构造方法
  • ? 表示任意一个字符
  • * 表示任意多个字符(不含分隔符,filename为/,classpath为.),用在{}中表示任意属性和方法
  • ** 表示任意多个字符,可含分隔符
  • % 表示java基本数据类型(不含void),用于filed type, method returntype, method argumenttype
  • *** 表示任意类型

输入输出选项

  • -include filename 同@filename,读取目录文件中的配置
  • -basedirectory directoryname 指定配置文件的目录
  • libraryjars class_path 指定的jar不会被混淆
  • -skipnonpubliclibraryclasses 不混淆jar包中的非public的class
  • -dontskipnonpubliclibraryclasses 混淆jar包中的非public的class(默认选项)
  • dontskipnonpubliclibraryclassmemebers 混淆非public classes中的成员
  • -keepdirectories [dirrectory_filter]

Keep Options 保持不变的选项

  • -keep [,modifier,...] class_specification 保持class_specification规则;若有[,modifier,…],则先启用它的规则;保护指定的类文件和类的成员
  • -keepclassmembers [,modifier,...]class_specification 保持类的成员:属性(可以是成员属性、类属性)、方法(可以是成员方法、类方法)
  • -keepclasseswithmembers [,modifier,...] class_specification 与-keep功能基本一致(经测试),保护指定的类和类的成员,但条件是所有指定的类和类成员都要存在
  • -keepnames class_specification 保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除)
  • -keepclassmembernames class_specification 保护指定的类的成员的名称(如果他们不会压缩步骤中删除)
  • -keepclasseswithmembernames class_specification 保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)
  • -printseeds [filename] 打印匹配的-keep家族的 类和类成员列表,到标准输出
  • -keepattributes {attribute_name,...} 保护给定的可选属性,例如LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and InnerClasses.

压缩

  • -dontdhrink 不压缩输入的类文件
  • -printusage {filename}
  • -whyareyoukeeping {class_specification}

优化

  • -dontoptimize 不优化输入的类文件
  • assumenosideeffects {class_specification} 优化时假设指定的方法,没有任何副作用
  • allowaccessmodification 优化时允许访问并修改有修饰符的类和类的成员

其它

  • ignorewarnings 屏蔽警告
  • dontwarn 去掉警告
  • -optimizationpasses 5 指定代码的压缩级别
  • -dontpreverify 混淆时不做预校验
  • -dontusemixedcaseclassnames 不使用大小写混合

注意事项

  • 在AndroidManifest.xml中配置过的activity、application、service、provider、receiver不能进行混淆
  • 一些在xml中配置的view也不能进行混淆
  • 实体类由于涉及到与服务端的交互,各种gson的交互等,需要保留

实践 - 5步搞定混淆

1. 复制混淆模板

将混淆分为两个个主要部分,定制化区域(包含4个小部分)和基本不用动区域,复制如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#-------------------------------------------定制化区域----------------------------------------------
#---------------------------------1.实体类---------------------------------
#-------------------------------------------------------------------------
#---------------------------------2.第三方包-------------------------------
#-------------------------------------------------------------------------
#---------------------------------3.与js互相调用的类------------------------
#-------------------------------------------------------------------------
#---------------------------------4.反射相关的类和方法-----------------------
#----------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------------
#-------------------------------------------基本不用动区域--------------------------------------------
#---------------------------------基本指令区----------------------------------
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
-dontpreverify
-verbose
-printmapping proguardMapping.txt
-optimizations !code/simplification/cast,!field/*,!class/merging/*
-keepattributes *Annotation*,InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
#----------------------------------------------------------------------------
#---------------------------------默认保留区---------------------------------
-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
-keep class android.support.** {*;}
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-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();
}
-keep class **.R$* {
*;
}
-keepclassmembers class * {
void *(**On*Event);
}
#----------------------------------------------------------------------------
#---------------------------------webview------------------------------------
-keepclassmembers class fqcn.of.javascript.interface.for.Webview {
public *;
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, jav.lang.String);
}
#----------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------------

2. 补充-实体类

涉及到与服务端的交互,gson解析,数据库操作(GreenDao)等的实体类是要保留的。将你项目中实体类都拎出来,用以下语法进行保留。

1
-keep class 你的实体类所在的包.** { *; }

如我的项目下类User的完整路径为:com.demo.login.bean.User, 那我的混淆如下

1
2
3
4
5
#---------------------------------1.实体类---------------------------------
-keep class com.demo.login.bean.** { *; }
#-------------------------------------------------------------------------

当然你的实体类肯定不止这一个,请用上边的方式一一添加,如果你的实体类都在一个包下,那你就幸福了。

3. 补充第三方包的混淆

查看项目都依赖了哪些开源库,然后到项目的wiki上查看官方给的对应版本的混淆代码,直接复制过来即可。

已经混淆过的jar包不需要再混淆,比如百度定位的jar包已经混淆过了,可以这么写:

1
-keep class com.baidu.** {*;}

如果没有找到开源库的混淆代码,可以先保留开源库的所有类不混淆,待其它部分的混淆完成打包安装应用能正常使用后再来解决这部分的混淆问题。

4. 与js互调的类

工程中没有直接跳过。一般你可以这样写

1
-keep class 你的类所在的包.** { *; }

如果是内部类的话,你可以这样

1
-keepclasseswithmembers class 你的类所在的包.父类$子类 { <methods>; }

5. 与反射有关的类

比如gson解析部分会用到反射,需要保留对应的实体和方法。此部分可能需要进行较多的尝试,不要气馁,都是这么过来的。。

以上就是进行代码混淆的基本流程,亲测完全解决了我的问题,也希望能帮助到在学习代码混淆的童鞋们!

Android Studio也有一键生成混淆代码的插件AndroidProguardPlugin,集成了很多Android第三方开源库的代码混淆。