本页介绍了一些与架构有关的最佳实践和建议。采用这些最佳实践和建议不仅可以提高应用的质量、稳健性和可伸缩性,还可以让您的应用更便于维护和测试。
以下最佳实践按主题分组。每项最佳实践都具有对应的优先级,反映了我们团队的建议程度。优先级列表如下:
- 强烈建议:除非它与您的方法发生根本性冲突,否则您不应该这样做。
- 建议:按照相应实践的要求操作很有可能让您的应用变得更优秀。
- 可选:按照相应实践的要求操作在某些情况下能让您的应用变得更优秀。
分层架构
我们建议采用的分层架构有助于实现关注点分离。这种架构可以通过数据模型来驱动界面,符合单一可信来源原则,也符合单向数据流原则。以下是一些与分层架构有关的最佳实践:
| 建议 | 说明 | 
|---|---|
| 使用明确定义的数据层。 强烈建议 | 数据层用于向应用的其余部分公开应用数据,并且包含应用的绝大部分业务逻辑。 
 | 
| 使用明确定义的界面层。 强烈建议 | 界面层用于在屏幕上显示应用数据,并充当主要的用户互动点。 
 | 
| 数据层应该使用代码库来公开应用数据。 强烈建议 | 界面层中的组件(如可组合项、activity 或 ViewModel)不应直接与数据源交互。数据源示例: 
 | 
| 使用协程和数据流。 强烈建议 | 使用协程和数据流在层之间进行通信。 | 
| 使用网域层。 建议在大型应用中使用 | 如果您需要在多个 ViewModel 中重复使用与数据层交互的业务逻辑,或者想要简化特定 ViewModel 业务逻辑的复杂程度,请使用网域层 | 
界面层
界面层的作用是在屏幕上显示应用数据,并充当主要的用户互动点。以下是一些有关界面层的最佳实践:
| 建议 | 说明 | 
|---|---|
| 遵循单向数据流 (UDF) 原则。 强烈建议 | 遵循单向数据流 (UDF) 原则,即 ViewModel 使用观察者模式来公开界面状态,并通过方法调用接收来自界面的操作。 | 
| 如果 AAC ViewModel 的优势适用于您的应用,请加以使用。 强烈建议 | 使用 AAC ViewModel 处理业务逻辑,并提取应用数据以向界面公开界面状态(Compose 或 Android View)。 | 
| 使用生命周期感知型界面状态收集方式。 强烈建议 | 使用适当的生命周期感知型协程构建器从界面收集界面状态:View 系统中使用 repeatOnLifecycle,Jetpack Compose 中使用collectAsStateWithLifecycle。详细了解  | 
| 请勿将来自 ViewModel 的事件发送到界面。 强烈建议 | 在 ViewModel 中立即处理事件,并通过事件的处理结果引发状态更新。如需详细了解界面事件,请访问此处。 | 
| 使用单 activity 应用。 建议 | 如果您的应用包含多个屏幕,请使用 Navigation fragment 或 Navigation Compose 在屏幕以及指向您应用的深层链接之间导航。 | 
| 使用 Jetpack Compose。 建议 | 使用 Jetpack Compose 为手机、平板电脑、可折叠设备和 Wear OS 构建新应用。 | 
以下代码段简要说明了如何以生命周期感知型方式收集界面状态:
View
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, Context或Resources作为依赖项传递。如果某元素需要在 ViewModel 中使用Context,您应该严格评估其是否位于正确的层中。 | 
| 使用协程和数据流。 强烈建议 | ViewModel 通过以下方式与数据层或网域层交互: 
 | 
| 在屏幕级别使用 ViewModel。 强烈建议 | 请勿在可重复使用的界面部分中使用 ViewModel。您应该在以下位置使用 ViewModel: 
 | 
| 在可重复使用的界面组件中使用普通状态容器类。 强烈建议 | 使用普通状态容器类处理可重复使用的界面组件中的复杂工作。这样即可从外部对状态进行提升和控制。 | 
| 请勿使用 AndroidViewModel。建议 | 使用 ViewModel类,而非AndroidViewModel。不应在 ViewModel 中使用Application类。正确做法是将依赖项移至界面层或数据层。 | 
| 公开界面状态。 建议 | ViewModel 应该通过名为 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时执行工作,请使用repeatOnLifecycleAPI。 | 
以下代码段简要说明了如何在特定生命周期状态下执行操作:
View
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。例如,如果您: 
 | 
测试
以下是一些有关测试的最佳实践:
| 建议 | 说明 | 
|---|---|
| 了解要测试的内容。 强烈建议 | 除非项目基本上像“Hello World”应用一样简单,否则您至少应对其进行以下几项测试: 
 | 
| 尽量采用虚假实现,而非模拟实现。 强烈建议 | 如需了解详情,请参阅 Android 文档中的“使用测试替身”。 | 
| 测试 StateFlow。 强烈建议 | 测试 StateFlow时:
 | 
如需了解详情,请参阅 Android DAC 指南中的“要测试的内容”。
模型
在应用中开发模型时,应该遵循以下最佳实践:
| 建议 | 说明 | 
|---|---|
| 对于复杂应用,要为每个层创建一个模型。 建议 | 在复杂应用中,必要时可以在不同的层或组件中创建新模型。请参考以下示例: 
 | 
命名惯例
为代码库命名时,您应了解以下最佳实践:
| 建议 | 说明 | 
|---|---|
| 命名方法。 可选 | 为方法命名时应该使用动词短语。例如, makePayment()。 | 
| 为属性命名。 可选 | 为属性命名时应该使用名词短语。例如, inProgressTopicSelection。 | 
| 为数据流命名。 可选 | 如果某个类公开了 Flow 流、LiveData 或任何其他流,则命名惯例为 get{model}Stream()。例如,getAuthorStream(): Flow<Author>如果函数会返回模型列表,则模型名称应该采用复数形式:getAuthorsStream(): Flow<List<Author>> | 
| 为接口实现命名。 可选 | 接口实现的名称应该有意义。如果找不到更好的名称,请使用 Default作为前缀。例如,对于NewsRepository接口,您可以使用OfflineFirstNewsRepository或InMemoryNewsRepository。如果找不到合适的名称,请使用DefaultNewsRepository。
    虚假实现应该添加前缀Fake,例如FakeAuthorsRepository。 | 
