【NowInAndroid架构拆解】系列文章
- 【NowInAndroid架构拆解】(1)分层设计与模块化
- 【NowInAndroid架构拆解】(2)数据层的设计和实现之model与database
- 【NowInAndroid架构拆解】(3)数据层的设计和实现之network
- 【NowInAndroid架构拆解】(4)数据层的设计和实现之data
- 【NowInAndroid架构拆解】(5)VM层的设计和实现之ForYouViewModel
- 【NowInAndroid架构拆解】(6)View层的设计和实现之Navigation路由
- 【NowInAndroid架构拆解】(7)UI层解析——MainActivity构建过程
- 【NowInAndroid架构拆解】(8)UI层解析——ForYou页面展示
- 【NowInAndroid架构拆解】(9)重新审视NowInAndroid架构设计
- 【NowInAndroid架构拆解】番外篇1之Jetpack Compose Navigation
- 【NowInAndroid架构拆解】番外篇2之Bottom Navigation底部导航
- 【NowInAndroid架构拆解】番外篇3之给xml布局者最佳的Jetpack Compose介绍文章
在完成了前面8篇架构拆解文章后,是时候进行阶段性的总结了,在本文中,我将重新审视NowInAndroid项目,尝试从宏观角度分析,该项目是如何在可伸缩性、模块化、可测试等角度提供最佳实践的思路的。
概览:ForYou 页面的实现
这一次我们同样以 ForYou 页面为例进行分析,一方面是因为这个页面足够简单,另一方面,它也很好地体现了,在 Data 层和 UI 层之间,数据流自底向上、以及控制流自顶向下的传递过程。
- UI:响应式 UI,监听 Data 层发送来的数据流
- Data:单一可信数据源
- 数据流:从 Data 流向 UI,使用 Kotlin Flow 实现
- 控制流:从 UI 流向 Data,例如“添加/移除书签”操作
Data 层
Data 的分层设计
对上层仅暴露 Repository,内部则由不同的 Data source 作为数据源。
LocalDataSource 实现
在 NowInAndroid APP 中,数据以新闻流的方式呈现,因此它设计了一个本地数据源 LocalDataSource
,用来提供本地缓存的数据。
一方面可以获取新闻列表,另一方面也会将用户进行的“收藏”操作同步到数据源。
LocalDataSource
代码实现如下所示:
// ===> 封装了数据源DataStore的具体实现,向上层提供数据流
class LocalDataSource(private val dataStore: DataStore<userPreferences>) {
val bookmarksStream : Flow<List<String>> = dataStore.data.map {
it.bookmarksMap.keys.toList()
}
// ===> 更新数据,耗时操作
suspend fun toggleBookmark(newsResourceId: String, isBookmarked: Boolean) {
...
if (isBookmarked) {
bookmarks.put(newsResourceId, true)
} else {
bookmarks.remove(newsResourceId)
}
...
}
}
Repository 实现
在更上层,则是提供对外接口的 Repository,它对 LocalDataSource 和 RemoteDataSource(图上未标出)进行统一管理,处理两者之间数据融合的逻辑。
其代码如下所示:
// ===>
class BookmarksRepository(private val localDataSource: LocalDataSource) {
val bookmarksStream: Flow<List<String>> = localDataSource.bookmarksStream
suspend fun toggleBookmark(newsResourceId: String, isBookmarked: Boolean) =
localDataSource.toggleBookmark(newsResourceId, isBookmarked)
}
}
封装不同的数据源
UI 层实现
UI 层的主要职责有两个:
- 展示 UI 界面
- 处理交互事件
ForYouViewModel 监听并组装 Repository 的数据流
位于 UI 层的 ViewModel,持有多个 Repository 的对象,将其发送的数据流拼接成 UiState 不可变对象,从而更新界面显示。
以新闻流为例,界面中不仅显示新闻的标题、头图、概要,同时也会在右上角展示该用户是否收藏了这条新闻。这个过程就是组装两个 Repository 提供的数据流的过程。
代码如下所示:
Loading、Success 页面状态处理
使用密封类来控制页面加载状态枚举,同时对于 Success 状态,可以携带列表数据,从而进行显示。
对于标记“收藏”的操作,同样由 ViewModel 提供函数,供 UI 层的 Screen 调用。
实现 Screen
在 UI 层,Screen 是最终展示在界面上的对象,它接收 ViewModel 发射的不可变 UiState 对象,并将其映射为不同状态的界面元素,随后展示给用户。
Screen 的主流实现有 View 和 Jetpack Compose 两种,以下为 Compose 实现。
待实现的效果图
对应的 Compose 代码
接下来是页面 ForYouScreen
。
在 Activity.onCreate()
中创建页面布局。
模块化 Modularization
通过良好的模块化设计,可以增加APP的伸缩性,更加有利于并行开发。而良好的模块化设计,应当做到以下两点:
- 关注点分离:模块自身的功能聚焦且独立
- 依赖关系明确:避免出现同级依赖、循环依赖
通常来说,应用的模块化可以分为 app
、feature
、core
三层,它们的关系如下所示:
NowInAndroid 项目的模块拆分如下所示:
最上层的 build-logic
可以用来管理公共依赖的版本,在不同子 module 里会使用到相同依赖包,将其版本号提取到顶层,以便统一处理。
Before:
After:
NowInAndroid 模块拆分
可测试性设计
在进行了分层设计的基础上,通过替换 Repository 为测试数据源,可以 mock 数据进行测试。
单元测试代码如下所示。