Android 组件化场景下多module依赖优雅实践方案

本文讨论了在Maven项目中,如何正确处理测试依赖以及不同依赖声明方式(如API,implementation,compileOnly)的影响。作者强调了在静态包使用时维护依赖传递的重要性,并提出了一种通过代码动态配置依赖以兼顾效率和灵活性的方法。
摘要由CSDN通过智能技术生成

我们发现,关于测试相关的依赖并没有被收录到pom文件中。这很合理,测试代码是针对该module的,并不需要提供给使用方,其依赖自然也不需要传递。我们知道,AGP中现在有4种声明依赖的方式(除去testXXX这种变种)

  • api

  • implementation

  • compileOnly

  • runtimeOnly

runtimeOnly对应以前的apk方式声明依赖,我们直接忽略掉,测试一下生成的pom文件。

dependencies {

api “org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version”

implementation ‘androidx.core:core-ktx:1.3.2’

compileOnly ‘androidx.appcompat:appcompat:1.2.0’

compileOnly ‘com.google.android.material:material:1.2.1’

testImplementation ‘junit:junit:4.+’

androidTestImplementation ‘androidx.test.ext:junit:1.1.2’

androidTestImplementation ‘androidx.test.espresso:espresso-core:3.3.0’

}

<?xml version="1.0" encoding="UTF-8"?>

<project xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd” xmlns=“http://maven.apache.org/POM/4.0.0”

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”>

4.0.0

leobert

B

1.0.0

aar

org.jetbrains.kotlin

kotlin-stdlib

1.4.21

compile

androidx.core

core-ktx

1.3.2

compile

使用compileOnly方式的并没有被收录到pom文件中,而api和implementation 方式,在pom文件中,都表现为 采用compile的方案应用依赖。

ps:api和implementation在编码期的不同,不是我们讨论的重点,略。

回到我们开始的问题,将library发布时,按照约定,会将library本身的依赖收录到pom文件中。相应的,使用方使用 仓库中的依赖项时,gradle会拉取其对应的pom文件,并添加依赖。

所以,如果我们直接使用一个编译好的静态包,而丢弃了他对应的pom文件时,可能会丢失依赖,出现打包失败或者运行异常。这意味着我们需要人为维护依赖传递

我们记住这些内容,并先放到一边。

下沉后,library会有多个层级

=============================================================================

例如图中:APP => A => B, 即APP依赖A,A依赖B,而A和B都是library

我们知道,对于B,并不会有什么说法,只会出现在A和APP

如果不使用静态包,那么A会声明:

api project(‘:B’)

//或者

implementation project(‘:B’)

我们先看一下,这样生成的library-A的pom文件

<?xml version="1.0" encoding="UTF-8"?>

<project xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd” xmlns=“http://maven.apache.org/POM/4.0.0”

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”>

4.0.0

leobert

A

1.0.0

aar

Demo

B

unspecified

compile

会得到groupID是项目名,artifactId是module名,version是未知的一个依赖项。假如我将A编译为静态包并发布到仓库,并运用了pom中的依赖描述,一定会得到无法找到:Demo-B-unspecified.pom 的问题。

当然,这个问题可以通过在APP中重新声明 B的依赖 来解决。

这意味着,我们需要时刻保持警惕,维护各个module的依赖。否则,我们无法同时享受:静态包减少编译 & 随心的修改局部并集成测试

这显然是一件不人道主义的事情。

反思一下,对于A而言,它需要B,但仅在两个时机需要:

  • 编译时受检,完成编译

  • 运行时

作为一个library,它本身并不对应运行时,所以,compileOnly 是其声明对B的依赖的最佳方式。这意味着,最终对应运行时 的内容,即APP,需要在编译时加入 对B的依赖。在原先 A使用Api方式声明对B的依赖时,是通过gradle分析pom文件实现的依赖加入。而现在,需要人为维护,只需要实现 人道主义,就可以鱼和熊掌兼得。

反思依赖传递的本质

=====================================================================

一般我们会像下面的演示代码一样声明依赖:

//APP:

implementation project(‘A’)

implementation project(‘Foo’)

//A:

implementation project(‘B’)

implementation project(‘Bar’)

因为依赖传递性,APP其实依赖了A,Foo,B,Bar。其实就是一颗树中,除去根节点的节点集合。而对于一个非根节点,它被依赖的形式只有两种:

  • 静态包,不需要重新编译,节约编译时间

  • module,需要再次编译,可以运用最新改动

我们可以定义这样一个键值对信息:

project.ext.depRules = [

“B”: “p”,

“A”: “a”

]

"p"代表使用project,"a"代表使用静态包。

并将这颗树的内容表达出来:我们先忽略掉Foo和Bar

project.ext.deps = [

“A” : [

“B”: [

“p”: project(‘:B’),

“a”: ‘leobert:B:1.0.0’

]

],

“APP”: [

“A”: [

“p”: project(‘:A’),

“a”: ‘leobert:A:1.0.0’

]

]

].with(true) {

A.each { e ->

APP.put(e.key, e.value)

}

}

以A为例,我们可以通过代码实现动态添加依赖:

project.afterEvaluate { p ->

println(“handle deps for:” + p)

deps.A.each { e ->

def rule = depRules.get(e.key)

println(“find deps of A: rule is” + rule + " ,dep is:" + e.value.get(rule).toString())

project.dependencies.add(“compileOnly”, e.value.get(rule))

}

}

同理,对于APP:

project.afterEvaluate { p->

println(“handle deps for:” + p)

deps.APP.each { e ->

def rule = depRules.get(e.key)

println(“find deps of App:rule is” + rule + " ,dep is:" + e.value.get(rule).toString())

project.dependencies.add(“implementation”, e.value.get(rule))

}

}

查看输出:

Configure project :A

handle deps for:project ‘:A’

find deps of A: rule isp ,dep is:project ‘:B’

Configure project :app

handle deps for:project ‘:app’

find deps of App:rule isa ,dep is:leobert:A:1.0.0

find deps of App:rule isp ,dep is:project ‘:B’

这样,我们就可以通过修改对应节点的依赖方式配置而实现鱼和熊掌兼得。不再受pom文件的约束。当时,我们回到上面说的不人道主义之处,我们通过了with 函数,将A自身的依赖信息,注入到APP中。

但是当树的规模变大时,人为维护就很累了。这是必须要解决的,当然,这很容易解决。我们直接使用递归处理即可

贴近人的直观感受才优雅,逐步实现人道主义 我们添加一个全局闭包:

ext.utils = [

applyDependency: { project, e ->

def rule = depRules.get(e.key)

println(“find deps of App:rule is " + rule + " ,dep is:” + e.value.get(rule).toString())

project.dependencies.add(“implementation”, e.value.get(rule))

try {

println(“try to add sub deps of:” + e.key)

def sub = deps.get(e.key)

if (sub != null && sub.get(“isEnd”) != true) {

sub.each { se ->

ext.utils.applyDependency(project, se)

}

}

} catch (Exception ignore) {

}

}

]

注意,因为我们定义的依赖信息是:moduleName-> (moduleName -> (scopeName-> depInfo)) 的方式。

这导致我们判断末端节点有一定的困难,即递归的尾部判断存在困难,我们需要人为标记一下末端节点 这时,我们只需描述一下树即可:同样忽略Foo,Bar

project.ext.deps = [

“A” : [

“B”: [

“isEnd”: true,

“p” : project(‘:B’),

“a” : ‘leobert:B:1.0.0’

]

],

“APP”: [

“A”: [

“p”: project(‘:A’),

“a”: ‘leobert:A:1.0.0’

]

]

]

问题基本得到解决了,但是并不优雅。

优雅,优雅,优雅

====================================================================

我们不妨再修改一下对依赖树的描述方式,将节点信息和树结构分开,重新改进:

更人道主义的依赖描述

project.ext.deps = [

“A” : [“B”],

“app”: [“A”]

]

project.ext.modules = [

“A”: [

“p”: project(‘:A’),

“a”: ‘leobert:A:1.0.0’

],

“B”: [

“p” : project(‘:B’),

“a” : ‘leobert:B:1.0.0’

]

]

project.ext.depRules = [

“B”: “p”,

“A”: “a”

]

抽象添加依赖的过程,递归处理每一个节点的依赖收集,并向宿主module添加,当某个节点在ext.deps中没有任何依赖时,归:

ext.utils = [

applyDependency: { project, scope, e ->

def rule = depRules.get(e)

def eInfo = ext.modules.get(e)

println("find deps of " + project + “:rule is " + rule + " ,dep is:” + eInfo.get(rule).toString())

project.dependencies.add(scope, eInfo.get(rule))

def sub = deps.get(e) //list deps of e

println(“try to add sub deps of:” + e + " —> " + sub)

if (sub != null && !sub.isEmpty()) {

sub.each { dOfE ->

ext.utils.applyDependency(project, scope, dOfE)

}

}

}

]

每个module只需要指定自己的scope:

//:app

project.afterEvaluate { p ->

println(“handle deps for:” + p)

deps.get(p.name).each { e ->

rootProject.ext.utils.applyDependency(p,“implementation”,e)

}

}

//:A

project.afterEvaluate { p ->

println(“handle deps for:” + p.name)

deps.get(p.name).each { e ->

rootProject.ext.utils.applyDependency(p,“compileOnly”,e)

}

}

只要不是独立运行的module,就是compileOnly,否则就是 implementation。输出也容易拍错:

Configure project :A

handle deps for:A

find deps of project ‘:A’:rule is p ,dep is:project ‘:B’

try to add sub deps of:B —> null

Configure project :app

handle deps for:project ‘:app’

find deps of project ‘:app’:rule is a ,dep is:leobert:A:1.0.0

try to add sub deps of:A —> [B]

find deps of project ‘:app’:rule is p ,dep is:project ‘:B’

try to add sub deps of:B —> null

测试一个复杂场景 我们再上图的基础上,让B和Foo依赖Base

project.ext.deps = [

“app”: [“A”, “Foo”],

“A” : [“B”, “Bar”],

“Foo”: [“Base”],

“B” : [“Base”],

]

project.ext.modules = [

“A”: [

“p”: project(‘:A’),

最后

总而言之,成功是留给准备好的人的。无论是参加什么面试,都要做好充足的准备,注意好面试的礼仪和穿着,向面试官表现出自己的热忱与真诚就好。即使最后没有过关,也要做好经验的总结,为下一次面试做好充足准备。

这里我为大家准备了一些我在面试后整理的面试专题资料,除了面试题,还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料分享给大家,希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

毕竟不管遇到什么样的面试官,去面试首先最主要的就是自己的实力,只要实力够硬,技术够强,就不怕面试拿不到offer!

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
“A”, “Foo”],

“A” : [“B”, “Bar”],

“Foo”: [“Base”],

“B” : [“Base”],

]

project.ext.modules = [

“A”: [

“p”: project(‘:A’),

最后

总而言之,成功是留给准备好的人的。无论是参加什么面试,都要做好充足的准备,注意好面试的礼仪和穿着,向面试官表现出自己的热忱与真诚就好。即使最后没有过关,也要做好经验的总结,为下一次面试做好充足准备。

这里我为大家准备了一些我在面试后整理的面试专题资料,除了面试题,还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料分享给大家,希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

毕竟不管遇到什么样的面试官,去面试首先最主要的就是自己的实力,只要实力够硬,技术够强,就不怕面试拿不到offer!

[外链图片转存中…(img-0VzdGOZQ-1714690056143)]

[外链图片转存中…(img-wOghgRPk-1714690056144)]

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!希望读到这的您能点个小赞和关注下我,以后还会更新技术干货,谢谢您的支持!

[外链图片转存中…(img-PAki4Cbw-1714690056144)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值