得物Android工程依赖优化实践

引言

随着业务的高速发展,代码量也越来越多,良好的工程结构和依赖管理对构建速度有着积极的作用,文章介绍了最近一段时间得物Android工程gradle依赖优化的一些实践,以及未来这个方向需要做的事情。原文链接 https://mp.weixin.qq.com/s/2EwKJpr9PGlvErbjN4tkuA

优化点

  • 扁平化依赖
    在不断的开发迭代中,业务模块implementation的组件中可能存在一部分已经不再使用了,但是仍然留在gradle配置里。另外一些common模块中存在大量的api出去的组件,有很大一部分上层模块使用率较低,甚至没有用到。这两种情况会对编译造成以下影响
    1、大量的时间花费在解析无用的依赖上增加gradle运行时间消耗
    2、compileClasspath链路任何一个组件发生变化(增加/删除方法、增加/删除注解等),都会触发javac、kotlinc的重新编译,拖慢编译速度
    3、增加编译过程中类寻找过程的时间消耗

  • 无法确定snapshot类型的组件是否为最新的
    我们使用SNAPSHOT类型组件,来解决组件化开发中的实时性问题,为了编译的效率刷新的时间设置的比较长,只有主动调用idea-plugin的刷新组件缓存时才会去拉新的aar包,组建锁定功能上线后有业务同学经常会反馈组件无法刷新,业务同学判断没有刷新的依据是sources.jar中的内容,AndroidStudio显示sources.jar有时候会有问题(gradle编译时使用的aar是最新的,但是sources.jar显示的是老版本的),studio这个bug的存在对排查编译的问题造成麻烦,无法定位是不是因为idea-plugin刷新逻辑的问题导致的无法刷新

  • 源码依赖/aar依赖行为对齐
    我们的组件化方案中使用了resolutionStrategy.dependencySubstitution来实现组件库源码切换,同一个组件存在源码/aar两种依赖方式,老版本发布插件从configuration中取组件打进pom文件时,没有区分implementation、api,scope设置的都是compile(可以理解为api的行为)。aar依赖时编译正常,如果开了多个源码模块时,由于基础库源码模块的implementation传递依赖被拦截,所以有可能会造成上层源码模块因缺失传递的依赖而导致编译报错,这个问题会对开发人员造成困扰,解决方案是生成的 POM 文件仅将api依赖项放入编译范围,其余implementation依赖项放入运行时范围,具体可以参考java_library_separation。duapp源码模块有200+,每处理一个基础组件都有可能造成很多上层模块因缺失传递的依赖而造成编译错误,手工补组件工作量巨大,另外处理时需要渐进的去做使影响范围做到可控,尽量少的block开发

结合以上的场景,我们需要一个分析依赖的工具为调整工程的依赖提供依据,由于很多用于分析的元数据在gradle运行期才能拿到,所以这个工具做成gradle plugin更为合适,这个工具需要满足以下的需求

1、拿到每个模块类级别的依赖图,进而拿到实际的组件依赖图(非./gradlew dependencies拿到的依赖图),实际的组件依赖图平铺后就是每个模块的最小compileClasspath
2、分析模块中那些implementation依赖用不到,剪除某个implementation依赖时,需要补上那些被翦除依赖的传递依赖
3、拿到gradle编译时snapshot组件的时间戳,这样就能和nexus上的maven-metadata.xml做对比确定是否为最新的版本

接下来让我们一起一步一步来实现poizon-jdeps插件吧

实现过程

拿到依赖的组件信息

AGP中有一个androidDependencies任务可以打印出组件信息,这里通过一个小技巧查看某个gradle task的实现类,调用./gradlew help --task androidDependencies

查阅DependencyReportTask类中的代码,找到了以下实现

Set<ResolvedArtifact> compileArtifacts =
  com.android.build.gradle.internal.ide.dependencies.getAllArtifacts(
        variant,
        AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH,
        null,
        buildMapping)

ResolvedArtifact包含了以下信息,可以满足我们的需求
● artifactFile aar或者jar的路径
● extractedFolder aar的解压目录,jar包的路径为jars/classes.jar、jars/libs/*.jar
● dependencyType 扩展名aar/jar
● componentIdentifier 组件标识符接口,主要的子类如下
l_ ProjectComponentIdentifier 工程依赖描述符
l_ ModuleComponentIdentifier 远程依赖描述符
|_ group 例如com.shizhuang.duapp.modules
|_ module 例如du_common
|_ version 例如4.78.0-SNAPSHOT
|_ MavenUniqueSnapshotComponentIdentifier snapshot类型远程依赖描述符
|_ timestamp 例如20210915.071815-14
timestamp字段,就是snapshot组件的时间戳,有这个值就可以确认是否为最新的包

获取jar包中包含哪些类

下面的内容中称为selfClasses


获取jar包中的外部引用类

下面的内容中称为refClasses
使用Java字节码的类库javassist,解析jar包里class中分散在AttributeInfo、MethodInfo、AttributeInfo中所有涉及的类记为allClasses
jar包外部引用的类refClasses = allClasses - selfClasses



为了提高后面扫描类依赖图时的效率,输出的数据结构尽量的丰富,大致的格式如下
● jarFile: jar包路径
● classNames: jar包里包含的所有类
● classSuperclasses: 类的父类
● classInterfaces: 类实现的接口
● refClassNames: jar外部引用类
● reverseClassRefInfos:jar反向引用
● classRefInfos:每个类引用的类信息
● reverseClassRefInfos:类反向引用

{
    "jarFile": "/Users/admin/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib1/build/intermediates/full_jar/debug/full.jar",
    "cacheFile": "/Users/admin/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib1/build/intermediates/full_jar/debug/47b280a467fc1cb913b13f5b6a278232a54fab11-jdeps-jarinfo-2.json",
    "classNames": [
        "androidlibrary1.Test2",
        "androidlibrary1.Test1",
        "com.example.androidlibrary1.BuildConfig",
        "androidlibrary1.TestKt1"
    ],
    "refClassNames": [
        "java.lang.Object",
        "java.io.Serializable",
        "androidlibrary2.Test1",
        "javalib1.Test1",
        "java.lang.Boolean",
        "java.lang.String",
        "com.google.gson.Gson",
        "kotlin.Metadata"
    ],
    "classSuperclasses": {
        "androidlibrary1.Test2": "java.lang.Object",
        "androidlibrary1.Test1": "androidlibrary2.Test1",
        "androidlibrary1.TestKt1": "java.lang.Object",
        "com.example.androidlibrary1.BuildConfig": "java.lang.Object"
    },
    "classInterfaces": {
        "androidlibrary1.Test2": [
            "java.io.Serializable"
        ],
        "androidlibrary1.Test1": [],
        "androidlibrary1.TestKt1": [],
        "com.example.androidlibrary1.BuildConfig": []
    },
    "classRefInfos": {
        "androidlibrary1.Test2": [
            "java.lang.Object",
            "java.io.Serializable"
        ],
        "androidlibrary1.Test1": [
            "androidlibrary2.Test1",
            "javalib1.Test1"
        ],
        "com.example.androidlibrary1.BuildConfig": [
            "java.lang.Boolean",
            "java.lang.Object",
            "java.lang.String"
        ],
        "androidlibrary1.TestKt1": [
            "com.google.gson.Gson",
            "java.lang.Object",
            "java.lang.String",
            "kotlin.Metadata"
        ]
    },
    "reverseClassRefInfos": {
        "java.lang.Object": [
            "androidlibrary1.Test2",
            "com.example.androidlibrary1.BuildConfig",
            "androidlibrary1.TestKt1"
        ],
        "java.io.Serializable": [
            "androidlibrary1.Test2"
        ],
        "androidlibrary2.Test1": [
            "androidlibrary1.Test1"
        ],
        "javalib1.Test1": [
            "androidlibrary1.Test1"
        ],
        "java.lang.Boolean": [
            "com.example.androidlibrary1.BuildConfig"
        ],
        "java.lang.String": [
            "com.example.androidlibrary1.BuildConfig",
            "androidlibrary1.TestKt1"
        ],
        "com.google.gson.Gson": [
            "androidlibrary1.TestKt1"
        ],
        "kotlin.Metadata": [
            "androidlibrary1.TestKt1"
        ]
    }
}

聚合的信息下面的内容中称为JarInfo

类与组件信息建立映射

使用map维护className与JarInfo的映射关系,key为className,value为jar包信息。


扫描最小的compileClasspath链路

可以把依赖树看做成一个以project的full_jar为根结点允许存在相同叶子结点的多叉树,扫描最小的compileClasspath链路可以理解为寻找有效的叶子结点,从project的full_jar出发,广度优先搜索依赖树,按层级通过refClass寻找链路上的所有jar包。


具体的实现如下:



输出的格式如下:

  • bootJarPath: 扫描的入口(当前工程打出来的jar包)
  • classpathJarPaths:送进去扫描的所有jar包,一般是compileClassPath或者- runtimeClasspath
  • linkedJarPaths:实际关联的jar包(这个就是我们要的结果)
  • jarRefMap: 两个jar包之间关联的信息
  • jarPathDependencyMap: jar路径和依赖的映射关系
{
    "bootJarPath": "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib1/build/intermediates/full_jar/debug/full.jar",
    "classpathJarPaths": [
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/9af88b1981097178b09bd573014bfda6/lifecycle-viewmodel-2.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/f183f4d072c7e7d4fa8026ec8c727cff/interpolator-1.0.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/502ef6f4cbcb90b320b6408c384a70fd/vectordrawable-1.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/8f9b4d8db9e4811a4566f2bdfead37eb/jetified-appcompat-resources-1.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.70/e5d97e25bb5b30dcfc022ec1c8f3959a875257fb/kotlin-stdlib-1.3.70.jar",
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/0322df2ec19980a4718110fbe6330e13/jetified-savedstate-1.0.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/40c8e4c3a7c2cb95ed86268e19d54370/lifecycle-livedata-2.0.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/58739eda211c462d06229a67014dc847/lifecycle-livedata-core-2.0.0/jars/classes.jar",
        "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib2/build/intermediates/full_jar/debug/full.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/49d437b0f3c29ae27e87cbafb4a6f260/transition-1.2.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.70/3fa8dd6c896d635e78201e5e811545f3846dec04/kotlin-stdlib-common-1.3.70.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/2cad5196d8d124071b7754ea606ee3b4/customview-1.0.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/androidx.annotation/annotation/1.1.0/e3a6fb2f40e3a3842e6b7472628ba4ce416ea4c8/annotation-1.1.0.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/e953bd98d3b04a8878566e2fea3195f3/vectordrawable-animated-1.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/androidx.arch.core/core-common/2.1.0/b3152fc64428c9354344bd89848ecddc09b6f07e/core-common-2.1.0.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/41fc22079476e3e282d2ed3641ff14fa/jetified-activity-1.0.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/a6a8b9240bb260704cdd96bff17d1157/drawerlayout-1.0.0/jars/classes.jar",
        "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/javalib1/build/libs/javalib1.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/2a0e305da24846f3e20625eb0c9d62a7/fragment-1.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.7/69d9503ea0a40ee16f0bcdac7e3eaf83d0fa914a/gson-2.8.7.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/e71699873477e0531c44b35d6fedee9e/viewpager-1.0.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/7781c1a15ccac1e25e1944a3f9bc8c67/core-runtime-2.0.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/8c4fb1441f20b8346a52b9617ff00b80/jetified-viewpager2-1.0.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/9715cdb6aa73e3311549b7c7d5697dc9/lifecycle-runtime-2.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/0148c504bf49d69fc0db21ace9bc27ec/recyclerview-1.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/d0a6d74b07aaab2a5b17c8f4b8a47e74/cursoradapter-1.0.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/61d8d915a7e283b3eed7aa5bb2bd0f75/material-1.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/5e3a688aef4d72c6a02a888ee3ce07ca/coordinatorlayout-1.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/df8638fb6ce6747faa4768159d06f4e6/cardview-1.0.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/fa18f164ebc4d3af2c50af480e90366c/core-1.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-common/2.1.0/c67e7807d9cd6c329b9d0218b2ec4e505dd340b7/lifecycle-common-2.1.0.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/8dfc6584720f127bd1a3c835e5587f9d/appcompat-1.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/1be1b54c35cd270cecfbe779ae5b51ed/versionedparcelable-1.1.0/jars/classes.jar",
        "/Users/xxx/.gradle/caches/transforms-2/files-2.1/eeb01b137525576c25e6ec6ca5ca18c0/loader-1.0.0/jars/classes.jar"
    ],
    "linkedJarPaths": [
        "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib2/build/intermediates/full_jar/debug/full.jar",
        "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/javalib1/build/libs/javalib1.jar",
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.70/e5d97e25bb5b30dcfc022ec1c8f3959a875257fb/kotlin-stdlib-1.3.70.jar",
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.7/69d9503ea0a40ee16f0bcdac7e3eaf83d0fa914a/gson-2.8.7.jar"
    ],
    "jarRefMap": {
        "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib1/build/intermediates/full_jar/debug/full.jar": [
            {
                "jarPath": "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib1/build/intermediates/full_jar/debug/full.jar",
                "refJarPath": "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/javalib1/build/libs/javalib1.jar",
                "level": 1,
                "refClasses": [
                    "javalib1.Test1"
                ],
                "classRefInfos": {
                    "androidlibrary1.Test1": [
                        "javalib1.Test1"
                    ]
                },
                "reverseClassRefInfos": {
                    "javalib1.Test1": [
                        "androidlibrary1.Test1"
                    ]
                }
            },
            {
                "jarPath": "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib1/build/intermediates/full_jar/debug/full.jar",
                "refJarPath": "/Users/xxx/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.7/69d9503ea0a40ee16f0bcdac7e3eaf83d0fa914a/gson-2.8.7.jar",
                "level": 1,
                "refClasses": [
                    "com.google.gson.Gson"
                ],
                "classRefInfos": {
                    "androidlibrary1.TestKt1": [
                        "com.google.gson.Gson"
                    ]
                },
                "reverseClassRefInfos": {
                    "com.google.gson.Gson": [
                        "androidlibrary1.TestKt1"
                    ]
                }
            },
            {
                "jarPath": "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib1/build/intermediates/full_jar/debug/full.jar",
                "refJarPath": "/Users/xxx/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.70/e5d97e25bb5b30dcfc022ec1c8f3959a875257fb/kotlin-stdlib-1.3.70.jar",
                "level": 1,
                "refClasses": [
                    "kotlin.Metadata"
                ],
                "classRefInfos": {
                    "androidlibrary1.TestKt1": [
                        "kotlin.Metadata"
                    ]
                },
                "reverseClassRefInfos": {
                    "kotlin.Metadata": [
                        "androidlibrary1.TestKt1"
                    ]
                }
            },
            {
                "jarPath": "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib1/build/intermediates/full_jar/debug/full.jar",
                "refJarPath": "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib2/build/intermediates/full_jar/debug/full.jar",
                "level": 1,
                "refClasses": [
                    "androidlibrary2.Test1"
                ],
                "classRefInfos": {
                    "androidlibrary1.Test1": [
                        "androidlibrary2.Test1"
                    ]
                },
                "reverseClassRefInfos": {
                    "androidlibrary2.Test1": [
                        "androidlibrary1.Test1"
                    ]
                }
            }
        ],
        "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib2/build/intermediates/full_jar/debug/full.jar": [
            {
                "jarPath": "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib2/build/intermediates/full_jar/debug/full.jar",
                "refJarPath": "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/javalib1/build/libs/javalib1.jar",
                "level": 2,
                "refClasses": [
                    "javalib1.Test1"
                ],
                "classRefInfos": {
                    "androidlibrary2.Test1": [
                        "javalib1.Test1"
                    ]
                },
                "reverseClassRefInfos": {
                    "javalib1.Test1": [
                        "androidlibrary2.Test1"
                    ]
                }
            }
        ]
    },
    "jarPathDependencyMap": {
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.7/69d9503ea0a40ee16f0bcdac7e3eaf83d0fa914a/gson-2.8.7.jar": "com.google.code.gson:gson:2.8.7",
        "/Users/xxx/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.70/e5d97e25bb5b30dcfc022ec1c8f3959a875257fb/kotlin-stdlib-1.3.70.jar": "org.jetbrains.kotlin:kotlin-stdlib:1.3.70",
        "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/javalib1/build/libs/javalib1.jar": "sample:javalib1:null",
        "/Users/xxx/Projects/plugin/poizon-jdeps-gradle-plugin/sample/androidlib2/build/intermediates/full_jar/debug/full.jar": "sample:androidlib2:null"
    }
}

最小的compileClasspath链路下面的内容中称为realRefDeps。

扫描无用的implementation

有了扫描最小的compileClasspath链路的能力,后面的事情就迎刃而解了。

没有用到的implementation = implementationDeps - apiDeps - realRefDeps,具体的实现如下:


源码模块中有可能会用到将要被剪除的implementation传递的依赖,所以还需要扫描剪除后需要补的组件列表(移除的组件pom文件带过来的)。

具体的实现是首先探测剪除依赖后的平铺组件列表,然后遍历realRefDeps平铺列表中不存在的组件就是需要补上的组件。


应用于工程

源码依赖/aar依赖对齐

使用shell调用所有业务模块的依赖分析任务,把输出的结果保存下来,方便以后的对比。

#!/bin/bash

output_dir="$(pwd)/poizon-jdeps"
mkdir -p $output_dir > /dev/null 2>&1
err_log="$output_dir/generate-minimum-deps-error.txt"
rm $err_log > /dev/null 2>&1
for project_path in $(cat << 'EOF'
/Users/xxx/codes/duapp-dependency-analysis/module_biz1
......
/Users/xxx/codes/duapp-dependency-analysis/lib1
EOF) ;do
 group_id=$(cat $project_path/build.gradle | grep "group " | sed "s/group \"//g" | sed "s/group \'//g" | sed "s/\"//g" | awk '$1=$1')
 module_name=$(basename $project_path)
 echo "${group_id}:${module_name} $project_path"
 echo "{\"local\":[{\"path\":\"${project_path}\",\"target\":\"${group_id}:${module_name}\"}}" > ./.idea/poizon.pzn
 ./gradlew :${module_name}:debugClassRefGraphWithFullJar
 if [[ $? == 0 ]];then
  rm -rf $output_dir/$module_name/ > /dev/null 2>&1
  cp -r $project_path/build/poizon-jdeps $output_dir/$module_name/
 else
  echo "${module_name}: 执行失败"
  echo "${module_name}: 执行失败" >> $err_log
 fi
done

打开所有业务组件的源码,和需要处理的某个基础组件的源码(相当于把所有的implementation在pom中的节点scope设置为runtime),获取当前的compileClasspath和上一步获取的内容做比较 ,就能得知缺失的组件。

#!/bin/bash

output_dir="poizon-jdeps/miss-deps"
mkdir -p $output_dir > /dev/null 2>&1
for project_path in $(cat << 'EOF'
/Users/xxx/codes/duapp-dependency-analysis/module_biz1
......
/Users/xxx/codes/duapp-dependency-analysis/lib1
EOF) ;do
 group_id=$(cat $project_path/build.gradle | grep "group " | sed "s/group \"//g" | sed "s/group \'//g" | sed "s/\"//g" | awk '$1=$1')
 module_name=$(basename $project_path)
 rm "$output_dir/${module_name}.txt" > /dev/null 2>&1
 rm "$output_dir/${module_name}-with-version.txt" > /dev/null 2>&1
 ./gradlew :${module_name}:debugCompileClasspathDeps -DlockVersion=false
 for dep in $(cat poizon-jdeps/$module_name/debug/compileClasspath/minimum-deps-by-class-ref.txt) ;do
  dep_group=$(echo $dep | awk -F ':' '{print $1}')
  dep_name=$(echo $dep | awk -F ':' '{print $2}')
  dep_version=$(echo $dep | awk -F ':' '{print $3}')
  echo "check $dep_group:$dep_name"
  cat $project_path/build/poizon-jdeps/debug/compileClasspath/foreach-artifacts.sh | grep "${dep_group}:${dep_name}" > /dev/null
  if [[ $? != 0 ]]; then
   echo "implementation \"${dep_group}:${dep_name}\"" >> "$output_dir/${module_name}.txt"
   echo "implementation \"${dep_group}:${dep_name}:${dep_version}\"" >> "$output_dir/${module_name}-with-version.txt"
  fi
 done
done

如果把组件发布一刀切全部切到新的发布插件,会造成大面积的业务组件报错block开发,所以使用了白名单的方式决定是否把scope设置为runtime,重新发布后风险较低的组件(依赖比较少)直接放进白名单里面,风险高的组件(依赖较多)处理一个使用上述的方式扫描一下把缺失的组件补进业务组件后,在加到白名单里面。


优化common模块的api组件

首先拿到common都有哪些api的依赖。
ConfigurationContainer configurationContainer = project.getConfigurations() Configuration

apiConfiguration = configurationContainer.getByName("releaseApi")

apiConfiguration.getAllDependencies() //需要的结果
然后循环的调用每个业务模块的依赖分析任务



遍历common的api依赖,去业务模块的分析结果文件里寻找是否包含这个依赖,发现一个计数+1。

每个组件的使用率 = 命中的次数 / 总的业务模块的个数

按照70%的阈值

= 70%的留在common中
0% && < 70%的放进业务模块中单独implementation
== 0%的直接移除掉

移除无用的implementation

首先需要知道组件的仓库有哪些,把以下代码放进build.gradle中,执行gradle -q

import groovy.json.JsonSlurper

def groups = ['xxx']
groups.each { groupId->
    String url = "https://gitlab.xxx.com/api/v4/groups/${groupId}/projects?private_token=xxx&per_page=100"
    def projects = new JsonSlurper().parse(url.toURL())
    projects.eachWithIndex{it, index->
        println(it.ssh_url_to_repo)
    }
}

使用gradle init.d机制把jdeps-plugin加到工程里,把下面这个脚本放到~/.gradle/init.d/apply_jdeps.gradle

import com.shizhuang.duapp.jdepsplugin.GradlePlugin

initscript {
    dependencies {
        classpath 'com.shizhuang.duapp.plugin:poizon-jdeps:0.0.18'
    }
}

gradle.rootProject{
    it.buildscript {
        dependencies {
            classpath 'com.shizhuang.duapp.plugin:poizon-jdeps:0.0.18'
        }
    }
    
    apply plugin: GradlePlugin
}

使用shell调用所有组件的分析任务,扫描了112个gradle project,有52个组件存在无用的implementation组件,总计移除221个。

总结

poizon-jdeps应用于工程后,简化了处理源码依赖/aar依赖对齐的工作,拉掉无用implementation组件和处理完common模块的api组件后,平均提高了10%左右的业务模块compileClasspath组件有效使用率,对提高增量编译的占比有一定作用,sync时间从之前的25秒左右降到10秒左右。

未来规划

1、常量引用,编译时做值copy丢失关联关系;从java、kotlin源码出发解析出AST生成JarInfo
2、通过Class.forName("com.example.Test") 引用的扫描不出来;解析MethodInfo字节码
3、资源文件里的引用暂时不支持;解析layout的xml文件取出自定义View对应的类,hook aapt流程拿到资源的link信息
4、扫描的入口是工程的full_jar,所以需要依赖编译成功才能扫描;如果从java、kotlin源文件出发解析出工程的selfClasses、refClasses,就可以脱离编译直接扫描出丢失的类,进而从组件池里映射出来丢失的依赖
5、studio External libraries侧边栏只会展示所有工程中compileClasspath中的组件,很多基础组件拉掉以后在这里就展示不出来了,会造成开发人员的困扰,也算是一个副作用吧;hook studio sync,把runtime组件也加到External libraries里
6、在工程中使用时有些场景需要shell脚本配合;后续尽量把通用的需求收敛到poizon-jdeps中

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容