Gradle中ProGuard的配置

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载自夜明的孤行灯

本文链接地址: https://www.huangyunkun.com/2013/12/23/gradle_with_proguard/

好久没有写博客了…元旦前赶紧写一篇吧…

这些日子琢磨了一下gradle。对比起maven确实在配置上灵活很多,对groovy的支持可以更容易的自定义任务。

由于最近的几个项目中都使用到了moco这个开源项目,它使用gradle管理,使用命令gradle uberjar可以生成一个独立运行包,这个包有8M大。我使用的环境比较特殊…8M有点大了,就琢磨这使用ProGuard给它瘦个身,效果不错,简单配置以后大小变成了4.6M,只有原来的57%了。

在配置的过程中遇到了很多难题,google之后都没有什么中文参考,故记录下了这次尝试,分享给大家。

ProGuard简介

ProGuard是一个压缩、优化和混淆Java 字节码文件的软件。它可以删除无用的类、字段、方法和属性。还可以删除没用的注释,优化 字节码文件。它还可以使用简短的无意义的名称来重命名已经存在的类、字段、方法和属性。

我最开始接触ProGuard是在Android的开发中,这个工具已经成为标配了。其实ProGuard一样可以用于一般的Java项目。它的主要功能有三块:压缩,优化,混淆。其中压缩是删除没有用到的类等,当你的项目使用到了大量开源库的时候这个功能尤其明显。这也是本文主要用到的功能。优化可以去除一些无用参数,还可以进行一些代码内联。混淆功能是将包名、类名、方法名等等用无意义的字符替换掉,可以增加反编译的难度,保护你的代码。混淆也可以减少包的大小,但是混淆后不利于调试。

ProGuard的配置

大部分的工具都有配置,如果你没有太大的需求,一般可以直接使用。而ProGuard的配置是不可缺少的,特别是刚刚接触这个工具的时候,我觉得这个配置完全是自己写不出来的,我是针对每一个用到的包去搜索复制相关配置。后面慢慢发现其实还好,了解了原理以后就可以自己配置了。

ProGuard会从你给出的入口点切入开始分析,一般是程序的main方法,如果某个类或者方法没有时候就会被去除。然而有时候程序中用到了某些类是通过配置文件或者反射等方法得到的,这种情况ProGuard可能就会误删。虽然目前ProGuard可以分析一些简单的情况,但是更多的时候还是需要自己配置。

在Gradle中使用ProGuard

ProGuard提供了Gradle插件,这样我们可以更方便的将ProGuard集成进来了。当前最新版是4.10。

这个插件的介绍页面我死活打不开了…主要的参考也是其他项目的配置。

先来看看如果在Gradle中生成一个独立包。这个包需要包含自身的源码、资源还有所有的第三方依赖。如果你的项目是一个多项目,那么你的独立包还应该其他需要的子项目,一般情况下配置如下:

task uberjar(type: Jar, dependsOn: jar) {
classifier ='standalone'
from files(sourceSets.main.output.classesDir)
from files(sourceSets.main.output.resourcesDir)
fromconfigurations.runtime.asFileTree.files.collect { zipTree(it) }

manifest {
attributes'Main-Class': YOUR MAINCLASS,
'Implementation-Title':"${project.name}",
'Implementation-Version':"${version}",
'Implementation-Vendor': YOUR NAME,
'Built-Date':new Date().getDateTimeString(),
'Built-With':"gradle-${project.getGradle().getGradleVersion()},groovy-${GroovySystem.getVersion()}",
'Created-By':'Java ' + System.getProperty('java.version') +' (' + System.getProperty('java.vendor') +')'
}
}

更多的细节可以查看Gradle文档中关于Jar任务的描述。

对于一个任务,首先声明这个项目的类型,它依赖于哪个项目。其次就是生成的jar的名称,名称规则为

${baseName}-${appendix}-${version}-${classifier}.${extension}

第一句配置就是将classifier指定为standalone。

第二三句指明该Jar文件包含项目源代码和资源文件。

第四句将所有运行时所用到的第三方库等等打包进来了。

后面的是manifest的配置。

如果你是多项目的,那么就需要添加依赖子项目的classesDir和resourcesDir。

以上的配置基本可用,但如果你仔细看看META-INF文件夹就有点小问题了,如果你使用了库比较多,那么你将会有很多重复的NOTICE、LICENSE文件在这个包里面,这样会对ProGurad的使用造成问题,比如错误【proguard can’t write resource duplicate zip entry】。

一般我推荐加上以下两句:

duplicatesStrategy = DuplicatesStrategy.EXCLUDE
exclude('META-INF/maven/**')

第一句是忽略重复文件,第二句是排除maven目录。

生成独立包以后就可以使用ProGuard来处理了,任务类型是:proguard.gradle.ProGuardTask,依赖刚才的uberjar任务。

首先在Gradle的配置中添加buildscript,在其中指明插件依赖:

buildscript{
repositories{
mavenCentral()
}
dependencies{
classpath'net.sf.proguard:proguard-gradle:4.10'
}
}

ProGuardTask最主要的参数是injars和outjars,分别指定输入文件和输出文件,其他的细节配置可以在proguard.pro中定义。

task proguard(type: proguard.gradle.ProGuardTask,dependsOn:uberjar) {
ext{
jar.classifier="standalone"
injar= jar.archivePath
jar.classifier="proguard"
outJar= jar.archivePath
}
injars injar
outjars outJar
configuration 'proguard.pro'
}

ext中的配置主要是获取输入文件和输出文件的路径,以作为injars和outjars的参数。

在proguard.pro文件中我们一般需要指定rt.jar所在,如果出现【can’t find referenced class javax.crypto】错误,那么你还需要指定jce所在。一般配置如下:

-libraryjars <java.home>/lib/rt.jar
-libraryjars <java.home>/lib/jce.jar
-printusage shrinking.outpu

-dontobfuscate
-dontoptimize

-keepattributes *Annotation*,EnclosingMethod

-keep public class com.github.dreamhead.moco.bootstrap.Main {
public static void main(java.lang.String[]);
}

第三句是输出ProGuard去除了哪些类和方法,以方便排错。后面两句是制定不混淆、不优化,这样ProGuard只会为我们压缩包。接下来的配置就比较好说了,一般有两种选择:

1.直接运行,针对具体的报错进行处理

2.依次搜索你用到的第三方库,看看有没有配置好的。

比如Google的Guava库,官方就有说明,需要一个jsr305包和一个javax.inject包,你可以使用参数libraryjars提供这两个包,也可以直接简单的使用-dontwarn com.google.**忽略掉这些。

如果你使用到了注解,那么推荐你添加

-keepattributes*Annotation*,EnclosingMethod

如果你使用Json的相关库,那么最好保护一下你的model类和相关的get和setter方法,添加

-keep class modal.**{void set*(***);*** get*();}
-keepclassmembers class model.** {public<fields>;}

如果使用到了枚举的话可以考虑

-keepclassmembersenum * {
publicstatic **[]values();
publicstatic **valueOf(java.lang.String);
}

如果使用了native相关的,那么必须添加

-keepclasseswithmembernames class * {
native <methods>;
}

如果使用了日志工具,比如apache common logging、logback等,那么你需要保护好你配置文件中指定的记录器,比如

-keeppublicclass org.apache.commons.logging.impl.**{*;}
-keeppublicclass org.slf4j.** {*;}
-keeppublicclass ch.** {*;}

其他的问题就根据情况配置了,一般情况下用不要轻易使用dontwarn这个配置,因为它只是屏蔽了错误,并没有解决问题。当然,如果你觉得你的思路足够清晰的话那么放心的使用。

Gradle中测试ProGuard的结果

ProGuard输出结果是一个jar包,有时候很难说输出的结果是完全正确的,这个时候我们需要对压缩过的包进行测试。

直接使用项目本身的单元测试是最好的选择,Gradle的Test任务支持Junit和TestNG。而我们要做的就是自定义一个Test任务,保留测试的内容,将源代码和第三方库移除,然后将我们压缩过的包链接过去,这样我们的测试就是测试生成好的Jar包,而不是源代码了。

相关配置如下:

task proguardCheck(type:Test,dependsOn:proguard){
ext{
jar.classifier="proguard"
outJar= jar.archivePath
}
testLogging { exceptionFormat"full" }
classpath =classpath-files(sourceSets.main.output.classesDir)-files(configurations.runtime)+files(outJar)
}

配置中的ext块获取到了压缩好的Jar包的位置。classpath是我们测试时用到的东西,在这里通过

-files(sourceSets.main.output.classesDir)

移除项目本身的源代码。再通过

-files(configurations.runtime)

移除依赖的第三方库。

最后通过

+files(outJar)

将我们压缩好的Jar包链接过去。

这样的话我们就可以在修改proguard.pro配置以后运行这个测试来检查压缩后的包时候正常工作。

Mac OS下的兼容问题

因为proguard的配置中我们配置了rt.jar,jce.jar,当然如果有需要你还可以添加jsse.jar等等,嫌麻烦的话可以只提供rt.jar。

但是Mac Os中的java可能不是包含的rt.jar,而有可能是classes.jar。这个时候就需要判断一下环境了。

在Gradle可以通过

if(System.properties["os.name"].toLowerCase().contains("mac"))
{
if(!newFile(javaBase+javaRt).exists())
{
...do something
}
}

来处理一下针对mac os的情况,比如

javaBase=System.properties["java.home"]
javaRt="/lib/rt.jar"
if(System.properties["os.name"].toLowerCase().contains("mac"))
{
javaRt="/../Classes/classes.jar"
}
libraryjars javaBase+javaRt

首先获取到java home路径,然后指定runtime.jar的相对位置,如果是mac平台的话就指定其他路径,最后拼接就可以了。

这个方法可以解决一部分问题…

我测试的时候找了三台不同的mac机器,发现有两台用不起。它们的文件系统没有classes.jar,却有rt.jar。

仔细查看才发现,这两台机器装的Oracle的Java7…有rt.jar而且路径和其他平台一样的。

修改为

javaBase=System.properties["java.home"]
javaRt="/lib/rt.jar"
if(System.properties["os.name"].toLowerCase().contains("mac"))
{
if(!new File(javaBase+javaRt).exist()){
javaRt="/../Classes/classes.jar"
}
}
libraryjars javaBase+javaRt

这样应该可以解决这个问题了。

参考

最后附上几个参考链接:

http://proguard.sourceforge.net/index.html

https://github.com/dreamhead/moco/blob/master/build.gradle

https://github.com/MinecraftForge/Installer/blob/master/build.gradle

https://github.com/deengames/Freedom/blob/master/proguard.cfg

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载自夜明的孤行灯

本文链接地址: https://www.huangyunkun.com/2013/12/23/gradle_with_proguard/

发表评论