会得到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’),
“a”: ‘leobert:A:1.0.0’
],
“B”: [
“p”: project(‘:B’),
“a”: ‘leobert:B:1.0.0’
],
“Foo”: [
“p”: project(‘:Foo’),
],
“Bar”: [
“p”: project(‘:Bar’),
],
“Base”: [
“p”: project(‘:Base’),
]
]
project.ext.depRules = [
“B” : “p”,
“A” : “a”,
“Foo” : “p”,
“Bar” : “p”,
“Base”: “p”
]
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 —> [Base]
find deps of project ‘:A’:rule is p ,dep is:project ‘:Base’
try to add sub deps of:Base —> null
find deps of project ‘:A’:rule is p ,dep is:project ‘:Bar’
try to add sub deps of:Bar —> 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, Bar]
find deps of project ‘:app’:rule is p ,dep is:project ‘:B’
try to add sub deps of:B —> [Base]
find deps of project ‘:app’:rule is p ,dep is:project ‘:Base’
try to add sub deps of:Base —> null
find deps of project ‘:app’:rule is p ,dep is:project ‘:Bar’
try to add sub deps of:Bar —> null
find deps of project ‘:app’:rule is p ,dep is:project ‘:Foo’
try to add sub deps of:Foo —> [Base]
find deps of project ‘:app’:rule is p ,dep is:project ‘:Base’
try to add sub deps of:Base —> null
Configure project :Bar
handle deps for:Bar
Configure project :Base
handle deps for:Base
Configure project :Foo
handle deps for:Foo
find deps of project ‘:Foo’:rule is p ,dep is:project ‘:Base’
try to add sub deps of:Base —> null
随着,树规模的增大,阅读依赖关系还算明显,但是阅读日志,又不太优雅了。
=================================================================
我们通过探寻,发现了一种可以 鱼和熊掌兼得 地依赖处理方式,让我们在Android领域组件化场景下(单项目,多module),能够灵活地切换:
-
静态包依赖,缩短编译时间
-
项目依赖,快速部署变更进行集成测试
对了,上面我们没有重点提到如何切换,其实非常地简单:
只需要修改 project.ext.depRules 中对应的配置项即可。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
-
Android前沿技术大纲
-
全套体系化高级架构视频
Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!*
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
-
Android前沿技术大纲
[外链图片转存中…(img-znROghUW-1712358014588)]
-
全套体系化高级架构视频
[外链图片转存中…(img-J0qJuzCT-1712358014589)]
Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!