一、概述
1.1 通用架构原则
1.1.1 关注点分离
在Activity或Fragment中编写所有代码是一个常见的错误。这些基于UI的类应该只包含处理UI和操作系统交互的逻辑。通过使这些类尽可能精简,我们可以避免与组件生命周期相关的许多问题,并提高这些类的可测试性。
1.1.2 数据模型驱动UI
1.1.3 单一可信任数据源
当一个新的数据类型在你的应用中被定义时,你应该给它分配一个单一的可信任数据源(SSOT)。SSOT是该数据的所有者,并且只有SSOT可以修改或改变它。为了实现这一点,SSOT使用不可变类型公开数据,为了修改数据,SSOT公开其他类型可以调用的函数或接收事件。
1.1.4 单向数据流
在UDF中,状态只在一个方向上流动。在相反方向上修改数据流的事件。
1.2 MVI
Model - UI State.
View - Activity / Fragment / Composable
Intent - 用户或应用程序本身执行操作的意图或愿望。
1.3 Clean
干净的架构代表一组实践,由这些实践产生的系统有以下特点:
-
独立于框架
-
可测试的
-
独立于UI
-
独立于数据源
-
独立于任何外部依赖
二、推荐应用架构
2.1 UI Layer
UI是将UI元素与UI状态绑定在屏幕上的结果。
- 状态向下流动而事件向上流动的模式称为单向数据流(UDF)。
- ViewModel保存并公开供UI使用的状态。UI状态是由ViewModel转换的应用程序数据。
- UI通知ViewModel用户事件。
- ViewModel处理用户操作并更新状态。 -更新后的状态反馈到UI进行渲染。
- 对于任何导致状态突变的事件都要重复上述操作。
class NewsViewModel(
private val repository: NewsRepository,
...
) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
private var fetchJob: Job? = null
fun fetchArticles(category: String) {
fetchJob?.cancel()
fetchJob = viewModelScope.launch {
try {
val newsItems = repository.newsItemsForCategory(category)
_uiState.update {
it.copy(newsItems = newsItems)
}
} catch (ioe: IOException) {
// Handle the error and notify the UI when appropriate.
_uiState.update {
val messages = getMessagesFromThrowable(ioe)
it.copy(userMessages = messages)
}
}
}
}
}
2.2 Domain Layer
Domain层是位于UI层和数据层之间的可选层
class MyUseCase(
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
suspend operator fun invoke(...) = withContext(defaultDispatcher) {
// Long-running blocking operations happen on a background thread.
}
}
2.3 Data Layer
Data层包含应用程序数据和业务逻辑。业务逻辑是为应用程序提供价值的东西——它由现实世界的业务规则组成,这些规则决定了应用程序数据必须如何创建、存储和更改。
重要的是,每个存储库定义一个单一的事实来源。事实的来源总是包含一致的、正确的和最新的数据。事实上,从存储库公开的数据应该始终是直接来自事实来源的数据。
事实的来源可以是数据源(例如数据库),甚至可以是存储库可能包含的内存缓存。存储库组合不同的数据源并解决数据源之间的任何潜在冲突,以便定期或由于用户输入事件而更新单个事实源。
应用程序中的不同存储库可能有不同的事实来源。例如,LoginRepository类可以使用它的缓存作为事实的来源,PaymentsRepository类可以使用网络数据源。
为了提供离线优先支持,建议使用本地数据源(例如数据库)作为事实来源。
class ExampleRepository(
private val exampleRemoteDataSource: ExampleRemoteDataSource, // network
private val exampleLocalDataSource: ExampleLocalDataSource // database
) {
val data: Flow<Example> = ...
suspend fun modifyData(example: Example) { ... }
}
三、结论
3.1 架构原则及目标
关注点分离
- UI / Domain / Data三层式架构
- 单向数据流
- 职责及边界清晰的模块划分
数据驱动UI
- 数据独立于UI,不跟随页面的生命周期,方便复用及测试
- 数据可持久化,进程结束时数据可保留;页面重建时UI状态及内存数据可恢复
可持续演进
- 可维护性,有长期技术支持
- 伸缩性,可进化升级
3.2 实现细节
UI Layer
- UI elements - Activity + Fragment + Views (optional: Jetpack Compose)
- State holders - ViewModel
- UDF - StateFlow (backup:
LiveData)
Domain Layer
- Threading - Prefer
suspend
function (Kotlin Coroutines)
Data Layer
- Repository - Using flow-friendly APIs or 3rd library (Room \ Retrofit \ etc.)
四、参考资料
4.1 架构相关
4.1.1 Architecture - Modern Android Development Skills
https://www.youtube.com/watch?v=TPWmfJq16rA&list=PLWz5rJ2EKKc8GZWCbUm3tBXKeqIi3rcVX
4.1.2 Google架构指南
https://developer.android.com/jetpack/guide
4.1.3 Architecting Android… Reloaded
https://github.com/android10/Android-CleanArchitecture-Kotlin
https://fernandocejas.com/2018/05/07/architecting-android-reloaded/#data-layer-repository-pattern-to-the-rescue
4.2 完整项目参考
4.2.1 Tivi
https://github.com/chrisbanes/tivi
4.2.2 sunflower
https://github.com/android/sunflower
4.2.3 Android Architecture Blueprints
https://github.com/android/architecture-samples
4.2.4 Now in Android App
https://github.com/android/nowinandroid
4.3 Coroutines相关
4.3.1 深入浅出协程、线程和并发问题
https://mp.weixin.qq.com/s?__biz=Mzk0NDIwMTExNw==&mid=2247491523&idx=1&sn=e1ad8c96a90254e9e68667b1ec2f8786
4.3.2 Kotlin Vocabulary | 揭秘协程中的 suspend 修饰符
https://mp.weixin.qq.com/s?__biz=MzAwODY4OTk2Mg==&mid=2652055127&idx=2&sn=283de8250bfc8a7bd8287a7aadad1339
4.3.3 从 LiveData 迁移到 Kotlin 数据流
https://mp.weixin.qq.com/s?__biz=Mzk0NDIwMTExNw==&mid=2247493227&idx=1&sn=559909a5cbea791f6a0320ed42655215
https://medium.com/androiddevelopers/migrating-from-livedata-to-kotlins-flow-379292f419fb
4.3.4 使用更为安全的方式收集 Android UI 数据流
https://mp.weixin.qq.com/s?__biz=Mzk0NDIwMTExNw==&mid=2247494116&idx=1&sn=6bd12ff9d62eb2a71fa74060afcac996
https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda
4.3.5 使用 Kotlin Flow 构建数据流 "管道"
https://mp.weixin.qq.com/s?__biz=Mzk0NDIwMTExNw==&mid=2247512636&idx=1&sn=6136e9e4595e957fb97d54c960f8a74f
4.3.6 设计 repeatOnLifecycle API 背后的故事
https://mp.weixin.qq.com/s?__biz=Mzk0NDIwMTExNw==&mid=2247497396&idx=1&sn=d4f447332856ab25a4ab85cf207c1a67
https://medium.com/androiddevelopers/repeatonlifecycle-api-design-story-8670d1a7d333