现状

网上关于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](https://github.com/adisonhyh/TestJarPlugin)

插件源码

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐