1,什么是混淆编译
Java代码是非常容易反编译的。为了很好的保护Java源代码,我们往往会对编译好的class文件进行混淆处理。
ProGuard是一个混淆代码的开源项目。它的主要作用就是混淆,当然它还能对字节码进行缩减体积、优化等,但那些对于我们来说都算是次要的功能。官方网址是:
http://proguard.sourceforge.net/。
混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。被混淆过的程序代码,仍然遵照原来的档案格式和指令集,执行结果也与混淆前一样,只是混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,在缺乏相应的函数名和程序注释的况下,即使被反编译,也将难以阅读。同时混淆是不可逆的,在混淆的过程中一些不影响正常运行的信息将永久丢失,这些信息的丢失使程序变得更加难以理解。
混淆器的作用不仅仅是保护代码,它也有精简编译后程序大小的作用
ProGuard是java类文件压缩,优化,混淆器.它探测并删除没有使用的类,字段,方法和属性.它删除没有用的说明并使用字节码得到最大优化.它使用无意义的名字来重命名类,字段和方法.
ProGuard的使用是为了:
1.创建紧凑的代码文档是为了更快的网络传输,快速装载和更小的内存占用.
2.创建的程序和程序库很难使用反向工程.
3.所以它能删除来自源文件中的没有调用的代码
4.充分利用java6的快速加载的优点来提前检测和返回java6中存在的类文件.
ProGuard支持那些种类的优化:
除了在压缩操作删除的无用类,字段和方法外,ProGuard也能在字节码级提供性能优化,内部方法有:
1 常量表达式求值
2 删除不必要的字段存取
3 删除不必要的方法调用
4 删除不必要的分支
5 删除不必要的比较和instanceof验证
6 删除未使用的代码
7 删除只写字段
8 删除未使用的方法参数
9 像push/pop简化一样的各种各样的peephole优化
10 在可能的情况下为类添加static和final修饰符
11 在可能的情况下为方法添加private, static和final修饰符
12 在可能的情况下使get/set方法成为内联的
13 当接口只有一个实现类的时候,就取代它
14 选择性的删除日志代码
实际的优化效果是依赖于你的代码和执行代码的虚拟机的。简单的虚拟机比有复杂JIT编译器的高级虚拟机更有效。无论如何,你的字节码会变得更小。
需要优化的不被支持技术:
1 使非final的常量字段成为内联
2 像get/set方法一样使其他方法成为内联
3 将常量表达式移到循环之外
4 Optimizations that require escape analysis
2,启动android中的混淆功能
eclipse下:
在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中不存在的代码。
Android源码平台(本人使用的是4.2.2):
在需要混淆的工程目录下(package/apps/下的工程)添加proguard.flags文件,该文件即为proguard.cfg,只是命名不一样而已,然后再Android.mk中添加如下两句:
LOCAL_PROGUARD_ENABLED := full
LOCAL_PROGUARD_FLAG_FILES := proguard.
上面的full 也可以是custom,如果不写这句,那还得添加如下一句:
TARGET_BUILD_VARIANT := user或者TARGET_BUILD_VARIANT := userdebug
这样后在工程目录下执行mm便可以看到在out目录下生成了形如proguard.classes.jar的东东,这就说明已在编译中启动了proguard
但反编译一看,并未出现网络云说的abcd替代符号,其实代码并未真正混淆:
android在编译时默认关闭了混淆选项,有去研究build/core目录的同志会发现这里也有个proguard.flags文件,其实在proguard的过程中,编译器会调用包括本地目录下和系统定义了的多个proguard.flags文件,而在这个文件中混淆的选项被禁止了,故而编译出来的apk仍未混淆。因此将如下句子注释掉便可实现真正的混淆编译:
# Don't obfuscate. We only need dead code striping.
-dontobfuscate(将该句加个#号注释掉)
为什么TARGET_BUILD_VARIANT := user和LOCAL_PROGUARD_ENABLED := full二选一即可,详见build/core/package.mk:
3,如何书写proguard.flags文件
语法:
-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} 设置源文件中给定的字符串常量
示例代码:
-optimizationpasses 5 # 指定代码的压缩级别
-dontusemixedcaseclassnames # 是否使用大小写混合
-dontskipnonpubliclibraryclasses # 是否混淆第三方jar
-dontpreverify # 混淆时是否做预校验
-verbose # 混淆时是否记录日志
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* # 混淆时所采用的算法
#忽略警告 也可以用-ignorewarnings
-dontwarn # 忽略警告,避免打包时某些警告出现
#-dontobfuscate
#声明第三方jar包
-libraryjars ../../../../../out/target/product/ac8317/system/framework/ac.jar
-libraryjars ../../../../../out/target/product/ac8317/system/framework/auto.jar
#项目使用到的第三方jar包
-libraryjars ../libs/fastjson-1.1.23.jar
-keepattributes Signature #不优化泛型和反射
-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.**{*;}
#以下为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 com.android.vending.licensing.ILicensingService
-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);
}
# 保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
-keep class MyClass; # 保持自己定义的类不被混淆
#不混淆第三方jar包中的类
-keep class android.support.v4.** {*;}
错误总结
问题一:使用gson包解析数据时,出现missing type parameter异常
程序中用到了gson的new typeToken,结果打包成apk发布时,发现抛出异常,但不通过打包apk时发现一切正常,百思不得其解,最初怀疑没有将gson-1.7.1.JAR打包进去,后来经过测试发现gson的其他方法经过打包也能正常运行,最终在google gson论坛中找到了解决方法。
第一种:在 proguard.cfg中添加
-dontobfuscate
-dontoptimize
第二种:在 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.** { *; }
这两种方法都测试可行,第一个方法没有混淆编译,第二个方法能够混淆编译
问题二:
反射类不能进行混淆编译,需加入
-keep class com.test.model.response.* {;}
问题三:
android辅助jar包异常,在proguard.cfg中加入
-dontwarn android.support.v4.**
-keep class android.support.v4.* {;}
问题四:(同1)
类型转换错误,因为我用的泛型,所以在调用某些方法的时候,会出现这种错误,后面在混淆配置文件加了一个过滤泛型的语句,如下。
-keepattributes Signature
过后,就没有出现类似的类型转换错误。
问题五:空指针异常,这个错误是我对比前面的错误来说,所用的时间比较短,开始是找不到方法到底是哪个(原因是上面提到的混淆后方法名相同),所以就把这个类里面的所有方法都过滤掉,这样我没用多少时间,也就找到了具体的方法,可还是不明白原因,后面发现了其中的一个if判断,我利用反射筛选方法,关键字是“get”,原来我 model的 set/get方法名全部都被混淆了,所以筛选不到方法,返回的也就是null值,自然下面用到这个方法的返回值就会抛出空指针异常。
解决方法:把 model包下面的所有类,全部过滤掉。
总结:如要用到反射,反射一般就会利用到泛型,所以必须要把泛型的全部过滤掉,如果有根据变量名或者方法名判断的,记得所在的类需过滤掉,之中还有用到 annotation的地方,要加入一行代码,如下:
-keepattributes Annotation
这样就能过滤掉所有的annotation,否则也会抛出空指针异常。