深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
其实 Gradle 是支持动态修改项目中的依赖的。动态修改依赖在上述场景,特别是组件化的场景中非常有效。这里我参考了公司组件化的切换源码的实现方式,用了 90 行左右的代码就实现了上述需求。
2.1 配置文件和工作流程抽象
这种实现方式里比较重要的一环是对切换源码工作机制的抽象。这里我重新定义了一个 json 配置文件,
[
{
“name”: “AndroidUtils”,
“url”: “git@github.com:Shouheng88/AndroidUtils.git”,
“branch”: “feature-2.8.0”,
“group”: “com.github.Shouheng88”,
“open”: true,
“children”: [
{
“name”: “utils-core”,
“path”: “AndroidUtils/utils”
},
{
“name”: “utils-ktx”,
“path”: “AndroidUtils/utils-ktx”
}
]
}
]
它内部的参数的含义分别是,
name
:工程的名称,对应于 Github 的项目名,用于寻找克隆到本地的代码源码url
:远程仓库的地址branch
:要启用的远程仓库的分支,这里我强制自动切换分支时的本地分支和远程分支同名group
:依赖的 group idopen
:表示是否启用源码依赖children.name
:表示子工程的 module 名称,对应于依赖中的artifact id
children.path
:表示子工程对应的相对目录
也就是说,
- 一个工程下的多个子工程的
group id
必须相同 children.name
必须和依赖的artifact id
相同
上述配置文件的工作流程是,
def sourceSwitches = new HashMap<String, SourceSwitch>()
// Load sources configurations.
parseSourcesConfiguration(sourceSwitches)
// Checkout remote sources.
checkoutRemoteSources(sourceSwitches)
// Replace dependencies with sources.
replaceDependenciesWithSources(sourceSwitches)
- 首先,Gradle 在 setting 阶段解析上述配置文件
- 然后,根据解析的结果,将打开源码的工程通过 project 的形式引用到项目中
- 最后,根据上述配置文件,将项目中的依赖替换为工程引用
2.2 为项目动态添加子工程
如上所述,这里我们忽略掉 json 配置文件解析的环节,直接看拉取最新分支并将其作为子项目添加到项目中的逻辑。该部分代码实现如下,
/** Checkout remote sources if necessary. */
def checkoutRemoteSources(sourceSwitches) {
def settings = getSettings()
def rootAbsolutePath = settings.rootDir.absolutePath
def sourcesRootPath = new File(rootAbsolutePath).parent
def sourcesDirectory = new File(sourcesRootPath, “open_sources”)
if (!sourcesDirectory.exists()) sourcesDirectory.mkdirs()
sourceSwitches.forEach { name, sourceSwitch ->
if (sourceSwitch.open) {
def sourceDirectory = new File(sourcesDirectory, name)
if (!sourceDirectory.exists()) {
logd(“clone start [
n
a
m
e
]
b
r
a
n
c
h
[
name] branch [
name]branch[{sourceSwitch.branch}]”)
“git clone -b ${sourceSwitch.branch}
s
o
u
r
c
e
S
w
i
t
c
h
.
u
r
l
"
.
e
x
e
c
u
t
e
(
n
u
l
l
,
s
o
u
r
c
e
s
D
i
r
e
c
t
o
r
y
)
.
w
a
i
t
F
o
r
(
)
l
o
g
d
(
"
c
l
o
n
e
c
o
m
p
l
e
t
e
d
[
{sourceSwitch.url} ".execute(null, sourcesDirectory).waitFor() logd("clone completed [
sourceSwitch.url".execute(null,sourcesDirectory).waitFor()logd("clonecompleted[name] branch [KaTeX parse error: Expected 'EOF', got '}' at position 26: …tch.branch}]") }̲ else { def sb …{currentBranch}], checkout branch [${sourceSwitch.branch}]”)
def out = new StringBuffer()
“git pull”.execute(null, sourceDirectory).waitFor()
“git checkout -b
s
o
u
r
c
e
S
w
i
t
c
h
.
b
r
a
n
c
h
o
r
i
g
i
n
/
{sourceSwitch.branch} origin/
sourceSwitch.branchorigin/{sourceSwitch.branch}”
.execute(null, sourceDirectory).waitForProcessOutput(out, System.err)
logd(“checkout completed: KaTeX parse error: Expected 'EOF', got '}' at position 27: …ng().trim()}") }̲ } // After che…{child.name}”)
settings.project(“😒{child.name}”).projectDir = new File(sourcesDirectory, child.path)
}
}
}
}
这里,我将子项目的源码克隆到 settings.gradle
文件的父目录下的 open_sources
目录下面。这里当该目录不存在的时候,我会先创建该目录。这里需要注意的是,我在组织项目目录的时候比较喜欢将项目的子工程放到和主工程一样的位置。所以,上述克隆方式可以保证克隆到的 open_sources
仍然在当前项目的工作目录下。
然后,我对 sourceSwitches
,也就是解析的 json 文件数据,进行遍历。这里会先判断指定的源码是否已经拉下来,如果存在的话就执行 checkout 操作,否则执行 clone 操作。这里在判断当前分支是否为目标分支的时候使用了 git rev-parse --abbrev-ref HEAD
这个 Git 指令。该指令用来获取当前仓库所处的分支。
最后,将源码拉下来之后通过 Settings
的 include()
方法加载指定的子工程,并使用 Settings
的 project()
方法指定该子工程的目录。这和我们在 settings.gradle
文件中添加子工程的方式是相同的,
include ‘:utils-core’, ‘:utils-ktx’
project(‘:utils-core’).projectDir = new File(‘…/AndroidUtils/utils’)
project(‘:utils-ktx’).projectDir = new File(‘…/AndroidUtils/utils-ktx’)
2.3 使用子工程替换依赖
动态替换工程依赖使用的是 Gradle 的 ResolutionStrategy 这个功能。也许你对诸如
configurations.all {
resolutionStrategy.force ‘io.reactivex.rxjava2:rxjava:2.1.6’
}
这种写法并不陌生。这里的 force
和 dependencySubstitution
一样,都属于 ResolutionStrategy 提供的功能的一部分。只不过这里的区别是,我们需要对所有的子项目进行动态更改,因此需要等项目 loaded 完成之后才能执行。
下面是依赖替换的实现逻辑,
/** Replace dependencies with sources. */
def replaceDependenciesWithSources(sourceSwitches) {
def gradle = settings.gradle
gradle.projectsLoaded {
gradle.rootProject.subprojects {
configurations.all {
resolutionStrategy.dependencySubstitution {
sourceSwitches.forEach { name, sourceSwitch ->
sourceSwitch.children.each { child ->
substitute module(“
s
o
u
r
c
e
S
w
i
t
c
h
.
a
r
t
i
f
a
c
t
:
{sourceSwitch.artifact}:
sourceSwitch.artifact:{child.name}”) with project(“😒{child.name}”)
}
}
}
}
}
}
}
这里使用 Gradle 的 projectsLoaded
这个点进行 hook,将依赖替换为子工程。
此外,也可以将子工程替换为依赖,比如,
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!**
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新