可能是最通用全面的Android(安卓)studio打包jar方法
现状
网上关于Android studio打包jar的教程很多,基本思路如下
- 项目
build.gradle
中增加一个Jar任务, - 指定打包路径。如下:
task buildJar(dependsOn: ['assembleDebug'], type: Jar) { .... def srcClassDir = [project.buildDir.absolutePath + "/intermediates/classes/debug"]; from srcClassDir include "**/*.class" ....}
这样做个人觉得有几个问题:
只能给当前项目应用module打包
/intermediates/classes/debug
对于依赖的aar,如support v7,编译输出class是在
/intermediates/exploded-aar/
对于依赖的jar包,目测在intermediates
中根本找不到不能混淆,当然你也可以在
build.gradle
写一个ProGuardTask,具体可参见这篇文章,这里直接复制其最终生成build.gradle
如下:
import com.android.build.gradle.AppPlugin import com.android.build.gradle.LibraryPlugin import proguard.gradle.ProGuardTask apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { applicationId "org.chaos.demo.jar" minSdkVersion 19 targetSdkVersion 22 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } //dependsOn 可根据实际需要增加或更改 task buildJar(dependsOn: ['compileReleaseJavaWithJavac'], type: Jar) { appendix = "demo" baseName = "androidJar" version = "1.0.0" classifier = "release" //后缀名 extension = "jar" //最终的 Jar 包名,如果没设置,默认为 [baseName]-[appendix]-[version]-[classifier].[extension] archiveName = "AndroidJarDemo.jar" //需打包的资源所在的路径集 def srcClassDir = [project.buildDir.absolutePath + "/intermediates/classes/release"]; //初始化资源路径集 from srcClassDir //去除路径集下部分的资源 // exclude "org/chaos/demo/jar/MainActivity.class" // exclude "org/chaos/demo/jar/MainActivity\$*.class" // exclude "org/chaos/demo/jar/BuildConfig.class" // exclude "org/chaos/demo/jar/BuildConfig\$*.class" // exclude "**/R.class" // exclude "**/R\$*.class" //只导入资源路径集下的部分资源 include "org/chaos/demo/jar/**/*.class" //注: exclude include 支持可变长参数 } task proguardJar(dependsOn: ['buildJar'], type: ProGuardTask) { //Android 默认的 proguard 文件 configuration android.getDefaultProguardFile('proguard-android.txt') //manifest 注册的组件对应的 proguard 文件 configuration project.buildDir.absolutePath + "/intermediates/proguard-rules/release/aapt_rules.txt" configuration 'proguard-rules.pro' String inJar = buildJar.archivePath.getAbsolutePath() //输入 jar injars inJar //输出 jar outjars inJar.substring(0, inJar.lastIndexOf('/')) + "/proguard-${buildJar.archiveName}" //设置不删除未引用的资源(类,方法等) dontshrink Plugin plugin = getPlugins().hasPlugin(AppPlugin) ? getPlugins().findPlugin(AppPlugin) : getPlugins().findPlugin(LibraryPlugin) if (plugin != null) { List runtimeJarList if (plugin.getMetaClass().getMetaMethod("getRuntimeJarList")) { runtimeJarList = plugin.getRuntimeJarList() } else if (android.getMetaClass().getMetaMethod("getBootClasspath")) { runtimeJarList = android.getBootClasspath() } else { runtimeJarList = plugin.getBootClasspath() } for (String runtimeJar : runtimeJarList) { //给 proguard 添加 runtime libraryjars(runtimeJar) } } }
看起来真不太舒服不是?(无意冒犯)
- 对于一个强迫症的程序员,除了代码要整洁之外,编译脚本文件
build.gradle
不整洁也不能忍
apply plugin: 'com.android.application'apply plugin: 'jar-gradle-plugin'android { compileSdkVersion 24 buildToolsVersion "24.0.0" defaultConfig { applicationId "com.adison.testjarplugin" minSdkVersion 15 targetSdkVersion 24 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }}dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:24.0.0' compile 'com.android.support:design:24.0.0'}BuildJar{ //输出目录 outputFileDir= project.buildDir.path+"/jar" //输出原始jar包名 outputFileName="test.jar" //输出混淆jar包名 outputProguardFileName="test_proguard.jar" //混淆配置 proguardConfigFile="proguard-rules.pro" //是否需要默认的混淆配置proguard-android.txt needDefaultProguard=true}
这样感觉是不是好些了哈
实践
关于第一个问题,我们可以利用Android Transform Task解决,其官方说明如下:
Starting with 1.5.0-beta1, the Gradle plugin includes a Transform API allowing 3rd party plugins to manipulate compiled class files before they are converted to dex files.(The API existed in 1.4.0-beta2 but it’s been completely revamped in 1.5.0-beta1)
可见Transform Task的输入文件肯定包含apk所有依赖class及其本身class,我们只要取得其输入文件就行了
关于第三个问题,我们写一个Gradle插件,把业务逻辑都交给插件处理就好了,关于Gradle及自定义Gradle插件可以参考Gradle深入与实战系列文章,在此不展开说明。废话不多说,直接上插件代码:
class BuildJarPlugin implements Plugin<Project> { public static final String EXTENSION_NAME = "BuildJar"; @Override public void apply(Project project) { DefaultDomainObjectSet variants if (project.getPlugins().hasPlugin(AppPlugin)) { variants = project.android.applicationVariants; project.extensions.create(EXTENSION_NAME, BuildJarExtension); applyTask(project, variants); } } private void applyTask(Project project, variants) { project.afterEvaluate { BuildJarExtension jarExtension = BuildJarExtension.getConfig(project); def includePackage = jarExtension.includePackage def excludeClass = jarExtension.excludeClass def excludePackage = jarExtension.excludePackage def excludeJar = jarExtension.excludeJar variants.all { variant -> if (variant.name.capitalize() == "Debug") { def dexTask = project.tasks.findByName(BuildJarUtils.getDexTaskName(project, variant)) if (dexTask != null) { def buildJarBeforeDex = "buildJarBeforeDex${variant.name.capitalize()}" def buildJar = project.tasks.create("buildJar", Jar) buildJar.setDescription("构建jar包") Closure buildJarClosure = { //过滤R文件和BuildConfig文件 buildJar.exclude("**/BuildConfig.class") buildJar.exclude("**/BuildConfig\$*.class") buildJar.exclude("**/R.class") buildJar.exclude("**/R\$*.class") buildJar.archiveName = jarExtension.outputFileName buildJar.destinationDir = project.file(jarExtension.outputFileDir) if (excludeClass != null && excludeClass.size() > 0) { excludeClass.each { //排除指定class buildJar.exclude(it) } } if (excludePackage != null && excludePackage.size() > 0) { excludePackage.each { //过滤指定包名下class buildJar.exclude("${it}/**/*.class") } } if (includePackage != null && includePackage.size() > 0) { includePackage.each { //仅仅打包指定包名下class buildJar.include("${it}/**/*.class") } } else { //默认全项目构建jar buildJar.include("**/*.class") } } project.task(buildJarBeforeDex) << { Set inputFiles = BuildJarUtils.getDexTaskInputFiles(project, variant, dexTask) inputFiles.each { inputFile -> def path = inputFile.absolutePath if (path.endsWith(SdkConstants.DOT_JAR) && !BuildJarUtils.isExcludedJar(path, excludeJar)) { buildJar.from(project.zipTree(path)) } else if (inputFile.isDirectory()) { //intermediates/classes/debug buildJar.from(inputFile) } } } def buildProguardJar = project.tasks.create("buildProguardJar", ProGuardTask); buildProguardJar.setDescription("混淆jar包") buildProguardJar.dependsOn buildJar //设置不删除未引用的资源(类,方法等) buildProguardJar.dontshrink(); //忽略警告 buildProguardJar.ignorewarnings() //需要被混淆的jar包 buildProguardJar.injars(jarExtension.outputFileDir + "/" + jarExtension.outputFileName) //混淆后输出的jar包 buildProguardJar.outjars(jarExtension.outputFileDir + "/" + jarExtension.outputProguardFileName) //libraryjars表示引用到的jar包不被混淆 // ANDROID PLATFORM buildProguardJar.libraryjars(project.android.getSdkDirectory().toString() + "/platforms/" + "${project.android.compileSdkVersion}" + "/android.jar") // JAVA HOME def javaBase = System.properties["java.home"] def javaRt = "/lib/rt.jar" if (System.properties["os.name"].toString().toLowerCase().contains("mac")) { if (!new File(javaBase + javaRt).exists()) { javaRt = "/../Classes/classes.jar" } } buildProguardJar.libraryjars(javaBase + "/" + javaRt) //混淆配置文件 buildProguardJar.configuration(jarExtension.proguardConfigFile) if (jarExtension.needDefaultProguard) { buildProguardJar.configuration(project.android.getDefaultProguardFile('proguard-android.txt')) } //applymapping def applyMappingFile=jarExtension.applyMappingFile if(applyMappingFile!=null){ buildProguardJar.applymapping(applyMappingFile) } //输出mapping文件 buildProguardJar.printmapping(jarExtension.outputFileDir + "/" + "mapping.txt") def buildJarBeforeDexTask = project.tasks[buildJarBeforeDex] buildJarBeforeDexTask.dependsOn dexTask.taskDependencies.getDependencies(dexTask) buildJar.dependsOn buildJarBeforeDexTask buildJar.doFirst(buildJarClosure) } } } } }}
插件使用
既然标题说了这是一个通用的打包jar插件,那么一些基本特性,如过滤包名
,指定包名
等是必须要支持的,目前该插件支持特性如下:
- 按需打包jar:
- 全项目打包jar
- 指定输出Jar包的包名路径列表
- 过滤指定包名路径列表
- 过滤指定class
- 过滤指定jar
- 支持混淆打包jar
- 支持applymapping
具体使用说明
引入依赖
dependencies { classpath 'com.android.tools.build:gradle:2.1.3' classpath 'com.adison.gradleplugin:jar:1.0.1' }
应用插件
apply plugin: 'jar-gradle-plugin'BuildJar{ //输出目录 outputFileDir= project.buildDir.path+"/jar" //输出原始jar包名 outputFileName="test.jar" //输出混淆jar包名 outputProguardFileName="test_proguard.jar" //混淆配置 proguardConfigFile="proguard-rules.pro" //是否需要默认的混淆配置proguard-android.txt needDefaultProguard=true applyMappingFile="originMapping/mapping.txt" //需要输出jar的包名列表,当此参数为空时,则默认全项目输出,支持多包,如 includePackage=['com/adison/testjarplugin/include','com/adison/testjarplugin/include1'...] includePackage=['com/adison/testjarplugin/include'] //不需要输出jar的jar包列表,如['baidu.jar','baidu1.jar'...] excludeJar=[] //不需要输出jar的类名列表,如['baidu.calss','baidu1.class'...] excludeClass=['com/adison/testjarplugin/TestExcude.class'] //不需要输出jar的包名列表,如 excludePackage=['com/adison/testjarplugin/exclude','com/adison/testjarplugin/exclude1'...] excludePackage=['com/adison/testjarplugin/exclude']}
- 使用
- 打包普通jar
./gradlew buildJar
- 打包混淆jar
./gradlew buildProguardJar
使用可参见使用demo
插件源码
https://github.com/adisonhyh/buildJar
更多相关文章
- 一款常用的 Squid 日志分析工具
- GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
- RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
- Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
- QT项目编译Android(安卓)版本时,错误合集
- Android(安卓)在SQLite中存取二进制图片
- Native Service 创建过程
- Binary XML file line #11: You must supply a layout_width att
- android状态栏显示进度