Android 自定义插件 获取构建apk时的so文件与aar的关系

2,749 阅读3分钟

插件的目的

大家都知道在工程变大变久的时候,项目里会有很多个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
}