可能是最通用全面的Android studio打包jar方法

现状

网上关于Android studio打包jar的教程很多,基本思路如下

  1. 项目build.gradle中增加一个Jar任务,
  2. 指定打包路径。如下:
1
2
3
4
5
6
7
8
9
task buildJar(dependsOn: ['assembleDebug'], type: Jar) {
....
def srcClassDir = [project.buildDir.absolutePath + "/intermediates/classes/debug"];
from srcClassDir
include "**/*.class"
....
}
这样做个人觉得有几个问题:

1. 只能给当前项目应用module打包/intermediates/classes/debug
> 对于依赖的aar,如support v7,编译输出class是在/intermediates/exploded-aar/
> 对于依赖的jar包,目测在intermediates中根本找不到

2. 不能混淆,当然你也可以在build.gradle写一个ProGuardTask,具体可参见这篇文章,这里直接复制其最终生成build.gradle如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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<String> 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)
}
}
}

看起来真不太舒服不是?(无意冒犯)

  1. 对于一个强迫症的程序员,除了代码要整洁之外,编译脚本文件build.gradle不整洁也不能忍
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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深入与实战系列文章,在此不展开说明。废话不多说,直接上插件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class BuildJarPlugin implements Plugin<Project> {
public static final String EXTENSION_NAME = "BuildJar";
@Override
public void apply(Project project) {
DefaultDomainObjectSet<ApplicationVariant> 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<File> 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插件,那么一些基本特性,如过滤包名指定包名等是必须要支持的,目前该插件支持特性如下:

  1. 按需打包jar:
    • 全项目打包jar
    • 指定输出Jar包的包名路径列表
    • 过滤指定包名路径列表
    • 过滤指定class
    • 过滤指定jar
  2. 支持混淆打包jar
  3. 支持applymapping

具体使用说明

  1. 引入依赖

    1
    2
    3
    4
    dependencies {
    classpath 'com.android.tools.build:gradle:2.1.3'
    classpath 'com.adison.gradleplugin:jar:1.0.1'
    }

  2. 应用插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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']
    }
  3. 使用

  • 打包普通jar

    1
    ./gradlew buildJar
  • 打包混淆jar

    1
    ./gradlew buildProguardJar

使用可参见使用demo

插件源码

https://github.com/adisonhyh/buildJar

Adison wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!