前言
我一直把我的毕设项目【小鹅事务所】当成日常记账本来使用。
可是最近总觉得厌烦了,它的功能都符合我的需求,总觉得差点什么。打开代码一看,金玉在外,败絮其中。看看别人家的NowInAndroid,是如此的优雅。
表面上我还在用着。但是内心已经暗下决心,要把小鹅事务所重构成NIA的样子,它才是我心目中的Android最佳实践。
项目架构重构
首先要着手重构的当然是项目架构。小鹅事务所的架构是这样的:
包含3个模块,CalendarView日历模块来源于开源项目。实际上其实所有应用代码都写在了app模块中,也就是一个单模块的项目,app模块中又包含了记事本、记账本、事务、纪念日的代码耦合在一起。
它的好处就是写起来很爽。但是这不行啊,你看看别人NIA分为十几个模块井井有条,有鼻子有眼的。我这个就全部挤在一团,丑死了。于是我打算把不同的模块先抽出来弄成这个样子。
刚开始动工,我又犯难了,项目依赖SDK的时候全部写在dependencies里的。如果要挪成不同的模块,第一个需要解决的就是依赖版本的统一。
VersionCatalogs
在NIA项目中有一个libs.version.toml
文件管理依赖,它的官方文档是这个docs.gradle.org/current/use…。
If a
libs.versions.toml
file is found in thegradle
subdirectory of the root build, then a catalog will be automatically declared with the contents of this file.
也就是说只要新建一个libs.version.toml
文件到gradle文件夹中,就会自动生成一个VersionCatalogs,可以直接在依赖的时候拿来用。
这种方式太优雅了,这小代码写的可真够俏。
为什么在plugins无法识别出现爆红呢?于是我看了一下NIA,也有这个问题,他们是加了一个注解@Suppress("DSL_SCOPE_VIOLATION")
解决的,并且注释了一个链接,有兴趣可以看看。youtrack.jetbrains.com/issue/KTIJ-…
Plugins
上面截图中出现的id(xxx.android.library)
其实就是使用了Gradle的注册Plugin的功能,只需要在build-logic
中的build.gradle
注册就可以了,就可以在编译的时候自动运行执行Plugin类的逻辑,而这个Plugin类可以自定义。
gradlePlugin {
plugins {
register("androidApplicationCompose") {
id = "nowinandroid.android.application.compose"
implementationClass = "AndroidApplicationComposeConventionPlugin"
}
...
}
}
class AndroidApplicationComposeConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
pluginManager.apply("com.android.application")
val extension = extensions.getByType<ApplicationExtension>()
configureAndroidCompose(extension)
}
}
}
在Kotlin类中写Gradle编译代码,还有代码联想,这多是一件美事啊,编译代码还可以互相复用,例如下面的configureAndroidCommon
函数。
build-logic复合构建
上文中经常出现build-logic
,它的作用其实和buildSrc
类似,用来定义编译脚本的。上文的Plugin就可以写在这个项目中。
与buildSrc
不同,它是一个独立的Gradle项目,不依赖当前项目,也就是说可以独立编译。于是它就享受不到自动生成的VersionCatalogs了,怎么办呢?
官方文档写得很明确,可以在另一个项目的settings.gradle.kts
中找到指定文件显式去生成。
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
关于build-logic
的减少重复构建、减少构建时间的之类好处就不细说啦。
分模块
到这里终于可以将app模块的代码抽出来分成多个模块了,由于我写了很多Plugin插件,所以在新建模块的时候就可以很好的复用了,举个例子我想弄一个home模块,build.gradle.kts
可以写成这样。
剩下的就只有挪代码、挪代码和处理爆红代码的工作了,非常枯燥。
就像整理一个杂乱无章乱七八糟的房间那样。一开始会干劲十足,等做到后面会慢慢开始泄气。我怎么写了这么多东西,这些代码为什么耦合度会这么大,此时我还没意识到,这仅仅只是一个开始。
View替换成Compose
我刚做这个毕设项目的时候,Compose刚出1.0稳定版。
我说:我要用全Compose来写UI界面,是不很大胆。
我将这个全Compose计划告诉我的其他同学,他们都很兴奋。
我说:只用三周,一周学习,一周编写代码,一周改BUG就可以写完这个项目。
国内很少资料,我参考了很多国外的资料,Compose实在太酷了。
经过一年多对Compose的研究和实践,我发现我还是忘不了这个计划,我很想把小鹅事务所变成Compose的模样。
设计
既然当初用的Material2来设计小鹅事务所,那现在,我就要用Material3设计,还要加入Android12最新的动态配色!
于是我打开了Figma,刚想来一场酣畅淋漓的设计。后来我想想,设计稿的用处是设计用来和开发对接的产物。而我既是设计也是开发,设计稿就变成了一个可有可无的东西。
Compose代码编写
刚开始将View换成Compose的时候,我非常兴奋,终于可以大规模地实践Compose了。我于是把代码写得非常规范,甚至在控件中加上Preview
。每每写完一个控件,就美美把之前的View和Layout文件删除。
当这项工作进行到一半的时候,我发现之前的UI界面好多,有的写得非常优雅,有的写得有点乱。每一行代码都牵引出我的回忆,有种帮同居对象搬走这个屋子的感觉。
慢慢地删除旧代码带来的剥离感让我陷入了沉思,我是不是一开始就不应该开始这项重构工作,凑合凑合也不是不能用。但是Compose带来的新鲜感让我无法拒绝。
在不断自我拉扯中,我潦草地完成了这些工作,Preview也不写了,代码也不那么优雅了。
在重构完成之际,我决定留下了这几行代码作为纪念。
思考
小鹅事务所的重构一期工作暂且告一段落,花了3个多月,仍然没有重构到NIA那般样子。
我在工作中也不断接触Android项目,有的项目在使用Java,很多项目仍然使用过去的架构、架构、设计模式。当Android最佳实践不断推陈出新的时候,不是所有项目都能跟得上。我这么一个小项目稍微重构一下都需要花上几个月时间,而大项目是几乎不可能像这样大规模重构的,这得花多少时间啊...这就造成了一个成规模的项目无法真正追赶上谷歌所谓的“最佳实践”。
Android的最佳实践只是一种理想,在历史代码和程序员考核面前,它似乎一文不值。
代码
最后放一下重构之后的代码仓库: github.com/MReP1/Littl…