現(xiàn)狀
網(wǎng)上關(guān)于Android studio打包jar的教程很多,基本思路如下
- 項(xiàng)目
build.gradle
中增加一個(gè)Jar任務(wù), - 指定打包路徑。如下:
task buildJar(dependsOn: ['assembleDebug'], type: Jar) {
....
def srcClassDir = [project.buildDir.absolutePath + "/intermediates/classes/debug"];
from srcClassDir
include "**/*.class"
....
}
這樣做個(gè)人覺得有幾個(gè)問題:
-
只能給當(dāng)前項(xiàng)目應(yīng)用module打包
/intermediates/classes/debug
對(duì)于依賴的aar,如support v7,編譯輸出class是在
/intermediates/exploded-aar/
對(duì)于依賴的jar包,目測(cè)在intermediates
中根本找不到 不能混淆,當(dāng)然你也可以在
build.gradle
寫一個(gè)ProGuardTask,具體可參見這篇文章,這里直接復(fù)制其最終生成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 可根據(jù)實(shí)際需要增加或更改
task buildJar(dependsOn: ['compileReleaseJavaWithJavac'], type: Jar) {
appendix = "demo"
baseName = "androidJar"
version = "1.0.0"
classifier = "release"
//后綴名
extension = "jar"
//最終的 Jar 包名,如果沒設(shè)置,默認(rèn)為 [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"
//只導(dǎo)入資源路徑集下的部分資源
include "org/chaos/demo/jar/**/*.class"
//注: exclude include 支持可變長(zhǎng)參數(shù)
}
task proguardJar(dependsOn: ['buildJar'], type: ProGuardTask) {
//Android 默認(rèn)的 proguard 文件
configuration android.getDefaultProguardFile('proguard-android.txt')
//manifest 注冊(cè)的組件對(duì)應(yīng)的 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}"
//設(shè)置不刪除未引用的資源(類,方法等)
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)
}
}
}
看起來真不太舒服不是?(無意冒犯)
- 對(duì)于一個(gè)強(qiáng)迫癥的程序員,除了代碼要整潔之外,編譯腳本文件
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"
//是否需要默認(rèn)的混淆配置proguard-android.txt
needDefaultProguard=true
}
這樣感覺是不是好些了哈
實(shí)踐
關(guān)于第一個(gè)問題,我們可以利用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,我們只要取得其輸入文件就行了
關(guān)于第三個(gè)問題,我們寫一個(gè)Gradle插件,把業(yè)務(wù)邏輯都交給插件處理就好了,關(guān)于Gradle及自定義Gradle插件可以參考Gradle深入與實(shí)戰(zhàn)系列文章,在此不展開說明。廢話不多說,直接上插件代碼:
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("構(gòu)建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 {
//默認(rèn)全項(xiàng)目構(gòu)建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
//設(shè)置不刪除未引用的資源(類,方法等)
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)
}
}
}
}
}
}
插件使用
既然標(biāo)題說了這是一個(gè)通用的打包jar插件,那么一些基本特性,如過濾包名
,指定包名
等是必須要支持的,目前該插件支持特性如下:
- 按需打包jar:
- 全項(xiàng)目打包jar
- 指定輸出Jar包的包名路徑列表
- 過濾指定包名路徑列表
- 過濾指定class
- 過濾指定jar
- 支持混淆打包jar
- 支持applymapping
具體使用說明
-
引入依賴
dependencies { classpath 'com.android.tools.build:gradle:2.1.3' classpath 'com.adison.gradleplugin:jar:1.0.1' }
?
-
應(yīng)用插件
apply plugin: 'jar-gradle-plugin' BuildJar{ //輸出目錄 outputFileDir= project.buildDir.path+"/jar" //輸出原始jar包名 outputFileName="test.jar" //輸出混淆jar包名 outputProguardFileName="test_proguard.jar" //混淆配置 proguardConfigFile="proguard-rules.pro" //是否需要默認(rèn)的混淆配置proguard-android.txt needDefaultProguard=true applyMappingFile="originMapping/mapping.txt" //需要輸出jar的包名列表,當(dāng)此參數(shù)為空時(shí),則默認(rèn)全項(xiàng)目輸出,支持多包,如 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)