Android 架构建议

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

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

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

分层架构

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

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

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

  • 数据库、DataStore、SharedPreferences、Firebase API。
  • GPS 定位提供程序。
  • 蓝牙数据提供程序。

  • 网络连接状态提供程序。
使用协程和流 使用协程和流在层之间进行通信。

更多协程最佳实践请查看此处.

使用领域层 使用领域层和用例,如果您需要重用与数据层交互的业务逻辑,这些逻辑跨越多个 ViewModel,或者您希望简化特定 ViewModel 的业务逻辑复杂性。

UI 层

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

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

查看更多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:

  • 屏幕级可组合项,
  • 视图中的 Activity/Fragment,
  • 使用Jetpack Navigation时的目标或图表。
在可重用的 UI 组件中使用普通状态持有者类 在可重用的 UI 组件中使用普通状态持有者类来处理复杂性。通过这样做,可以将状态提升并由外部控制。
不要使用AndroidViewModel 使用ViewModel类,而不是AndroidViewModelApplication类不应在 ViewModel 中使用。相反,将依赖项移动到 UI 或数据层。
公开 UI 状态。 ViewModel 应该通过名为uiState的单个属性向 UI 公开数据。如果 UI 显示多个不相关的的数据片段,则 VM 可以公开多个 UI 状态属性
  • 您应该将uiState设置为StateFlow
  • 如果数据作为数据流来自层次结构的其他层,则应使用stateIn运算符和WhileSubscribed(5000)策略创建uiState (示例)
  • 对于没有来自数据层的任何数据流的更简单的案例,可以接受将公开为不可变StateFlowMutableStateFlow (示例)
  • 您可以选择将${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 生命周期的一些最佳实践

建议 描述
不要覆盖 Activity 或 Fragment 中的生命周期方法。 不要覆盖 Activity 或 Fragment 中的生命周期方法,例如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,包括流。
  • 单元测试数据层实体。即,存储库和数据源。
  • UI 导航测试,在 CI 中作为回归测试很有用。
优先使用伪造对象而不是模拟对象。 阅读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