Android 架构建议

此页面介绍了一些架构最佳实践和建议。采用它们可以提高应用的质量、健壮性和可扩展性。它们还有助于简化应用的维护和测试。

以下最佳实践按主题分组。每个都具有优先级,反映了团队推荐的力度。优先级列表如下

  • 强烈推荐:除非与您的方法存在根本冲突,否则您应该实施此实践。
  • 推荐:此实践可能会改进您的应用。
  • 可选:在某些情况下,此实践可以改进您的应用。

分层架构

我们推荐的分层架构有利于关注点分离。它通过数据模型驱动 UI,符合单一数据源原则,并遵循单向数据流原则。以下是分层架构的一些最佳实践

建议 描述
使用明确定义的数据层
强烈推荐
数据层将应用程序数据公开给应用的其余部分,并包含应用的大部分业务逻辑。
  • 即使存储库只包含单个数据源,您也应该创建存储库
  • 在小型应用中,您可以选择将数据层类型放在data包或模块中。
使用明确定义的UI 层
强烈推荐
UI 层在屏幕上显示应用程序数据,并用作用户交互的主要点。

  • 在小型应用中,您可以选择将数据层类型放在 ui 包或模块中。
更多 UI 层最佳实践请点击此处.
数据层data layer 应该使用资源库公开应用程序数据。
强烈推荐

UI 层中的组件,例如可组合函数、活动或 ViewModel,不应直接与数据源交互。数据源示例包括:

  • 数据库、DataStore、SharedPreferences、Firebase API。
  • GPS 定位提供程序。
  • 蓝牙数据提供程序。
  • 网络连接状态提供程序。
使用协程和流
强烈推荐
使用协程和流在层之间进行通信。

更多协程最佳实践请点击此处.

使用领域层
建议在大应用中使用
如果您需要跨多个 ViewModel 重用与数据层交互的业务逻辑,或者您希望简化特定 ViewModel 的业务逻辑复杂度,请使用领域层和用例。

UI 层

UI 层UI layer 的作用是在屏幕上显示应用程序数据,并充当用户交互的主要入口点。以下是 UI 层的一些最佳实践:

建议 描述
遵循单向数据流 (UDF)
强烈推荐
遵循单向数据流 (UDF)原则,其中 ViewModel 使用观察者模式公开 UI 状态,并通过方法调用从 UI 接收操作。
如果其优势适用于您的应用,请使用AAC ViewModel
强烈推荐
使用AAC ViewModel处理业务逻辑,并获取应用程序数据以向 UI(Compose 或 Android 视图)公开 UI 状态。

查看更多ViewModel 最佳实践请点击此处

查看ViewModel 的优势请点击此处

使用生命周期感知的 UI 状态收集。
强烈推荐
使用适当的生命周期感知协程构建器从 UI 中收集 UI 状态:在视图系统中使用repeatOnLifecycle,在 Jetpack Compose 中使用collectAsStateWithLifecycle

阅读更多关于 repeatOnLifecycle的内容。

阅读更多关于 collectAsStateWithLifecycle的内容。

不要从 ViewModel 发送事件到 UI。
强烈推荐
在 ViewModel 中立即处理事件,并使用处理事件的结果导致状态更新。更多关于UI 事件请点击此处
使用单活动应用程序。
推荐
如果您的应用有多个屏幕,请使用导航片段导航 Compose在屏幕之间导航,并深层链接到您的应用。
使用Jetpack Compose
推荐
使用Jetpack Compose为手机、平板电脑和折叠屏设备以及 Wear OS 构建新的应用。

以下代码片段概述了如何在生命周期感知的方式下收集 UI 状态

视图

class MyFragment : Fragment() {

    private val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Process item
                }
            }
        }
    }
}

Compose

@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}

ViewModel

ViewModel 负责提供 UI 状态和对数据层的访问。以下是 ViewModel 的一些最佳实践:

建议 描述
ViewModel 应该与 Android 生命周期无关。
强烈推荐
ViewModel 不应该持有任何与生命周期相关的类型的引用。不要将Activity、Fragment、ContextResources作为依赖项传递。如果 ViewModel 中的内容需要Context,则应认真评估它是否在正确的层级中。
使用协程和流
强烈推荐

ViewModel 使用以下方式与数据层或领域层交互:

  • Kotlin 流用于接收应用程序数据,
  • suspend函数用于使用viewModelScope执行操作。
在屏幕级别使用 ViewModel。
强烈推荐

不要在可重用的 UI 部分中使用 ViewModel。您应该在以下情况下使用 ViewModel:

  • 屏幕级可组合函数,
  • 视图中的活动/片段,
  • 使用Jetpack Navigation时的目标或图。
在可重用的 UI 组件中使用普通状态持有者类
强烈推荐
在可重用的 UI 组件中使用普通状态持有者类来处理复杂性。通过这样做,可以将状态提升并由外部控制。
不要使用AndroidViewModel
推荐
使用ViewModel类,而不是AndroidViewModelApplication类不应在 ViewModel 中使用。相反,将依赖项移动到 UI 或数据层。
公开 UI 状态。
推荐
ViewModel 应该通过一个名为uiState的单个属性向 UI 公开数据。如果 UI 显示多个不相关的部分数据,则 VM 可以公开多个 UI 状态属性
  • 您应该将uiState设为StateFlow
  • 如果数据来自层次结构的其他层的数据流,则应使用stateIn运算符和WhileSubscribed(5000)策略创建uiState (示例)
  • 对于没有来自数据层的数据流的更简单的案例,可以使用一个MutableStateFlow作为不可变的StateFlow公开 (示例)
  • 您可以选择将${Screen}UiState作为数据类,其中可以包含数据、错误和加载信号。如果不同的状态是互斥的,此类也可以是密封类。

以下代码片段概述了如何从 ViewModel 公开 UI 状态

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

生命周期

以下是一些与Android 生命周期协作的最佳实践:

建议 描述
不要覆盖活动或片段中的生命周期方法。
强烈推荐
不要覆盖活动或片段中的生命周期方法,例如onResume。请改用LifecycleObserver。如果应用需要在生命周期达到某个Lifecycle.State时执行工作,请使用repeatOnLifecycle API。

以下代码片段概述了如何在给定生命周期状态下执行操作

视图

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                // ...
            }
            override fun onPause(owner: LifecycleOwner) {
                // ...
            }
        }
    }
}

Compose

@Composable
fun MyApp() {

    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner, ...) {
        val lifecycleObserver = object : DefaultLifecycleObserver {
            override fun onStop(owner: LifecycleOwner) {
                // ...
            }
        }

        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
}

处理依赖项

在管理组件之间的依赖项时,您应该遵守以下一些最佳实践:

建议 描述
使用依赖项注入
强烈推荐
使用依赖项注入最佳实践,尽可能使用构造函数注入
在必要时限定作用域到组件。
强烈推荐
当类型包含需要共享的可变数据,或者类型初始化成本高且在应用中被广泛使用时,限定作用域到依赖项容器
使用Hilt
推荐
在简单的应用中使用Hilt手动依赖项注入。如果您的项目足够复杂,请使用Hilt。例如,如果您有:
  • 具有 ViewModel 的多个屏幕——集成
  • WorkManager 使用——集成
  • 导航的高级用法,例如限定作用域到导航图的 ViewModel——集成。

测试

以下是一些测试的最佳实践:

建议 描述
了解要测试的内容.
强烈推荐

除非项目非常简单,例如 Hello World 应用,否则您应该对其进行测试,至少应包括:

  • 单元测试 ViewModel,包括流。
  • 单元测试数据层实体。即资源库和数据源。
  • 作为 CI 中回归测试的 UI 导航测试。
优先使用模拟数据而不是模拟对象。
强烈推荐
阅读Android 文档中使用测试替身的更多内容。
测试 StateFlow。
强烈推荐
在测试StateFlow

有关更多信息,请查看Android DAC 指南中要测试的内容

模型

在开发应用中的模型时,您应该遵守以下最佳实践:

建议 描述
在复杂的应用中为每个层创建模型。
推荐

在复杂的应用中,当有意义时,在不同的层或组件中创建新的模型。请考虑以下示例:

  • 远程数据源可以将通过网络接收到的模型映射到一个更简单的类,其中仅包含应用所需的数据
  • 资源库可以将 DAO 模型映射到更简单的类,其中仅包含 UI 层所需的信息。
  • ViewModel 可以将数据层模型包含在UiState类中。

命名约定

在命名代码库时,您应该了解以下最佳实践:

建议 描述
命名方法。
可选
方法应该是动词短语。例如,makePayment()
命名属性。
可选
属性应该是名词短语。例如,inProgressTopicSelection
命名数据流。
可选
当类公开 Flow 流、LiveData 或任何其他流时,命名约定为get{model}Stream()。例如,getAuthorStream(): Flow<Author> 如果函数返回模型列表,则模型名称应为复数:getAuthorsStream(): Flow<List<Author>>
命名接口实现。
可选
接口实现的名称应具有意义。如果找不到更好的名称,则使用Default作为前缀。例如,对于NewsRepository接口,您可以使用OfflineFirstNewsRepositoryInMemoryNewsRepository。如果找不到合适的名称,则使用DefaultNewsRepository。模拟实现应以Fake作为前缀,例如FakeAuthorsRepository