Android 架构建议

本页面介绍了多项 架构 最佳实践和建议。采纳这些实践有助于提高应用的质量、稳健性和可伸缩性,并使其更易于维护和测试。

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

  • 强烈建议: 您应实现此做法,除非它与您的方法从根本上发生冲突。
  • 建议: 此做法可能会改进您的应用。
  • 可选: 在某些情况下,此做法可以改进您的应用。

分层架构

我们建议的分层架构推崇关注点分离。它通过数据模型驱动界面,遵循单一可信来源原则,并遵守单向数据流原则。以下是分层架构的一些最佳实践:

建议 描述
使用明确定义数据层 数据层将应用数据暴露给应用的其余部分,并包含应用的绝大部分业务逻辑。
  • 您应该创建数据仓库,即使它们只包含单个数据源。
  • 在小型应用中,您可以选择将数据层类型放置在 data 软件包或模块中。
使用明确定义的界面层 界面层在屏幕上显示应用数据,并作为用户交互的主要入口点。
  • 在小型应用中,您可以选择将数据层类型放置在 ui 软件包或模块中。
更多界面层最佳实践请见此处.
数据层应使用数据仓库公开应用数据。

界面层中的组件(例如可组合项、Activity 或 ViewModel)不应直接与数据源交互。数据源的示例如下:

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

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

使用领域层 如果您需要在多个 ViewModel 中重用与数据层交互的业务逻辑,或者您想简化特定 ViewModel 的业务逻辑复杂性,请使用领域层和用例。

界面层

界面层的作用是在屏幕上显示应用数据并作为用户交互的主要入口点。以下是界面层的一些最佳实践:

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

更多 ViewModel 最佳实践请见此处。

ViewModel 的优势请见此处。

使用生命周期感知型界面状态收集。 使用适当的生命周期感知型协程构建器从界面收集界面状态:视图系统中使用 repeatOnLifecycle,Jetpack Compose 中使用 collectAsStateWithLifecycle

阅读有关 repeatOnLifecycle 的更多信息。

阅读有关 collectAsStateWithLifecycle 的更多信息。

不要将事件从 ViewModel 发送到界面。 立即在 ViewModel 中处理事件,并通过事件处理结果触发状态更新。有关界面事件的更多信息请见此处。
使用单 Activity 应用。 如果您的应用有多个屏幕,请使用Navigation FragmentNavigation Compose在屏幕之间导航,并为您的应用设置深层链接。
使用Jetpack Compose 使用Jetpack Compose为手机、平板电脑、可折叠设备和 Wear OS 构建新应用。

以下代码段概述了如何以生命周期感知的方式收集界面状态:

视图

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负责提供界面状态和数据层访问权限。以下是 ViewModel 的一些最佳实践:

建议 描述
ViewModel 应与 Android 生命周期无关。 ViewModel 不应持有任何与生命周期相关的类型的引用。不要将 Activity, Fragment, ContextResources 作为依赖项传递。如果 ViewModel 中需要 Context,您应该认真评估其是否位于正确的层。
使用协程和流

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

  • 用于接收应用数据的 Kotlin 流,
  • 使用 viewModelScope 执行操作的 suspend 函数。
在屏幕级别使用 ViewModel。

不要在可重用的界面片段中使用 ViewModel。您应该在以下场景中使用 ViewModel:

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

以下代码段概述了如何从 ViewModel 暴露界面状态:

@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 用法 - 集成
  • Navigation 的高级用法,例如作用域到导航图的 ViewModel - 集成。

测试

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

建议 描述
了解要测试什么.

除非项目与 hello world 应用一样简单,否则您至少应该测试以下内容:

  • 单元测试 ViewModel,包括 Flow。
  • 单元测试数据层实体。即数据仓库和数据源。
  • 在 CI 中作为回归测试很有用的界面导航测试。
优先使用假对象而非模拟对象。 Android 文档中使用测试替身中阅读更多内容。
测试 StateFlow。 测试 StateFlow

如需了解更多信息,请查阅Android DAC 指南中的测试内容

模型

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

建议 描述
在复杂应用中,每层创建一个模型。

在复杂应用中,在不同的层或组件中创建新模型(如果合理)。请考虑以下示例:

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

命名惯例

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

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