APP架构设计_1.官方应用架构指南

打印 上一主题 下一主题

主题 1028|帖子 1028|积分 3084

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1.官方应用架构指南

1.1架构的原则

        应用架构定义了应用的各个部门之间的界限以及每个部门应承担的职责。谷歌建议按照以下原则设计应用架构。

  • 分离关注点
  • 通过数据模子驱动界面
  • 单一数据源
  • 单向数据流
1.2谷歌推荐的应用架构

        每个应用应至少有两个层:

  • 界面层 - 在屏幕上表现应用数据。
  • 数据层 - 包含应用的业务逻辑并公开应用数据。
        可以额外添加一个名为“网域层”的架构层,以简化和重复使用界面层与数据层之间的交互。
        总结下来,我们的应用架构应该有三层:界面层、网域层、数据层。
        此中网域层可选,即无论你的应用中有没有网域层,与你的应用架构是 MVVM 照旧 MVI 无关。


1.2.1界面层架构设计指导

1.2.1.1界面层在架构中的作用

        界面的作用是在屏幕上表现应用数据,并充当主要的用户互动点。
        从数据层获取是业务数据,有时间需要界面层将业务数据转换成 UI 数据供界面元素表现。
1.2.1.2 界面层的组成

        界面层由以下两部门组成:
        界面元素:在屏幕上呈现数据的界面元素可以使用 View 或 Jetpack Compose 函数实现。
        状态容器:用于存储数据、向界面提供数据以及处置惩罚逻辑的状态容器(如 ViewModel 类)。

1.2.1.3界面层的架构设计遵循的原则

这里以一个常见的列表页面为案例进行讲解,这个列表页面有以下交互:


  • 打开页面时,网络数据返来之前展示一个加载中 view。
  • 首次打开页面,如果没有数据大概网络请求发生错误,展示一个错误 view。
  • 具备下拉革新能力,革新后,如果有数据,则替换列表数据;如果无返回数据,则弹出一个 Toast。
1.2.1.4具体原则应用业务分析

        接着我们用这个业务,按照以下原则进行分析:

1定义界面状态

        界面元素 加上 界面状态 才是用户看到的界面。


        上面说的列表页面,根据它的业务需求,需要有以下界面状态



  • 展示加载中 view 的界面状态
  • 展示加载错误 view 的界面状态
  • 列表数据 view 界面状态
  • Toast view 界面状态
  • 革新完成 view 界面状态
        无论接纳 MVVM 照旧 MVI,都需要这些界面状态,只是他们的实现细节不同。

2)定义状态容器

        状态容器:就是存放我们定义的界面状态,而且包含实行相应任务所必须的逻辑的类。

ViewModel 范例是推荐的状态容器,用于管理屏幕级界面状态,具有数据层访问权限。但并不是只能用 ViewModel作为状态容器。

无论接纳 MVVM 照旧 MVI,都需要定义状态容器,来存放界面状态。

3)使用单向数据流管理状态

        看看官方在界面层的架构指导图:


        面状态数据运动是单向的,只能从 状态容器 到 界面元素。

        界面发生的变乱 events(如革新、加载更多等变乱)运动是单向的,只能从 界面元素 到 状态容器。

        无论接纳 MVVM 照旧 MVI,都需要使用单向数据流管理状态。

4唯一数据源

        唯一数据源针对的是:定义的界面状态 和 界面发生的变乱。

        界面状态唯一数据源指的是将定义的多个界面状态,封装在一个类中,如上面的列表业务,不接纳唯一数据源,界面状态的声明为:

  1. /**
  2. * 加载失败 UI 状态,显示失败图
  3. * 首屏获取的数据为空、首屏请求数据失败时展示失败图
  4. * 初始值:隐藏
  5. */
  6. val loadingError: StateFlow<Boolean>
  7.     get() = _loadingError
  8. private val _loadingError = MutableStateFlow<Boolean>(false)
  9. /**
  10. * 正在加载 UI 状态,显示加载中图
  11. * 首屏时请求网络时展示加载中图
  12. * 初始值:展示
  13. */
  14. val isLoading: StateFlow<Boolean>
  15.     get() = _isLoading
  16. private val _isLoading = MutableStateFlow<Boolean>(true)
  17. /**
  18. * 加载成功后回来的列表 UI 状态,将 list 数据展示到列表上
  19. */
  20. val newsList: StateFlow<MutableList<News>>
  21.     get() = _newsList
  22. private val _newsList = MutableStateFlow<MutableList<News>>(mutableListOf())
  23. /**
  24. * 加载完成 UI 状态
  25. */
  26. val loadingFinish: StateFlow<Boolean>
  27.     get() = _loadingFinish
  28. private val _loadingFinish = MutableStateFlow<Boolean>(false)
  29. /**
  30. * 界面 toast UI 状态
  31. */
  32. val toastMessage: StateFlow<String>
  33.     get() = _toastMessage
  34. private val _toastMessage = MutableStateFlow<String>("")
复制代码
        接纳唯一数据源声明界面状态时,代码如下:

  1. sealed interface NewsUiState  {
  2.     object IsLoading: NewsUiState
  3.     object LoadingError: NewsUiState
  4.     object LoadingFinish: NewsUiState
  5.     data class Success(val newsList: MutableList<News>): NewsUiState
  6.     data class ToastMessage(val message: String = ""): NewsUiState
  7. }
  8. val newsUiState: StateFlow<NewsUiState>
  9.     get() = _newsUiState
  10. private val _newsUiState: MutableStateFlow<NewsUiState> =
  11.     MutableStateFlow(NewsUiState.IsLoading)
复制代码
         界面发生的变乱的唯一数据源指的是将界面发生的变乱封装在一个类中,然后统一处置惩罚。好比上面描述的列表业务,它的界面变乱有 初始化列表变乱(首屏请求网络数据)、革新变乱、加载更多变乱。
        不接纳唯一数据源,界面变乱的调用实现逻辑为:在 activity 中直接调用 viewModel 提供的 initData、freshData 和 loadMoreData 方法;

        接纳唯一数据源,界面变乱的调用实现逻辑为,先将变乱中封装在一个 Intent 中,viewModel 中提供一个统一的变乱入口处置惩罚方法 dispatchIntent,在 activity 中 各个场景下都调用 viewModel#dispatchIntent,代码如下:

  1. sealed interface NewsActivityIntent {
  2.     data class InitDataIntent(val type: String = "init") : NewsActivityIntent
  3.     data class RefreshDataIntent(val type: String = "refresh") : NewsActivityIntent
  4.     data class LoadMoreDataIntent(val type: String = "loadMore") : NewsActivityIntent
  5. }
  6. fun dispatchIntent(intent: NewsActivityIntent) {
  7.     when (intent) {
  8.         is NewsActivityIntent.InitDataIntent -> {
  9.             //初始化逻辑
  10.             initNewsData()
  11.         }
  12.         is NewsActivityIntent.RefreshDataIntent -> {
  13.             //刷新逻辑
  14.             refreshNewsData()
  15.         }
  16.         is NewsActivityIntent.LoadMoreDataIntent -> {
  17.             //加载更多逻辑
  18.             loadMoreNewsData()
  19.         }
  20.     }
  21. }
复制代码
        因为有了唯一数据源这一特点,才将最新的应用架构称为 MVI,MVVM 不具备这一特点。

5向界面公开界面状态的方式

        在状态容器中定义界面状态后,下一步思考的是怎样将提供的状态发送给界面。

        谷歌推荐使用 LiveData 或 StateFlow 等可观察数据容器中公开界面状态。如许做的优点有:



  • 解耦界面元素(activity 或 fragment) 与 状态容器,如:activity 持有 viewModel 的引用,viewModel 不需要持有 activity 的引用。
        无论接纳 MVVM 照旧 MVI,都需要向界面公开界面状态,公开的方式也可以是一样的。

6使用界面状态

        在界面中使用界面状态时,对于 LiveData,可以使用 observe() 方法;对于 Kotlin 数据流,您可以使用 collect() 方法或其变体。

        留意:在界面中使用可观察数据容器时,需要思量界面的生命周期。因为当未向用户表现视图时,界面不应观察界面状态。使用 LiveData 时,LifecycleOwner 会隐式处置惩罚生命周期问题。使用数据流时,最好通过适当的协程作用域和 repeatOnLifecycle API,如:

  1. class NewsActivity : AppCompatActivity() {
  2.     private val viewModel: NewsViewModel by viewModels()
  3.     override fun onCreate(savedInstanceState: Bundle?) {
  4.         ...
  5.         lifecycleScope.launch {
  6.             repeatOnLifecycle(Lifecycle.State.STARTED) {
  7.                 viewModel.uiState.collect {
  8.                     // Update UI elements
  9.                 }
  10.             }
  11.         }
  12.     }
  13. }
复制代码
        无论接纳 MVVM 照旧 MVI,都需要使用界面状态,使用的方式都是一样的。

1.2.2数据层架构设计指导

1.2.2.1数据层在架构中的作用

        数据层包含应用数据和业务逻辑。业务逻辑决定应用的价值,它由现实天下的业务规则组成,这些规则决定着应用数据的创建、存储和更改方式。
1.2.2.2数据层的架构设计

        数据层由多个仓库组成,此中每个仓库都可以包含零到多个数据源。您应该为应用中处置惩罚的每种不同范例的数据分别创建一个存储库类。例如,您可以为与电影相关的数据创建一个 MoviesRepository 类,大概为与付款相关的数据创建一个 PaymentsRepository 类。
        每个数据源类应仅负责处置惩罚一个数据源,数据源可以是文件、网络泉源或本地数据库。
        层次结构中的其他层不能直接访问数据源;数据层的入口点始终是存储库类。
1.2.2.3公开 API

        数据层中的类通常会公开函数,以实行一次性的创建、读取、更新和删除 (CRUD) 调用,或接收关于数据随时间变革的关照。对于每种情况,数据层都应公开以下内容:
        一次性操纵:在 Kotlin 中,数据层应公开挂起函数;对于 Java 编程语言,数据层应公开用于提供回调来关照操纵结果的函数。
        接收关于数据随时间变革的关照:在 Kotlin 中,数据层应公开数据流,对于 Java 编程语言,数据层应公开用于发出新数据的回调。
  1. class ExampleRepository(
  2.     private val exampleRemoteDataSource: ExampleRemoteDataSource, // network
  3.     private val exampleLocalDataSource: ExampleLocalDataSource // database
  4. ) {
  5.     val data: Flow<Example> = ...
  6.     suspend fun modifyData(example: Example) { ... }
  7. }
复制代码
1.2.2.4多层存储库

        在某些涉及更复杂业务要求的情况下,存储库可能需要依赖于其他存储库。这可能是因为所涉及的数据是来自多个数据源的数据聚合,大概是因为相应职责需要封装在其他存储库类中。

        例如,负责处置惩罚用户身份验证数据的存储库 UserRepository 可以依赖于其他存储库(例如 LoginRepository 和 RegistrationRepository,以满足其要求。


        留意:传统上,一些开辟者将依赖于其他存储库类的存储库类称为 manager,例如称为 UserManager 而非 UserRepository。

1.2.2.5数据层生命周期

        如果该类的职责作用于应用至关重要,可以将该类的实例的作用域限定为 Application 类。

如果只需要在应用内的特定流程(例如注册流程或登录流程)中重复使用同一实例,则应将该实例的作用域限定为负责相应流程的生命周期的类。例如,可以将包含内存中数据的 RegistrationRepository 的作用域限为 RegistrationActivity。

1.2.2.6数据层定位思考

        数据层不应该是页面级别的(一个页面对应一个数据层),而应该是应用级别的(数据层有多个存储仓库,每种数据范例有一个对应的存储仓库,不同的界面层可以复用存储仓库)。

        好比我做的应用是运动健康app,用户的就寝相关的数据有一个 SleepResposity,用户体重相关的数据有一个 WeightReposity,由于应用中许多界面都可能需要展示用户的就寝数据和体重数据,所以 SleepResposity 和 WeightReposity 可以供不同界面层使用。



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

干翻全岛蛙蛙

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表