现状
网上关于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 {
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
- 打包普通jar