(2.2.8.5) Android proguard 详解和常见错误

时间:2021-02-25 04:37:23

Java代码是非常容易反编译的。为了很好的保护Java源代码,我们往往会对编译好的class文件进行混淆处理。

ProGuard是一个混淆代码的开源项目。它的主要作用就是混淆,当然它还能对字节码进行缩减体积、优化等,但那些对于我们来说都算是次要的功能。官网网址是:

http://proguard.sourceforge.net/

我现在用的AndroidStudio , 只需要在 build.grade 的配置文件中配置如下即可:

buildTypes {
debug {
versionNameSuffix ".dev"
}

release {
debuggable false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}

1-原理

Java 是一种跨平台的、解释型语言,Java 源代码编译成中间”字节码”存储于 class 文件中。由于跨平台的需要,Java 字节码中包括了很多源代码信息,如变量名、方法名,并且通过这些名称来访问变量和方法,这些符号带有许多语义信息,很容易被反编译成 Java 源代码。为了防止这种现象,我们可以使用 Java 混淆器对 Java 字节码进行混淆。

混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。被混淆过的程序代码,仍然遵照原来的档案格式和指令集,执行结果也与混淆前一样,只是混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,在缺乏相应的函数名和程序注释的况下,即使被反编译,也将难以阅读。同时混淆是不可逆的,在混淆的过程中一些不影响正常运行的信息将永久丢失,这些信息的丢失使程序变得更加难以理解。

混淆器的作用不仅仅是保护代码,它也有精简编译后程序大小的作用。由于以上介绍的缩短变量和函数名以及丢失部分信息的原因, 编译后 jar 文件体积大约能减少25% ,这对当前费用较贵的无线网络传输是有一定意义的。

2-语法

-include {filename} 从给定的文件中读取配置参数 
-basedirectory {directoryname} 指定基础目录为以后相对的档案名称
-injars {class_path} 指定要处理的应用程序jar,war,ear和目录
-outjars {class_path} 指定处理完后要输出的jar,war,ear和目录的名称
-libraryjars {classpath} 指定要处理的应用程序jar,war,ear和目录所需要的程序库文件
-dontskipnonpubliclibraryclasses 指定不去忽略非公共的库类。
-dontskipnonpubliclibraryclassmembers 指定不去忽略包可见的库类的成员。

保留选项
-keep {Modifier} {class_specification} 保护指定的类文件和类的成员
-keepclassmembers {modifier} {class_specification} 保护指定类的成员,如果此类受到保护他们会保护的更好
-keepclasseswithmembers {class_specification} 保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。
-keepnames {class_specification} 保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除)
-keepclassmembernames {class_specification} 保护指定的类的成员的名称(如果他们不会压缩步骤中删除)
-keepclasseswithmembernames {class_specification} 保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)
-printseeds {filename} 列出类和类的成员-keep选项的清单,标准输出到给定的文件

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

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

混淆
-dontobfuscate 不混淆输入的类文件
-printmapping {filename}
-applymapping {filename} 重用映射增加混淆
-obfuscationdictionary {filename} 使用给定文件中的关键字作为要混淆方法的名称
-overloadaggressively 混淆时应用侵入式重载
-useuniqueclassmembernames 确定统一的混淆类的成员名称来增加混淆
-flattenpackagehierarchy {package_name} 重新包装所有重命名的包并放在给定的单一包中
-repackageclass {package_name} 重新包装所有重命名的类文件中放在给定的单一包中
-dontusemixedcaseclassnames 混淆时不会产生形形色色的类名
-keepattributes {attribute_name,...} 保护给定的可选属性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and

InnerClasses.
-renamesourcefileattribute {string} 设置源文件中给定的字符串常量

demo 实例

-ignorewarnings # 忽略警告,避免打包时某些警告出现
-optimizationpasses 5 # 指定代码的压缩级别
-dontusemixedcaseclassnames # 是否使用大小写混合
-dontskipnonpubliclibraryclasses # 是否混淆第三方jar
-dontpreverify # 混淆时是否做预校验
-verbose # 混淆时是否记录日志
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* # 混淆时所采用的算法

-libraryjars libs/treecore.jar

-dontwarn android.support.v4.** #缺省proguard 会检查每一个引用是否正确,但是第三方库里面往往有些不会用到的类,没有正确引用。如果不配置的话,系统就会报错。
-dontwarn android.os.**
-keep class android.support.v4.** { *; } # 保持哪些类不被混淆
-keep class com.baidu.** { *; }
-keep class vi.com.gdi.bgl.android.**{*;}
-keep class android.os.**{*;}

-keep interface android.support.v4.app.** { *; }
-keep public class * extends android.support.v4.**
-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.content.ContentProvider
-keep public class * extends android.support.v4.widget
-keep public class * extends com.sqlcrypt.database
-keep public class * extends com.sqlcrypt.database.sqlite
-keep public class * extends com.treecore.**
-keep public class * extends de.greenrobot.dao.**


-keepclasseswithmembernames class * { # 保持 native 方法不被混淆
native <methods>;
}

-keepclasseswithmembers class * { # 保持自定义控件类不被混淆
public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembers class * { # 保持自定义控件类不被混淆
public <init>(android.content.Context, android.util.AttributeSet, int);
}

-keepclassmembers class * extends android.app.Activity { //保持类成员
public void *(android.view.View);
}

-keepclassmembers enum * { # 保持枚举 enum 类不被混淆
public static **[] values();
public static ** valueOf(java.lang.String);
}

-keep class * implements android.os.Parcelable { # 保持 Parcelable 不被混淆
public static final android.os.Parcelable$Creator *;
}

-keep class MyClass; # 保持自己定义的类不被混淆

3-文件

在release模式下打包apk时会自动运行ProGuard,这里的release模式指的是通过ant release命令或eclipse project->Android tools->export signed(unsigned)
application package生成apk。
在debug模式下为了更快调试并不会调用proguard。

如果是ant命令打包apk,proguard信息文件会保存于/bin/proguard文件夹内;
如果用eclipse export命令打包,会在/proguard文件夹内。其中包含以下文件:

  • mapping.txt
    表示混淆前后代码的对照表,这个文件非常重要。如果你的代码混淆后会产生bug的话,log提示中是混淆后的代码,希望定位到源代码的话就可以根据mapping.txt反推。
    每次发布都要保留它方便该版本出现问题时调出日志进行排查,它可以根据版本号或是发布时间命名来保存或是放进代码版本控制中。

  • dump.txt
    描述apk内所有class文件的内部结构。

  • seeds.txt
    列出了没有被混淆的类和成员。

  • usage.txt
    列出了源代码中被删除在apk中不存在的代码。

4-Android 混淆原则

  • 反射用到的类不混淆
  • JNI方法不混淆
  • AndroidMainfest中的类不混淆,四大组件和Application的子类和Framework层下所有的类默认不会进行混淆
  • Parcelable的子类和Creator静态成员变量不混淆,否则会产生android.os.BadParcelableException异常
  • 使用GSON、fastjson等框架时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象
  • 使用第三方开源库或者引用其他第三方的SDK包时,需要在混淆文件中加入对应的混淆规则
  • 有用到WEBView的JS调用也需要保证写的接口方法不混淆

先看看google默认混淆文件: \sdk\tools\proguard\proguard-android.txt

-keepattributes *Annotation*//使用注解需要添加
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {//指定不混淆所有的JNI方法
native <methods>;
}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {//所有View的子类及其子类的get、set方法都不进行混淆
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 {//不混淆Activity中参数类型为View的所有方法
public void *(android.view.View);
}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {//不混淆Enum类型的指定方法
public static **[] values();
public static ** valueOf(java.lang.String);
}
//不混淆Parcelable和它的子类,还有Creator成员变量
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
//不混淆R类里及其所有内部static类中的所有static变量字段
-keepclassmembers class **.R$* {
public static <fields>;
}

# The support library 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.
-dontwarn android.support.**//不提示兼容库的错误警告

如果加入一些自己的混淆规则 只需要在 proguard-rules.pro 中文件加入自己的混淆规则即可,

其实google以及给我提供很好的打包规则, 即proguard-rules.pro 啥也不写, 我打出来的release包也是混淆好的

但是我们会遇到一些情况, 不得不 添加自己的混淆规则:

  1. 代码中使用了反射,如一些ORM框架的使用
    需要保证类名 方法不变, 不然混淆后, 就反射不了
  2. 使用GSON、fastjson等JSON解析框架所生成的对象类
    生成的bean实体对象,内部大多是通过反射来生成, 不能混淆
  3. 引用了第三方开源框架或继承第三方SDK,如开源的okhttp网络访问框架,百度定位SDK等
    在这些第三库的文档中 一班会给出 相应的 混淆规则, 复制过来即可
  4. 有用到WEBView的JS调用接口
    没真么用过这块, 不是很熟, 网上那个看到的
  5. 继承了Serializable接口的类
    在反序列画的时候, 需要正确的类名等, 在Android 中大多是实现 Parcelable来序列化的

4-1 如果用到了反射需要加入 :

-keepattributes Signature
-keepattributes EnclosingMethod

4-2 如果想让一些bean 对象不混淆, 里 com.czy.bean 包下面的全是 Json框架生成的bean对象, 那么只需加入:

-keep class czy.**{*;}//不混淆所有的com.czy.bean包下的类和这些类的所有成员变量

4-3 继承了Serializable接口的类,需要加上:

//不混淆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();
}

4-4 关于第三方的库的, 一班都是看他们的官方文档

4-5 有用到WEBView的JS调用接口,需加入如下规则:

-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
-keep class com.xxx.xxx.** { *; }//保持WEB接口不被混淆 此处xxx.xxx是自己接口的包名

5- bug(常见错误)

5-1 Proguard returned with error code 1. See console

  1. 更新proguard版本
  2. android-support-v4 不进行混淆
  3. 添加缺少相应的库

5-2 使用gson包解析数据时,出现missing type parameter异常

1、在 proguard.cfg中添加

-dontobfuscate
-dontoptimize

2、在 proguard.cfg中添加

# removes such information by default, so configure it to keep all of it.
-keepattributes Signature


# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }


# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }

5-3类型转换错误

-keepattributes Signature

5-4 空指针异常

混淆过滤掉相关类与方法

5-5安卓代码混淆与反射冲突,地图无法显示等问题解决及反编译方法,安卓反编译

此前的代码混淆,因为并没有用到反射,所以常规的代码混淆方式一遍就能通过,而此项目中某些类利用到了反射机制(本人的这个项目中有即时通讯功能,所以有表情类资源,因此需要通过反射由文件名找到表情资源id),当由文件名去寻找资源id时就报空指针异常了,期初我并不知道什么原因,通过反编译已经混淆的apk,一步一步寻找到出错的地方,才恍然大悟,正是反射那一步出现了问题:Field field = R.drawable.class.getDeclaredField(name);走到这一步就挂了,程序直接崩溃。

解决办法:
1.在proguard.cfg文件中,将反射用到的类中的变量不被混淆:
如:-keep public class com.byl.bean.Expressions { *; },表示Expressions 这个类及类中的所有变量及方法不被混淆,注意要写全路径;
2.过滤泛型:-keepattributes Signature
3.最重要的一点:保持R文件不被混淆,否则,你的反射是获取不到资源id的:-keep class *.R$ {*;}

补充一下:上个问题解决后,接下来又遇到了一个问题就是混淆后,地图无法正常使用了,博主使用的是百度地图,在proguard.cfg也已经写明了:
-keep class com.baidu.* {;}
-keep class vi.com.* {;}

5-6 android.provider.Settings$Global

# Project target.
target=android-19

5-7 java.lang.reflect.UndeclaredThrowableException

-keep interface com.dev.impl.**

5-8 内存溢出和无法写入堆栈

javaOptions in proguard := Seq(…)
or
javaOptions in (Proguard, proguard) := Seq(…)

5-9 Error: Unable to access jarfile ..\lib\proguard.jar

路径问题

5-10 java.lang.NoSuchMethodError

没有相关方法,方法被混淆了,混淆过滤掉相关方法便可。

5-11 专业网站bug解决方法

http://proguard.sourceforge.net/index.html#manual/troubleshooting.html