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

news2025/1/12 19:48:31

1.官方应用架构指南

1.1架构的原则

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

  1. 分离关注点
  2. 通过数据模型驱动界面
  3. 单一数据源
  4. 单向数据流

1.2谷歌推荐的应用架构

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

  1. 界面层 - 在屏幕上显示应用数据。
  2. 数据层 - 包含应用的业务逻辑并公开应用数据。

        可以额外添加一个名为“网域层”的架构层,以简化和重复使用界面层与数据层之间的交互。

        总结下来,我们的应用架构应该有三层:界面层、网域层、数据层。

        其中网域层可选,即无论你的应用中有没有网域层,与你的应用架构是 MVVM 还是 MVI 无关。

1.2.1界面层架构设计指导

1.2.1.1界面层在架构中的作用

        界面的作用是在屏幕上显示应用数据,并充当主要的用户互动点。

        从数据层获取是业务数据,有时候需要界面层将业务数据转换成 UI 数据供界面元素显示。

1.2.1.2 界面层的组成

        界面层由以下两部分组成:

        界面元素:在屏幕上呈现数据的界面元素可以使用 View 或 Jetpack Compose 函数实现。

        状态容器:用于存储数据、向界面提供数据以及处理逻辑的状态容器(如 ViewModel 类)。

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

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

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

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

1定义界面状态

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

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

  • 展示加载中 view 的界面状态
  • 展示加载错误 view 的界面状态
  • 列表数据 view 界面状态
  • Toast view 界面状态
  • 刷新完成 view 界面状态

        无论采用 MVVM 还是 MVI,都需要这些界面状态,只是他们的实现细节不同。

2)定义状态容器

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

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

无论采用 MVVM 还是 MVI,都需要定义状态容器,来存放界面状态。

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

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

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

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

        无论采用 MVVM 还是 MVI,都需要使用单向数据流管理状态。

4唯一数据源

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

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

/**
 * 加载失败 UI 状态,显示失败图
 * 首屏获取的数据为空、首屏请求数据失败时展示失败图
 * 初始值:隐藏
 */
val loadingError: StateFlow<Boolean>
    get() = _loadingError
private val _loadingError = MutableStateFlow<Boolean>(false)

/**
 * 正在加载 UI 状态,显示加载中图
 * 首屏时请求网络时展示加载中图
 * 初始值:展示
 */
val isLoading: StateFlow<Boolean>
    get() = _isLoading
private val _isLoading = MutableStateFlow<Boolean>(true)

/**
 * 加载成功后回来的列表 UI 状态,将 list 数据展示到列表上
 */
val newsList: StateFlow<MutableList<News>>
    get() = _newsList
private val _newsList = MutableStateFlow<MutableList<News>>(mutableListOf())

/**
 * 加载完成 UI 状态
 */
val loadingFinish: StateFlow<Boolean>
    get() = _loadingFinish
private val _loadingFinish = MutableStateFlow<Boolean>(false)

/**
 * 界面 toast UI 状态
 */
val toastMessage: StateFlow<String>
    get() = _toastMessage
private val _toastMessage = MutableStateFlow<String>("")

        采用唯一数据源声明界面状态时,代码如下:

sealed interface NewsUiState  {

    object IsLoading: NewsUiState

    object LoadingError: NewsUiState

    object LoadingFinish: NewsUiState

    data class Success(val newsList: MutableList<News>): NewsUiState

    data class ToastMessage(val message: String = ""): NewsUiState

}


val newsUiState: StateFlow<NewsUiState>
    get() = _newsUiState

private val _newsUiState: MutableStateFlow<NewsUiState> =
    MutableStateFlow(NewsUiState.IsLoading)

         界面发生的事件的唯一数据源指的是将界面发生的事件封装在一个类中,然后统一处理。比如上面描述的列表业务,它的界面事件有 初始化列表事件(首屏请求网络数据)、刷新事件、加载更多事件。

        不采用唯一数据源,界面事件的调用实现逻辑为:在 activity 中直接调用 viewModel 提供的 initData、freshData 和 loadMoreData 方法;

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

sealed interface NewsActivityIntent {
    data class InitDataIntent(val type: String = "init") : NewsActivityIntent

    data class RefreshDataIntent(val type: String = "refresh") : NewsActivityIntent

    data class LoadMoreDataIntent(val type: String = "loadMore") : NewsActivityIntent
}

fun dispatchIntent(intent: NewsActivityIntent) {
    when (intent) {
        is NewsActivityIntent.InitDataIntent -> {
            //初始化逻辑
            initNewsData()
        }
        is NewsActivityIntent.RefreshDataIntent -> {
            //刷新逻辑
            refreshNewsData()
        }
        is NewsActivityIntent.LoadMoreDataIntent -> {
            //加载更多逻辑
            loadMoreNewsData()
        }
    }
}

        因为有了唯一数据源这一特点,才将最新的应用架构称为 MVI,MVVM 不具备这一特点。

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

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

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

  • 解耦界面元素(activity 或 fragment) 与 状态容器,如:activity 持有 viewModel 的引用,viewModel 不需要持有 activity 的引用。

        无论采用 MVVM 还是 MVI,都需要向界面公开界面状态,公开的方式也可以是一样的。

6使用界面状态

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

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

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

        无论采用 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 编程语言,数据层应公开用于发出新数据的回调。

class ExampleRepository(
    private val exampleRemoteDataSource: ExampleRemoteDataSource, // network
    private val exampleLocalDataSource: ExampleLocalDataSource // database
) {

    val data: Flow<Example> = ...

    suspend fun modifyData(example: Example) { ... }
}
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 可以供不同界面层使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2060790.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

近视防控明星:蔡司小乐圆中期临床数据详解

近视防控明星&#xff1a;蔡司小乐圆中期临床数据详解 小乐圆镜片作为近视防控镜片里的明星产品&#xff0c;从22年5月上市以来防控效果就一直备受大家的关注。而最近中期临床试验的结果&#xff0c;给家长孩子吃了一颗定心丸。 本次实验中&#xff0c;240位受试者被随机分成三…

基于springboot框架的电影订票系统_wqc3k

TOC springboot611基于springboot框架的电影订票系统_wqc3k--论文 绪 论 1.1研究背景和意义 随着科学技术的不断发展&#xff0c;计算机现在已经成为了社会的必需品&#xff0c;人们通过网络可以获得海量的信息&#xff0c;这些信息可以和各行各业进行关联&#xff0c;电影…

你应该停止使用的 7 个已弃用的 Python 库

欢迎来到雲闪世界。升级您的 Python 工具包&#xff1a;发现 7 个应停止使用的过时库以及替代它们的功能。最近&#xff0c;我回顾了 Python 的新特性&#xff0c;发现每个版本都引入了创新&#xff0c;使我们的日常开发工作变得更加轻松。 这让我意识到科技是一门永无止境的艺…

8.21 QT

1.思维导图 2. 服务器端 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer>//服务器类 #include <QMessageBox> #include <QDebug> #include <QList> #include <QTcpSocket>QT_BEGIN_NAMESPACE names…

免费高画质提取PPT/Word/Excel中的图片工具

下载地址&#xff1a;https://pan.quark.cn/s/134ccc35b8a2 软件简介&#xff1a; 好不容易搞到一个几十上百MB的ppt&#xff0c;想导出里面的图片进行二次加工&#xff0c;却被ppt超低画质的图片另存为功能劝退&#xff0c;明知里面全是高清图片&#xff0c;走时却是两手空空…

【C++从练气到飞升】14---深入浅出继承

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书&#x1f389; 目录 ⛳️推荐 一、继承的概念及定义 1.1 继承的概念 1.2 继承定义 1.2.1 定义格式 1.2.2 继承方式和访问限定符…

重新认识AbstractQueuedSynchronizer

开篇之前&#xff0c;烦请诸位允许我附庸风雅一次。近期因诸事繁杂&#xff0c;心情颇低落&#xff0c;遂于喜马拉雅APP中收听《老子》一文。其中的第八十一章《结天道》一文于我感悟颇深&#xff1a;和大怨&#xff0c;必有余怨&#xff0c;报怨以德&#xff0c;焉可以为善&am…

C++,std::bind 详解

文章目录 1. 概述2. 基本用法2.1 使用占位符2.2 示例 3. 总结 1. 概述 std::bind 是 C11 引入的一个功能&#xff0c;它允许你将函数&#xff08;或成员函数、函数对象&#xff09;与其参数绑定&#xff0c;生成一个新的可调用对象。这个功能在需要将函数及其参数一起传递给其…

DNF攻略:护石符文体系辅助详解,VMOS云手机助攻核心玩法!

在DNF游戏中&#xff0c;护石符文系统是提升角色实力的重要部分。当前版本中&#xff0c;护石符文体系经过了优化&#xff0c;使得获取方式更加便捷。以下是护石符文体系的详细介绍&#xff0c;以及如何使用VMOS云手机来更高效地管理和利用这一系统。 一、护石符文体系简介 护…

HarmonyOS 地图服务:深度解析其丰富功能与精准导航实力

目录 前期准备打造个性化地图&#xff1a;聚焦创建地图功能导入Map Kit相关模块通过MapOptions初始化地图切换地图类型设置地图中心点及层级展示定位按钮展示比例尺指定地图的日间夜间模式 通过MapComponentController对象方法控制地图切换地图类型开启3D建筑图层在指定的持续时…

【安当产品应用案例100集】008-UKEY在工业自动化数据传输中应用

工业自动化中的数据传输是确保生产过程高效、稳定运行的关键环节。工业自动化系统中&#xff0c;一般会有一个远程的客户端&#xff0c;负责将各个传感器、控制器等设备产生的信息传递到服务端&#xff0c;以实现生产过程的自动化控制和监控。它对于提高生产效率、降低生产成本…

SQL Server 2017上服务端设置强制加密启用SSL

在数据库服务端设置&#xff0c;强制所有客户端使用 SSL&#xff0c;设置完后&#xff0c;后续客户端所有连接&#xff0c;都将以密文传送&#xff0c;不论客户端是否指定安全连接&#xff08;即EncryptTrue/False&#xff09; 一、服务端强制加密使用 SSL 1.在数据库服务器上…

C++ 模板进阶知识

目录 一. 非类型模板参数 与类型模板参数确认的区别 何时确认 确认方式 二. 模板的特化 1. 概念 2. 函数模板特化 3. 类模板特化 3.1 全特化 3.2 偏特化 (1). 部分特化 (2). 进一步限制 4. 实际应用 三. 模板分离编译 1. 概念 2. 模板的分离编译 3. 解决方法 四…

【学习笔记】STM32F407探索者HAL库开发(一)STM32F4资源概要

【学习笔记】STM32F407探索者HAL库开发&#xff08;一&#xff09;STM32F4资源概要 1 硬件资源2 STM32命名规则3 STM32数据手册3.1 数据手册各章节内容概要3.2 引脚分布3.3 引脚定义3.4 引脚定义表的具体说明 1 硬件资源 STM32F407ZGT6具体的 内部资源如表 资源数量资源数量内…

鸿蒙Harmony实战开发:Touchscreen驱动器件硬件接口使用实例

功能简介 Touchscreen驱动用于驱动触摸屏使其正常工作&#xff0c;该驱动主要完成如下工作&#xff1a;对触摸屏驱动IC进行上电、配置硬件管脚并初始化其状态、注册中断、配置通信接口&#xff08;I2C或SPI&#xff09;、设定Input相关配置、下载及更新固件等操作。 在HDF&am…

考试:数据库系统(01)

数据库系统 ◆数据&#xff1a;是数据库中存储的基本对象&#xff0c;是描述事物的符号记录。 数据的种类&#xff1a;文本、图形、图像、音频、视频、学生的档案记录、货物的运输 情况等。 ◆数据库DB: 是长期存储在计算机内、有组织的、可共享的大量数据的集合。 ◆数据库…

安恒信息总裁宋端智,辞职了!活捉一枚新鲜出炉的餐饮人!

吉祥知识星球http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247485367&idx1&sn837891059c360ad60db7e9ac980a3321&chksmc0e47eebf793f7fdb8fcd7eed8ce29160cf79ba303b59858ba3a6660c6dac536774afb2a6330#rd 《网安面试指南》http://mp.weixin.qq.com/s?…

雅菲奥朗 FinOps 认证培训:开启企业云财务管理转型之路

前言&#xff1a; 在当今快速变化的商业环境中&#xff0c;企业面临着前所未有的IT财务挑战。随着云计算和数字化转型的推进&#xff0c;传统的财务管理方式已经不能满足“企业上云”的需求。FinOps&#xff0c;即“云财务管理”应运而生&#xff0c;成为帮助企业实现IT财务流…

Compose TextField详解

首先明确Compose TextField的底层依赖是&#xff1a; TextField BasicTextField CoreTextField 相较于Text&#xff0c;TextField需要提供可输入的能力以及渲染效果动态更新的能力。 // CompositionLocals// If the text field is disabled or read-only, we should not d…

智能菜谱推荐系统_ct3p7

TOC springboot575智能菜谱推荐系统_ct3p7--论文 第一章 概述 1.1 研究背景 近些年&#xff0c;随着中国经济发展&#xff0c;人民的生活质量逐渐提高&#xff0c;对网络的依赖性越来越高&#xff0c;通过网络处理的事务越来越多。随着智能菜谱推荐管理的常态化&#xff0c…