什么是依赖
项目的编译或运行时类路径中的 jar(Java 库)或 aar(Android 库)。它提供了零个或多个的集合:.class 文件、Java 资源、Android 资源或 Android 组件(如活动、服务等)。可能是项目中的或者项目外部的单独模块。
什么是被使用的依赖
编译项目所需的库,或者项目在运行时需要的库。
未使用的依赖项是项目在编译或运行时不需要的依赖项。
了解依赖项是否用于编译
要知道在编译过程中是否使用了依赖项,我们必须知道两个基本的东西:
- 依赖会产生什么?依赖项的编译字节码中有什么?
- 项目消耗什么?项目的编译字节码中有什么?
分析生产者(库)
一般来说,项目中的每个模块都是依赖关系图的受益者。在顶层直接声明的所有依赖项,但在这些之下是顶层(或直接)依赖项所依赖的传递依赖项;这些通常可以直接使用,尽管项目没有声明。
private fun analyzeJar(artifact: Artifact): AnalyzedJar {
val zipFile = ZipFile(artifact.file)
val analyzedClasses = zipFile.entries.toList()
.filter { it.name.endsWith(“.class”)
.map { classEntry ->
val visitor = MyClassVisitor()
val reader = zipFile
.getInputStream(classEntry)
.use { ClassReader(it.readBytes()) }
reader.accept(visitor, 0)
visitor // “return”
}
.map { it.analyzeClass() }
return AnalyzedJar(analyzedClasses)
}
依赖 ASM 库进行一些基本的字节码分析。
给定一个 Artifact ,返回一个 AnalyzedJar 。在这种情况下,Artifact 是一种自定义数据类型,它在磁盘上包装了一个 jar 文件。从那个 jar 开始,我们遍历它包含的类文件,用 ASM 的 ClassVisitor 的实现访问每个文件;在本例中为 MyClassVisitor。这个访问者产生了一个叫做“分析类”的东西,它是一个包含我们关心的所有东西的编译类文件的表示。然后我们返回一个 AnalyzedJar,它是一组 AnalyzedClasses 的包装器。
class MyClassVisitor : ClassVisitor(ASM8) {
fun getAnalyzedClass(): AnalyzedClass {
return AnalyzedClass(
className = className,
superClassName = superClassName,
retentionPolicy = retentionPolicy,
isAnnotation = isAnnotation,
hasNoMembers = fieldCount == 0 && methodCount == 0,
access = access,
methods = methods,
innerClasses = innerClasses,
constantClasses = constantClasses
)
}
override fun visit(
version: Int, access: Int,
name: String, signature: String?, superName: String?,
interfaces: Array<out String>?
) {
className = name
superClassName = superName
if (interfaces?.contains("java/lang/annotation/Annotation") == true) {
isAnnotation = true
}
this.access = Access.fromInt(access)
}
}
ASM 解析字节码,访问类文件中的每个节点并从中提取信息,以便我们可以在更高级别上操作该信息。
分析消费者(我们的项目!)
每个类引用在使用现场都是完全限定的。考虑到这一点,我们只需要访问类文件中的每个节点——成员、方法体、注释、类型注释和超类(可能只是“java/lang/Object”)——并提取类型引用。
获取Module编译时依赖的所有组工件
新建一个Android Java Module,build.grdle里面配置为:
dependencies {
api("androidx.appcompat:appcompat:1.4.1")
}
获取到CompileClasspath的配置 ConfigurationContainer
val configurationContainer = configurations["CompileClasspath"]
此配置的传入依赖项 ResolvableDependencies
val resolvableDependencies = configurationContainer.incoming
为这组依赖项解析的工件的视图 Artifacts
val artifactCollection = resolvableDependencies.artifactView {
// Attribute 是具有类型的命名实体
attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, "android-classes")
// 将配置的依赖项转换成 Attribute 返回
}.artifacts
打印出来的 artifactCollection :
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/7bcd8badc609448d29b333db5701e7f0/transformed/appcompat-1.4.1-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/a684fff54dab8e9d085b2337d511904e/transformed/fragment-1.3.6-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/55da1756b1bbeed488c7f9314f8ed9b1/transformed/activity-1.2.4-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/c7a81e7f40c2912c50c011b9bc7af227/transformed/appcompat-resources-1.4.1-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/40c3bfe0f38de514faf50472f7361b2a/transformed/drawerlayout-1.0.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/fffc2ba92930ef5c9f0cd6bc47a167e5/transformed/viewpager-1.0.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/0f843722c765de9c5a910e506d20ab6d/transformed/loader-1.0.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/c7b7df3a62afcd1d375312c3e8820887/transformed/vectordrawable-animated-1.1.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/ea1868553a8b1b91baac7c9ac52c3af8/transformed/vectordrawable-1.1.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/d9c599ce48f4dfd10fbb3a27e6daf04f/transformed/customview-1.0.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/052f750d99c0467cc98e16b3367e564b/transformed/core-1.7.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/1dfcdcb61ebe96e949ffb98da8bcacea/transformed/cursoradapter-1.0.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/c83fdb8189d639853c6aa1d7edb8b227/transformed/lifecycle-viewmodel-savedstate-2.3.1-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/917c029faf7308477b94b8fbfd441dfb/transformed/savedstate-1.1.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/ee79f7d610e46e5373d0efd95a590b7c/transformed/lifecycle-runtime-2.4.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/b6b9bfeb957fea8cfe3832375059480d/transformed/versionedparcelable-1.1.1-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/8074ff81080751599ed38ad8f7dd6f33/transformed/lifecycle-viewmodel-2.3.1-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/7ddd26137408d87fdb67efed0697905d/transformed/lifecycle-livedata-2.0.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/a371fdc194ebe6c64b0b3260fcc5c2e0/transformed/lifecycle-livedata-core-2.3.1-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.4.0/1fdb7349701e9cf2f0a69fc10642b6fef6bb3e12/lifecycle-common-2.4.0.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/caf17aa4edb084a49c0fe05c8b8320d3/transformed/core-runtime-2.1.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/9ce0a291c7dc4d0da3cacc365a458ca5/transformed/interpolator-1.0.0-api.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.3.0/21f49f5f9b85fc49de712539f79123119740595/annotation-1.3.0.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/1cf8ae3d0ea3c40a5218da8bb8c2745c/transformed/annotation-experimental-1.1.0-api.jar
可以看到里面包含了我们依赖的 appcompat-1.4.1-api.jar 还有一些其他工件。其他工件属于间接依赖。
获取Module运行时依赖的所有组工件
我们知道Module之间相互依赖的形式有以下几种:
api, compileOnly, implementation, runtimeOnly
获取具体的依赖坐标:
val configuration = project.configurations
val configurationName = configuration.name
val dep = configuration.dependencies
val group = dep.group
val name = dep.name
val version = dep.version
configurationName 的形式 api
group:name:version 的形式 androidx.appcompat:appcompat:1.4.1
组合起来就是我们上面gradle文件中配置的依赖:
api("androidx.appcompat:appcompat:1.4.1")
运行时不需要的依赖属于 compileOnly,那就需要区分出具体的依赖形式。
我们这时候添加一个 compileOnly 测试:
dependencies {
api("androidx.appcompat:appcompat:1.4.1")
compileOnly("androidx.core:core-ktx:1.7.0")
}
获取到CompileClasspath的配置 ConfigurationContainer
val configurationContainer = configurations["RuntimeClasspath"]
此配置的传入依赖项 ResolvableDependencies
val resolvableDependencies = configurationContainer.incoming
为这组依赖项解析的工件的视图 Artifacts
val artifactCollection = resolvableDependencies.artifactView {
// Attribute 是具有类型的命名实体
attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, "android-classes")
// 将配置的依赖项转换成 Attribute 返回
}.artifacts
打印出来的 artifactCollection :
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/67f0119821bf070a5df08a7a6e508a11/transformed/appcompat-1.4.1-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/8bf73974408ed5df3a562bb6f5c98e23/transformed/fragment-1.3.6-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/7695cbbb481689f894834b260b637a79/transformed/activity-1.2.4-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/d21428b1999cd6c76bb6d965f3416d08/transformed/appcompat-resources-1.4.1-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/88c7f3994523ac82084960b3a18f5100/transformed/drawerlayout-1.0.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/230eee3476a9da97fdc26470f6c90b64/transformed/emoji2-views-helper-1.0.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/e83518f25f0ebf326d18887e73880dca/transformed/emoji2-1.0.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/ec390e545a99075fe5ee4d88475c1069/transformed/viewpager-1.0.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/6c6907cb1fdd3c066cb26b05401aa7c7/transformed/loader-1.0.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/b8fec809190876d9af3c683a151d01b2/transformed/vectordrawable-animated-1.1.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/3cdc1b920c2fc072ea17142f1b129a7d/transformed/vectordrawable-1.1.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/943838500c8130f86a839ce998d8adaf/transformed/customview-1.0.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/92d329a127c634655dffb10d3f930c1b/transformed/core-1.7.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/0cfbdd3feb46a33a20d918a4b380254c/transformed/cursoradapter-1.0.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/8a3377486c1d1fbf241557a778fb983c/transformed/lifecycle-viewmodel-savedstate-2.3.1-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/1ebf2f4f6871bc66836463242f6fa734/transformed/savedstate-1.1.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/5baae918e3d949f12d8bf03ace605b18/transformed/lifecycle-viewmodel-2.3.1-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/modules-2/files-2.1/androidx.resourceinspection/resourceinspection-annotation/1.0.0/8c21f8ff5d96d5d52c948707f7e4d6ca6773feef/resourceinspection-annotation-1.0.0.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/6d0986df9e7ea18687664fa898f80d3d/transformed/versionedparcelable-1.1.1-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/modules-2/files-2.1/androidx.concurrent/concurrent-futures/1.0.0/c1e77e3ee6f4643b77496a1ddf7a2eef1aefdaa1/concurrent-futures-1.0.0.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/9dca1f50be0ce62dd8f42f768375e564/transformed/lifecycle-process-2.4.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/bdda4dae981c7eaa160361bf7d969770/transformed/startup-runtime-1.0.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/9bc7bb5a1264e89a08e851870f5ba4d7/transformed/tracing-1.0.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/6ac1c26fdde948f363014e543571b3dd/transformed/lifecycle-livedata-2.0.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/0998fb049f169a7eb47e37a978e724d0/transformed/lifecycle-livedata-core-2.3.1-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/a8737e0fc72bd7656e9bd4902ae25ff0/transformed/lifecycle-runtime-2.4.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/24d5ea73311b910732e7d03a37edb5ed/transformed/core-runtime-2.1.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/42615e1237baa1f56555fdc76c063cb6/transformed/interpolator-1.0.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.4.0/1fdb7349701e9cf2f0a69fc10642b6fef6bb3e12/lifecycle-common-2.4.0.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.3.0/21f49f5f9b85fc49de712539f79123119740595/annotation-1.3.0.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/transforms-3/53216a01d5a3110a89e9cf77e78115e9/transformed/annotation-experimental-1.1.0-runtime.jar
zpw$$ artifact is /Users/zpw/.gradle/caches/modules-2/files-2.1/com.google.guava/listenablefuture/1.0/c949a840a6acbc5268d088e47b04177bf90b3cad/listenablefuture-1.0.jar
可以发现打印中并没有 core-ktx 。
计算Module的直接依赖
通过上面的 RuntimeClasspath 的 configuration 我们可以通过
configuration.incoming
.resolutionResult
.root
.dependencies
获取所有的依赖工件:
androidx.appcompat:appcompat:1.4.1
通过直接依赖的结果叠加全部依赖的结果,我们就可以分出哪些是简介依赖的 Module。
建立工件和工件内部的.class文件的映射。
常量收集
通过ASM过滤字段:
Opcodes.ACC_STATIC && Opcodes.ACC_FINAL
判断 import 方式引入的常量,可以通过 ANTLR 进行收集。
Res文件收集
通过获得 CompileClasspath 的 configuration,过滤出resourceArtifacts.
val resourceArtifacts = project
.configurations["CompileClasspath"]
.incoming
.artifactView {
attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, "android-symbol-with-package-name")
}.artifacts
androidx.annotation.experimental-r.txt (androidx.annotation:annotation-experimental:1.1.0)
通过读取txt文件的第一行可以得出包名,对应的导入字段就是:
dependency=androidx.fragment:fragment:1.3.6, import=androidx.fragment.R
通过获得 RuntimeClasspath 的 configuration,过滤出manifests.
val manifests = project
.configurations[runtimeConfigurationName]
.incoming
.artifactView {
attributes.attribute(attribute, "android-manifest")
}.artifacts
AndroidManifest.xml (androidx.appcompat:appcompat:1.4.1)
通过 DocumentBuilderFactory 可以解析出manifests文件的包名,对应的导入字段就是:
dependency=androidx.core:core-ktx:1.7.0, import=androidx.core.ktx.R
获取本模块的sourceSets,获取到里面所有的Java和Kotlin文件:
include("**/*.java")
include("**/*.kt")
按照每一行读取,跟依赖的所有的资源文件进行对比,就可以得出使用的哪些资源文件。
获取依赖的所有class
module
获取 bundleLibCompileToJar task,它是负责打包jar文件的task。里面包含了所有打包的jar文件。通过反射调用BundleLibraryClassesJar的getOutput方法,就可以得到task打包的所有jar文件。
然后收集所有kapt生成的文件:
**/kapt*/**/${variantName}/**/*.java
以及所有的 sourceSets 中的 layout 文件。
从所有的jar文件中可以提取出class文件。读取所有的layout文件进行解析包含的类。根据正则表达式从kapt中解析对应的class。
app
跟module的方法类似。
没有使用的引用
遍历声明的依赖项,排除不包含 class 文件的依赖项(例如 androidx.legacy:legacy-support-v4),遍历依赖项中的所有class。
- 如果是直接依赖,如果使用的类中不包含这个直接依赖项的中的class,未使用的class的个数增加,否则被使用的class的个数增加。
- 如果是间接依赖,排除来自 android.jar,并且被使用的class集合中包含它,不在使用的直接依赖项集中,那么间接使用的class的个数增加。
此时如果未使用的class数目等于这个依赖中的class数目,排除具有内联用法的模块,排除使用 Android res 的模块,排除使用常量的模块。那么这个依赖就是未使用的依赖项。否则他则是被使用的间接依赖项。