插件的目的
大家都知道在工程变大变久的时候,项目里会有很多个module 也会有很多个so文件,有时候我们想确认一个so 来自于哪个module 就不是一件容易的事, 这个插件要解决的问题就是 运行一个任务task 然后 打印出来 第三方module 都引入了哪些so 文件。
准备工作
考虑到groovy 实在是不好用,且kotlin 现在更为主流,我们这次采用的gradle插件编写语言为gradle,采用buildSrc的方式 这里直接上一份build文件吧 避免走弯路
//用kotlin 来写插件 不用groovy了, groovy 实在是不好用
apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'
//额外引入kotlin 来支持 gradle插件编写 要独立 设定 bs环境
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61"
}
}
sourceSets {
main {
//暂时只设定
kotlin {
srcDir 'src/main/kotlin'
}
resources {
srcDir 'src/main/resources'
}
}
}
repositories {
mavenCentral()
jcenter()
google()
}
compileKotlin {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8
}
compileTestKotlin {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8
}
dependencies {
//这里注意了 要跟随 主工程的 tools 里面的 gradle版本号走 一定要保持一致 否则可能会导致 插件运行不正常
compile 'com.android.tools.build:gradle:3.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.61"
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61"
}
buildSrc 创建gradle插件的方法 我就不多介绍了,网上很多,大家自行百度即可。
怎么拿到产物 Varint
要想获得 so与module 的对应关系,我们首先想到的就是 怎么获得Varint。
class VivoSpaceSmartPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.afterEvaluate {
project.extensions.findByName("android")?.let {
if (it is AppExtension) {
it.applicationVariants.forEach { applicationVariant ->
println("applicationVariant: " + applicationVariant.name)
}
}
}
}
}
}
我们看下执行结果 这个变体到底是个啥东西
很容易吧,这里我们拿到了 application的变体,注意是application 不是library, 因为我们只想对主工程进行检测, 这里有这么多变体,很多都是渠道包的,我们简单一点,就对 我们这个经常使用的defaultDebug这个varint来进行处理吧。
这里我们看到 这个变体实际上是一个接口,我们想看看他的实体类是什么? 可以打印出来
applicationVariant class: com.android.build.gradle.internal.api.ApplicationVariantImpl_Decorated
applicationVariant class: com.android.build.gradle.internal.api.ApplicationVariantImpl_Decorated
所以这个接口的实体类就是
有了这些信息 我们就可以将varint的数据保存起来 然后给我们的task使用,这里接着修改一下代码
package com.vivo.space.detect
import com.android.build.gradle.AppExtension
import com.android.build.gradle.internal.api.ApplicationVariantImpl
import org.gradle.api.Plugin
import org.gradle.api.Project
class VivoSpaceSmartPlugin : Plugin<Project> {
//存储拿到的变体
lateinit var debugVarint: ApplicationVariantImpl;
override fun apply(project: Project) {
project.afterEvaluate {
project.extensions.findByName("android")?.let {
if (it is AppExtension) {
it.applicationVariants.forEach { applicationVariant ->
if ("DefaultDebug" == applicationVariant.name) {
debugVarint = applicationVariant as ApplicationVariantImpl
}
}
}
}
}
}
}
在task中处理varint数据
project.tasks.create("soInfoTask").doFirst {
//取出来我们想要的变体的范围 注意参数的设定
val varintCollection = debugVarint.variantData.scope.getArtifactCollection(AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH, AndroidArtifacts.ArtifactScope.ALL, AndroidArtifacts.ArtifactType.AAR)
varintCollection.artifacts.forEach { resolvedArtifactResult: ResolvedArtifactResult? ->
println("id:" + resolvedArtifactResult?.id)
println("displayName:" + resolvedArtifactResult?.id?.componentIdentifier?.displayName)
println("fileName:" + resolvedArtifactResult?.file?.name)
println("-----------------------------")
}
}
我们执行一下这个任务 看看结果 是啥:
看到这个大家应该有数了吧, file 是可以拿的到的,那我们直接解析这个file 不就可以了吗? 把里面的so文件找出来, 然后打印一下 不就能拿到我们的目的了?
获取so文件与aar文件的关系
这里直接上代码吧:
project.tasks.create("soInfoTask").doFirst {
//取出来我们想要的变体的范围 注意参数的设定
val varintCollection = debugVarint.variantData.scope.getArtifactCollection(AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH, AndroidArtifacts.ArtifactScope.ALL, AndroidArtifacts.ArtifactType.AAR)
varintCollection.artifacts.forEach { resolvedArtifactResult: ResolvedArtifactResult? ->
//取得aar的名字
val displayName = resolvedArtifactResult?.id?.componentIdentifier?.displayName
//对产物的文件进行判定,只判定 文件后缀名为aar或者jar的文件
when (resolvedArtifactResult?.file?.extension?.toLowerCase()) {
"aar", "jar" -> {
//aar本质上也是个jar 所以 这里直接解压缩 取得so文件 然后稍微排序一下 好看一点
//因为有的aar 里面没有so文件 所以 我们要过滤一下
JarFile(resolvedArtifactResult.file).use { jar ->
jar.entries().asSequence().filter { jarEntry ->
jarEntry.name.endsWith(".so")
}.sortedBy { jarEntry ->
jarEntry.name
}.toList().ifNotEmpty {
println("$displayName")
it.forEach { jarEntry ->
println("-------------------$jarEntry")
}
}
}
}
}
}
}
然后运行一下我们的task:
完美 perfect,这里注意 我们的kotlin 默认是没有 ifNotEmpty这个高阶函数的, 需要你自己手动实现一个
inline fun <T> Collection<T>.ifNotEmpty(action: (Collection<T>) -> Unit): Collection<T> {
if (isNotEmpty()) {
action(this)
}
return this
}