前言
前一阵子帮业务同学解决了个代码问题,其实挺有意思的,就打算和大家分享下这个内容。
先简单的介绍下背景,业务同学写了个apt
的框架,然后里面包含一个注解的库,而注解库中需要使用到Android
源码中的View
。但是因为这是一个Java Library
,无法直接将安卓的源码添加到依赖中,就无法引用到View
。然后他们为了解决这个问题,又创建了一个库,然后生成了一个同包名的Android View
,类似下图这总,然后compileOnly
这个库。
因为这个模块内有了这个View
,业务同学在后续调试系统源码的时候都会进到这个造假出来的View上去了,就产生了很大的干扰作用。原因呢其实就是因为这个类呗添加到sourceSet
中了,同名类的情况下会优先使用上层加载的。
这种在java库内需要造假出一些Android View
,就变成了一个很好玩的东西了。接下来我们就通过gradle的一些简单的操作,来把这个坑填上。
详细代码可以看下这个 Router-Android
Gradle Java Compiler Task
在build.gradle
中,我们可以声明一个task
任务,然后声明这个任务继承的类型,让它变成一个可以java编译代码的任务。
task("stubLib", JavaCompile::class) {
source(file("src/stub/java"))
classpath = project.files(getAndroidJar("32"))
// libraries
destinationDirectory.set(File(project.buildDir, "/tmp/stubLibs"))
}
fun getAndroidJar(compileSdkVersion: String): String {
var androidSdkDir =
System.getenv(com.android.tools.analytics.Environment.EnvironmentVariable.ANDROID_SDK_HOME.key)
if (androidSdkDir.isNullOrEmpty()) {
val propertiesFile = rootProject.file("local.properties")
if (propertiesFile.exists()) {
val properties = Properties()
properties.load(propertiesFile.inputStream())
androidSdkDir = properties.getProperty("sdk.dir")
}
}
if (androidSdkDir.isNullOrEmpty()) {
throw StopExecutionException("please declares your 'sdk.dir' to file 'local.properties'")
}
val path = "platforms${File.separator}android-${compileSdkVersion}${File.separator}android.jar"
return File(androidSdkDir.toString(), path).absolutePath
}
复制代码
看起来这段代码就比较简单。首先我们声明了一个gradle task(gradle基础概念 有兴趣的可以自己去了解下),这个Task继承自JavaCompile
,然后输入的是src/stub/java
这个文件夹下的内容,classpath是android
源代码,输出是工程的build//tmp/stubLibs
文件夹。
介绍完了Task的声明之后,它会做些什么。这个声明的任务会基于他的输入内容,然后执行java编译任务,最后把.class输出到输出的文件夹下。
获取Android.jar
这个比较简单,其实Android.jar是要区分compile版本的,这些都放在android sdk下。类似这种/Android/sdk/platforms/android-32/android.jar
。代码就是上面的getAndroidJar
。
class -> jar
上面这个JavaCompile
任务负责的就是将java
转变成class
文件,但是并没有办法直接被工程使用。因为工程内我们只能依赖于jar或者aar的依赖方式,而没有办法使用class文件。所以我们要做的就是把这些class通过另外一个任务压缩成一个jar包。
task("stubLibsJar", Jar::class) {
archiveBaseName.set("stub")
archiveVersion.set("1.0")
from(tasks.getByName("stubLib"))
include("**/*.class")
}
复制代码
这个也是Gradle
内提供的一个任务,可以从类型中看出来就是一个转化Jar::class
的任务。其中jar的名字叫stub,版本号1.0。内容则来自前置的任务stubLib
(我们上面声明的那个任务)。然后包含里面所有的.class文件。之后把这些内容都转化成一个jar包输出。
dependencies中执行任务
上面的这个方法已经让我们可以在一个"java-library"
中使用安卓编译出来的jar包了。但是我们的代码内还没有办法建立索引,因为configuration内并不存在这个jar包,我们需要把这个编译产物添加到dependencies中去才行。
dependencies {
// implementation fileTree(dir: 'libs', include: ['*.jar'])
val stub = tasks.getByName("stubLibsJar").outputs.files
compileOnly(stub)
}
复制代码
先从taskManager中获取到这个任务,然后取出这个任务的output的文件,然后compileOnly
这个jar。
通过这种方式我们就可以活学活用gradle的特性,先造假出一些我们想要的假的系统类,然后编译成jar包,之后仅在编译时使用这些,这样这些类在实际运行时就会被替换成android.jar中的类了。
这样一开始我们说的工程内的问题就被我们完美的规避和解决了。
结尾
本文可以当做一个gradle task
的入门文章,通过几个简单的例子给大家介绍下。我之前也关注了些Gradle相关的文章,一般介绍的gradle task的文章就有点太无聊了,很难有用一个生动的例子和各位说明为什么需要task,输入输出的含义是什么,希望本文对大家有所帮助。
另外作为一个老卷逼了,最近在做整个工程的kotlin compose androidx的升级工作,进度还是挺顺利的,能不能顺利提桶就看这一出了啊。