apk反编译(6)用ProGuard 混淆、压缩代码,压缩资源。
1.android官方文档
https://developer.android.com/studio/build/shrink-code 主要内容如下:
1.1 压缩代码
- 混淆生成的文件:
<module-name>/build/outputs/mapping/release/目录下
- 自定义要保留的代码,-keep与@keep
- 解码混淆,使用
<sdk-root>/tools/proguard/
下的工具。 - 通过 Instant Run 启用代码压缩
1.2 压缩资源
- shrinkResources
- 自定义要保留的资源
- 启用严格引用检查
- 移除未使用的备用资源
- 合并重复资源
- 排查资源压缩问题
1.3 官方proguard示例
Android SDK/tools/proguard/
proguard-android.txt
是默认混淆文件,同目录下的proguard-android-optimize.txt 是优化版本。
在proguard/examples下有很多示例,如android.pro:
1 # 2 # This ProGuard configuration file illustrates how to process Android 3 # applications. 4 # Usage: 5 # java -jar proguard.jar @android.pro 6 # 7 # If you're using the Android SDK (version 2.3 or higher), the android tool 8 # already creates a file like this in your project, called proguard.cfg. 9 # It should contain the settings of this file, minus the input and output paths 10 # (-injars, -outjars, -libraryjars, -printmapping, and -printseeds). 11 # The generated Ant build file automatically sets these paths. 12 13 # Specify the input jars, output jars, and library jars. 14 # Note that ProGuard works with Java bytecode (.class), 15 # before the dex compiler converts it into Dalvik code (.dex). 16 17 # -injars bin/classes 18 # -injars libs 19 # -outjars bin/classes-processed.jar 20 21 # -libraryjars /usr/local/android-sdk/platforms/android-9/android.jar 22 #-libraryjars /usr/local/android-sdk/add-ons/google_apis-7_r01/libs/maps.jar 23 # ... 24 25 # Save the obfuscation mapping to a file, so you can de-obfuscate any stack 26 # traces later on. 27 28 -printmapping bin/classes-processed.map 29 30 # You can print out the seeds that are matching the keep options below. 31 32 #-printseeds bin/classes-processed.seeds 33 34 # Preverification is irrelevant for the dex compiler and the Dalvik VM. 35 36 -dontpreverify 37 38 # Reduce the size of the output some more. 39 40 -repackageclasses '' 41 -allowaccessmodification 42 43 # Switch off some optimizations that trip older versions of the Dalvik VM. 44 45 -optimizations !code/simplification/arithmetic 46 47 # Keep a fixed source file attribute and all line number tables to get line 48 # numbers in the stack traces. 49 # You can comment this out if you're not interested in stack traces. 50 51 -renamesourcefileattribute SourceFile 52 -keepattributes SourceFile,LineNumberTable 53 54 # RemoteViews might need annotations. 55 56 -keepattributes *Annotation* 57 58 # Preserve all fundamental application classes. 59 60 -keep public class * extends android.app.Activity 61 -keep public class * extends android.app.Application 62 -keep public class * extends android.app.Service 63 -keep public class * extends android.content.BroadcastReceiver 64 -keep public class * extends android.content.ContentProvider 65 66 # Preserve all View implementations, their special context constructors, and 67 # their setters. 68 69 -keep public class * extends android.view.View { 70 public <init>(android.content.Context); 71 public <init>(android.content.Context, android.util.AttributeSet); 72 public <init>(android.content.Context, android.util.AttributeSet, int); 73 public void set*(...); 74 } 75 76 # Preserve all classes that have special context constructors, and the 77 # constructors themselves. 78 79 -keepclasseswithmembers class * { 80 public <init>(android.content.Context, android.util.AttributeSet); 81 } 82 83 # Preserve all classes that have special context constructors, and the 84 # constructors themselves. 85 86 -keepclasseswithmembers class * { 87 public <init>(android.content.Context, android.util.AttributeSet, int); 88 } 89 90 # Preserve the special fields of all Parcelable implementations. 91 92 -keepclassmembers class * implements android.os.Parcelable { 93 static android.os.Parcelable$Creator CREATOR; 94 } 95 96 # Preserve static fields of inner classes of R classes that might be accessed 97 # through introspection. 98 99 -keepclassmembers class **.R$* { 100 public static <fields>; 101 } 102 103 # Preserve the required interface from the License Verification Library 104 # (but don't nag the developer if the library is not used at all). 105 106 -keep public interface com.android.vending.licensing.ILicensingService 107 108 -dontnote com.android.vending.licensing.ILicensingService 109 110 # The Android Compatibility library references some classes that may not be 111 # present in all versions of the API, but we know that's ok. 112 113 -dontwarn android.support.** 114 115 # Preserve all native method names and the names of their classes. 116 117 -keepclasseswithmembernames class * { 118 native <methods>; 119 } 120 121 # Preserve the special static methods that are required in all enumeration 122 # classes. 123 124 -keepclassmembers class * extends java.lang.Enum { 125 public static **[] values(); 126 public static ** valueOf(java.lang.String); 127 } 128 129 # Explicitly preserve all serialization members. The Serializable interface 130 # is only a marker interface, so it wouldn't save them. 131 # You can comment this out if your application doesn't use serialization. 132 # If your code contains serializable classes that have to be backward 133 # compatible, please refer to the manual. 134 135 -keepclassmembers class * implements java.io.Serializable { 136 static final long serialVersionUID; 137 static final java.io.ObjectStreamField[] serialPersistentFields; 138 private void writeObject(java.io.ObjectOutputStream); 139 private void readObject(java.io.ObjectInputStream); 140 java.lang.Object writeReplace(); 141 java.lang.Object readResolve(); 142 } 143 144 # Your application may contain more items that need to be preserved; 145 # typically classes that are dynamically created using Class.forName: 146 147 # -keep public class mypackage.MyClass 148 # -keep public interface mypackage.MyInterface 149 # -keep public class * implements mypackage.MyInterface
2.proguard官方教程
https://www.guardsquare.com/en/products/proguard/manual 包含:
- sdk/tools/proguard/bin/proguardgui.sh 界面工具教程
- 混淆选项列表
- 混淆示例
- 解混淆
- Gradle plugin 配置教程
- 等等
sdk/tools/proguard/docs/index.html 是官方教程本地版本。
2.1 混淆选项列表
官方地址:https://www.guardsquare.com/en/products/proguard/manual/refcard
@ filename |
Short for '-include filename'. |
-include filename |
Read configuration options from the given file. |
-basedirectory directoryname |
Specifies the base directory for subsequent relative file names. |
-injars class_path |
Specifies the program jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). |
-outjars class_path |
Specifies the names of the output jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories). |
-libraryjars class_path |
Specifies the library jars (or apks, aabs, aars, wars, ears, jmods, zips, or directories) |
-skipnonpubliclibraryclasses |
Ignore non-public library classes. |
-dontskipnonpubliclibraryclasses |
Don't ignore non-public library classes (the default). |
-dontskipnonpubliclibraryclassmembers |
Don't ignore package visible library class members. |
-keepdirectories [directory_filter] |
Keep the specified directories in the output jars (or wars, ears, zips, or directories). |
-target version |
Set the given version number in the processed classes. |
-forceprocessing |
Process the input, even if the output seems up to date. |
-keep [,modifier,...] class_specification |
Preserve the specified classes and class members. |
-keepclassmembers [,modifier,...] class_specification |
Preserve the specified class members, if their classes are preserved as well. |
-keepclasseswithmembers [,modifier,...] class_specification |
Preserve the specified classes and class members, if all of the specified class members are present. |
-keepnames class_specification |
Preserve the names of the specified classes and class members (if they aren't removed in the shrinking step). |
-keepclassmembernames class_specification |
Preserve the names of the specified class members (if they aren't removed in the shrinking step). |
-keepclasseswithmembernames class_specification |
Preserve the names of the specified classes and class members, if all of the specified class members are present (after the shrinking step). |
-if class_specification |
Specify classes and class members that must be present to activate the subsequent |
-printseeds [filename] |
List classes and class members matched by the various to the standard output or to the given file. |
-dontshrink |
Don't shrink the input class files. |
-printusage [filename] |
List dead code of the input class files, to the standard output or to the given file. |
-whyareyoukeeping class_specification |
Print details on why the given classes and class members are being kept in the shrinking step. |
-dontoptimize |
Don't optimize the input class files. |
-optimizations optimization_filter |
The optimizations to be enabled and disabled. |
-optimizationpasses n |
The number of optimization passes to be performed. |
-assumenosideeffects class_specification |
Assume that the specified methods don't have any side effects, while optimizing. |
-assumenoexternalsideeffects class_specification |
Assume that the specified methods don't have any external side effects, while optimizing. |
-assumenoescapingparameters class_specification |
Assume that the specified methods don't let any reference parameters escape to the heap, while optimizing. |
-assumenoexternalreturnvalues class_specification |
Assume that the specified methods don't return any external reference values, while optimizing. |
-assumevalues class_specification |
Assume fixed values or ranges of values for primitive fields and methods, while optimizing. |
-allowaccessmodification |
Allow the access modifiers of classes and class members to be modified,while optimizing. |
-mergeinterfacesaggressively |
Allow any interfaces to be merged, while optimizing. |
-dontobfuscate |
Don't obfuscate the input class files. |
-printmapping [filename] |
Print the mapping from old names to new names for classes and class members that have been renamed, to the standard output or to the given file. |
-applymapping filename |
Reuse the given mapping, for incremental obfuscation. |
-obfuscationdictionary filename 可自定义混淆字符 |
Use the words in the given text file as obfuscated field names and method names. |
-classobfuscationdictionary filename |
Use the words in the given text file as obfuscated class names. |
-packageobfuscationdictionary filename |
Use the words in the given text file as obfuscated package names. |
-overloadaggressively |
Apply aggressive overloading while obfuscating. |
-useuniqueclassmembernames |
Ensure uniform obfuscated class member names for subsequent incremental obfuscation. |
-dontusemixedcaseclassnames |
Don't generate mixed-case class names while obfuscating. |
-keeppackagenames [package_filter] |
Keep the specified package names from being obfuscated. |
-flattenpackagehierarchy [package_name] |
Repackage all packages that are renamed into the single given parent package. |
-repackageclasses [package_name] |
Repackage all class files that are renamed into the single given package. |
-keepattributes [attribute_filter] |
Preserve the given optional attributes; typically
|
-keepparameternames |
Keep the parameter names and types of methods that are kept. |
-renamesourcefileattribute [string] |
Put the given constant string in the SourceFile attributes. |
-adaptclassstrings [class_filter] |
Adapt string constants in the specified classes, based on the obfuscated names of any corresponding classes. |
-adaptresourcefilenames [file_filter] |
Rename the specified resource files, based on the obfuscated names of the corresponding class files. |
-adaptresourcefilecontents [file_filter] |
Update the contents of the specified resource files, based on the obfuscated names of the processed classes. |
-dontpreverify |
Don't preverify the processed class files. |
-microedition |
Target the processed class files at Java Micro Edition. |
-android |
Target the processed class files at Android. |
-verbose |
Write out some more information during processing. |
-dontnote [class_filter] |
Don't print notes about potential mistakes or omissions in the configuration. |
-dontwarn [class_filter] |
Don't warn about unresolved references at all. |
-ignorewarnings |
Print warnings about unresolved references, but continue processing anyhow. |
-printconfiguration [filename] |
Write out the entire configuration, in traditional ProGuard style, to the standard output or to the given file. |
-dump [filename] |
Write out the internal structure of the processed class files, to the standard output or to the given file. |
-addconfigurationdebugging |
Instrument the processed code with debugging statements that print out suggestions for missing ProGuard configuration. |
2.2 混淆选项通配符
Keep 选项后面的匹配条件中,经常需要用到通配符,例如上面默认 proguard-android.txt 规则中的 void set*(***);。
常见的
通配符如下:
通配符 | 描述 |
---|---|
<init> |
匹配所有构造函数 |
<fields> |
匹配所有字段 |
<methods> |
匹配所有方法,不包括构造函数 |
? | 匹配任意单个字符 |
% | 匹配任意原始数据类型,例如 boolean、int,但是不包括 void |
* | 匹配任意长度字符,但是不包括包名分隔符( . ),例如 android.support.* 不匹配 android.support.annotation.Keep |
** | 匹配任意长度字符,包括包名分隔符( . ),例如 android.support.** 匹配 support 包下的所有类 |
*** | 匹配任意类型,包括原始数据类型、数组 |
… | 匹配任意数量的任意参数类型 |
2.3 解混淆
- debug版本
如果有异常,直接点击行号就可以了。下图第25行。
- release版本:
找到对应版本的mapping.txt以及对应的异常日志,如果没有日志可以从android studio logcat中复制,
1.打开ProGuard图形工具,选择左侧ReTrace 栏
2.在右侧Mapping file中找到对应的mapping.txt
3.把异常的堆栈信息复制到Obfuscated stack track中,或者点Load stack trace...按钮找到对应的文件。
4.点右下角ReTrace!按钮。
3.哪些不应该混淆
对于某些情况,ProGuard 会移除所有(并且只会移除)未使用的代码。不过,ProGuard 难以对许多情况进行正确分析,可能会移除应用真正需要的代码。
不该混淆 |
原因 |
当应用引用的类只来自 AndroidManifest.xml 文件时 |
混淆处理之后,类名就会被篡改,实际使用的类与 如android常用4组件和Application类 |
当应用调用的方法来自 Java 原生接口 (JNI) 时 |
混淆后,会找不对对应的方法的实现 |
当应用在运行时(例如使用反射或自检)操作代码时 |
混淆后,找不到相应的名字,经常发生 |
序列化的类 |
经过混淆的"洗礼"之后,序列化之后的 同时,反序列化的过程创建对象从根本上来说还是借助于反射,混淆之后 所以也会违背我们预期的效果。 |
枚举 |
枚举类内部存在 Android 系统默认的混淆规则文件中已经添加了对于枚举类的处理,无需再去做额外工作。 |
自定义控件不需要被混淆 |
view的一些回调方法会被修改,导致出错或者显示不正常。 |
JavaScript 调用 Java 的方法不应混淆 |
容易找不到方法 |
第三方库也不建议混淆 |
通常第3库以jar或者so方式存在,工具很难分析其中的调用关系。容易出错。 |
注解不能混淆 |
很多场景下注解被用作在运行时反射确定一些元素的特征,Android工程默认的混淆配置 已经包含了保留注解的配置。 |
4.自定义混淆字典
4.1 以自定义混淆字典选项
-obfuscationdictionary
filename 混淆属性名、方法名参考的字典-classobfuscationdictionary
filename 混淆类参考的字典-packageobfuscationdictionary
filename 混淆包名参考的字典
4.2 混淆字典规则
- 一行一个单词
- 空行、空格忽略,
- 标点符号无效
- 重复的字符被忽略
#
后面的字符忽略- 混淆属性和方法时,最好使用类对应的class文件中经存在的字符
4.3 android提供的字典示例
在sdk/tools/proguard/examples/dictionaries/ 目录下有字典示例:
4.4 使用混淆字典
可把一份字典示例文件(如 windows.txt)或者自定义一个字典文件复制到项目中,与proguard-rules.pro同级。
在proguard-rules.pro中添加混淆选项,然后开始生成apk。
1 -obfuscationdictionary windows.txt 2 -classobfuscationdictionary windows.txt 3 -packageobfuscationdictionary windows.txt
如:
5.实例
5.1 自定义要保留的类或包
打完包后,用android studio分析下它。打开其中的dex文件,选择要保留的类,或者包 -> 右键 -> Generate Proguard keep rule
复制其中的内容到 proguard-rules.pro 下。
5.2 配置默认android混淆选项
把 sdk/tools/proguard/examples/android.pro 内容复制到 proguard-rules.pro中
把不必要的选项关掉。
5.3 打开混淆开关
在release配置里打开混淆开关
1 ... 2 buildTypes { 3 release { 4 minifyEnabled true 5 shrinkResources true 6 zipAlignEnabled = true 7 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 8 } 9 debug{ 10 minifyEnabled false 11 shrinkResources false 12 // useProguard false 13 zipAlignEnabled = true 14 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 15 } 16 } 17 ...
5.4 混淆走掉 Log.e 等
1 #-dontoptimize 2 -assumenosideeffects class android.util.Log { 3 public static boolean isLoggable(java.lang.String, int); 4 public static int v(...); 5 public static int i(...); 6 public static int w(...); 7 public static int d(...); 8 public static int e(...); 9 }