一、背景
差不多一年前写了一篇文《一文聊聊 Android 项目架构的方方面面》,概括的聊了一下个人认为的构建一个项目的需要考虑的很多方向,非常幸运的得到了很多读者的喜欢。
上一篇主要是从大的方向着眼考虑,本篇想从相对微观的一些角度聊聊,当我们准备推进开发一个复杂的Android工程时,我们需要考虑哪些小事情?
二、梳理工程中的命名规则
为什么要规划工程中各个文件的命名规则?
以drawable文件夹下的资源文件为例,如果不将命名规则提前梳理清楚,随着业务的持续迭代后续将会出现drawable文件爆炸的情况。由于没有统一命名规则,新开发业务时开发者不清楚某个背景资源文件(例如:背景纯白-半径角度是8dp这样的文件)在工程中是否存在,最好的解决方案就是再写一遍。复用性可能仅限于自己所写的资源文件中。
资源命名规则
如前面所说,很多资源没有统一的命名,在后期开发时存在反复引入、反复建设的情况。
如Drawable文件,当我们统一命名规则后,在后续开发中就可以先提前根据命名规则前提前搜索是否存在该文件。所在在我们工程中的drawable文件(如shape)的命名都与业务无关,都是根据其填充颜色、圆角是多少、边框宽度、边框颜色来统一命名。
icon资源也一样,如返回、搜索、更多、设置、编辑等图标资源等都集中命名管理。 例如:
- icon_back
- icon_search
- icon_more
- icon_setting
不过后续在实际开发中也存在着一个返回图标设计师非要搞成不同的样式的情况,从开发的角度来看可能差异性不大,但设计师坚持如此,搞得开发也没有办法。最终只能在命名上想办法,尽可能体现出来差异,如存在icon_back也存在icon_back_arrow。
其他文件命名统一
- Activity文件:统一使用XxxActivity命名
- Fragment文件:统一使用XxxFragment命名
- ViewModel文件:统一使用XxxViewModel,这里的Xxx与前面Activity完全对应
- SQL接口封装:统一使用XxxDao的方式命名
- 数据逻辑封装:统一使用XxxRepository类命名
- 工具类封装:统一使用XxxUtil命名
- Activity对应的xml:统一使用activity_xxx的方式命名
- Fragment对应的xml:统一使用fragment_xxx的方式命名
- dialog对应的xml:统一使用dialog_xxx的方式命名
- RecyclerView的Item的xml:统一使用item_xxx的方式命名
- 通用图标:通常以icon_xxx命名
- 业务图标:通常以ic_xxx命名
- 等等。
之所以搞以上这些规则主要还是为了后续迭代时减少理解成本。
规则统一之后我们利用AS的插件,可以一键生成以上代码的模版代码,在实际开发过程也挺方便的。例如当我创建一个Activity时,默认你就要有对应的ViewModel文件、XML文件,那么就可以通过插件一键生成。
工程目录结构
这里请问读者,你的工程目录是怎样的?是以业务模块来命名还是以文件的分类来命名。
笔者所在的工程,是差不多10年的项目了,有非常多的页面。单纯的以文件分类命名不利于我们查找某个页面。所以我们采用的是,除了统一封装的如组件、工具等文件夹,其他业务逻辑代码均是先以业务功能命名文件夹,然后每个业务功能下再按照文件分类来命名。
三、规划文件路径
为什么要规划文件路径?
App通常需要在磁盘存储一些信息,例如日志文件、配置文件、动态下载的so、用户手动下载的文件、数据库文件等。那么这些文件分别都存储在内部还是外部?当不同用户在同一个手机上使用App时,会不会出现文件错乱的问题?哪些路径下的文件是可以被删除的、哪些路径下的文件是不可以被删除的?
针对App存储在磁盘的文件笔者认为大致需要考虑以下几个方面:
基于存储位置
在Android 12之前通常App会在sdcard上规划一个目录用于存储相关文件,例如你的sdcard中大概率会有一个tencent目录。
Android 12文件分区之后,App能够访问的sdcard上的目录受到了限制,各家的应用都在sdcard上的公共目录迁移到了私有目录。那么哪些文件适合放到外部私有,哪些适合放在内部私有目录呢?
内部私有
- 需要绝对安全的,如秘钥、用户身份凭证
- 数据库文件
- 配置文件
- 日志文件
外部私有
- 相对大的文件:如用户下载的文件、或者用户主动缓存的较大的文件
- 允许被删除可以重新下载的文件等
基于账号系统
当App存在账号体系时,尤其是不登录时基本无法使用App。那么建议直接在私有目录中先通过账号id来规划目录,将不同的用户通过账号id直接进行隔离。
例如:私有目录
- zhangsan
- cache
- logs
- config
- databases
- msg
- voice
- send
- receive
- img
- send
- receive
- voice
- lisi
- cache
- logs
- config
- databases
使用账号参与规划目录好处是可以统一管理,上层业务不需要关心不同账号的文件、信息等存在混淆的问题。
统一管理 & 清理
在我们的工程中会统一管理路径地址,各业务不要自行创建路径。因为为了应对用户反馈,App在手机中占用了过多的存储空间,App中存在自动清理功能,针对文件路径会有白名单逻辑,仅在白名单中路径中文件可以在清理时不被删除,其他路径都会删除。
四、规划日志
为什么要提前规划日志?
这里面从笔者的角度来讲主要是两个方面。
- 一是规划Tag,一个业务逻辑统一使用同一个Tag。
- 二是规划哪些通用逻辑需要写日志,例如在笔者的工程中我们会统一埋Activity生命周期的日志。
Tag
在笔者负责的工程中,我们初期没有针对日志Tag进行统一规范,最终出现的问题就是每个Activity都在头部声明了一个Activity名字的Tag,而一个业务逻辑通常可能会贯穿几个页面。一个页面也存在不同的业务功能。这就导致开发者没有办法通过Tag将某个业务逻辑的日志统一过滤出来,排查某个业务Bug时就非常痛苦并且低效。
所以我们痛定思痛统一管理Tag,规定某个业务模块不区分上层UI还是底层逻辑,统一使用同一个Tag,确保排查业务逻辑Bug时,可以基于某个Tag将日志统一过滤出来。
日志信息
关于日志我们在业务开发时,要求务必在关键路径添加日志,保证if else逻辑,try cache等所有逻辑分支都存在日志。
同时我们也会留存一些必要的信息在日志中。如当前打包的分支,手机型号、厂商等信息。还有每个Activity的声明周期都会留存日志,帮助开发者确认用户操作路径,整理时间线。
五、UI组件规划
为什么要规划UI组件?
笔者的理由还是从业务出发,拿到设计稿时能够复用的UI组件提前规划出来,在后续开发UI时可以节省非常多的时间。也可以有效的倒逼设计同学注意设计的统一规范。
通常项目初始时,可以封装哪些组件?
下面笔者简单梳理了一下:
- BaseTextView
- BaseImageView
- BaseRecycleView
- BaseEdtText(如支持计数)
- 业务通用TitleBar
- 业务通用ToastUtils
- 业务通用Dialog
- 业务通用Loading
- BaseRefreshLayout
- BaseRefreshHeader
- 空状态
- 加载失败状态
- BaseSwitchView
- 等等
注意以上组件实际上都是从业务角度出发封装的逻辑,最终也是为了在业务开发中节省时间的同时保持体验一致,减少业务Bug。
所以梳理UI组件的原则也非常简单,就是哪些UI预期会有大量复用场景的就提前设计好,后期业务扩展时你会感谢前面的你。
六、路由规则
为啥要规划路由规则,Android App一定要引入路由吗?
如果你的工程中都没有几个页面,笔者认为完全不同搞这么复杂,怎么简单怎么写就行。哪怕页面多也不一定就需要,更多时还是从结合业务的角度来看的。
在笔者所在的工程中,引入路由的原因主要是以下几点:
- 存在非常多的页面,我们希望尽可能解耦
- 存在统一基于登录态,或者音视频状态的跳转拦截逻辑
- 存在通过HTTP接口返回的数据,直接跳转页面的逻辑,这点就需要移动两个段路由路径一致。
那么基于以上原则Android与iOS,一同讨论统一了页面的路由规则。
七、代码风格
这点相信不用多说,只要是程序员大家都比较了解了。
八、总结
以上为笔者工作多年自行总结的一些经验,需要说明的是在以上所有的实践中,笔者认为可能没有所谓的最优解,即仁者见仁智者见智。适合你的、适合你们团队的可能就是最好的。