本页介绍了一些与架构有关的最佳实践和建议。采用这些最佳实践和建议不仅可以提高应用的质量、稳健性和可伸缩性,还可以让您的应用更便于维护和测试。
以下最佳实践按主题分组。每项最佳实践都具有对应的优先级,反映了建议的程度。优先级列表如下:
- 强烈建议:除非与您的做法发生根本冲突,否则您应该按照相应实践的要求操作。
- 建议:按照相应实践的要求操作很有可能让您的应用变得更优秀。
- 可选:按照相应实践的要求操作在某些情况下能让您的应用变得更优秀。
分层架构
我们建议采用的分层架构有助于实现关注点分离。这种架构可以通过数据模型来驱动界面,符合单一可信来源原则,也符合单向数据流原则。以下是一些与分层架构有关的最佳实践:
| 建议 | 说明 |
|---|---|
| 使用明确定义的数据层。
强烈建议 |
数据层用于向应用的其余部分公开应用数据,并且包含应用的绝大部分业务逻辑。
|
| 使用明确定义的界面层。
强烈建议 |
界面层用于在屏幕上显示应用数据,并充当主要的用户互动点。Jetpack Compose 是推荐用于构建应用界面的新工具包。
|
| 使用代码库从数据层公开应用数据。 强烈建议 |
确保界面层中的组件(如可组合项或 ViewModel)不会直接与数据源交互。数据源示例包括:
|
| 使用协程和数据流。
强烈建议 |
使用协程和数据流在层之间进行通信。
如需详细了解协程最佳实践,请参阅在 Android 中使用协程的最佳实践。 |
| 使用网域层。
建议在大型应用中使用 |
如果您需要在多个 ViewModel 中重复使用与数据层交互的业务逻辑,或者想要简化特定 ViewModel 业务逻辑的复杂程度,请使用包含用例的网域层 |
界面层
界面层的作用是在屏幕上显示应用数据,并充当主要的用户互动点。以下是一些有关界面层的最佳实践:
| 建议 | 说明 |
|---|---|
| 遵循单向数据流 (UDF) 原则。
强烈建议 |
遵循单向数据流 (UDF) 原则,即 ViewModel 使用观察者模式来公开界面状态,并通过方法调用接收来自界面的操作。 |
| 如果 AAC ViewModel 的优势适用于您的应用,请加以使用。
强烈建议 |
使用 AAC ViewModel 处理业务逻辑,并提取应用数据以向界面公开界面状态。
如需详细了解 ViewModel 最佳实践,请参阅架构建议。 如需详细了解 ViewModel 的优势,请参阅将 ViewModel 用作业务逻辑状态容器。 |
| 使用生命周期感知型界面状态收集方式。
强烈建议 |
使用适当的生命周期感知型协程构建器 collectAsStateWithLifecycle 从界面收集界面状态。
|
| 请勿将来自 ViewModel 的事件发送到界面。
强烈建议 |
在 ViewModel 中立即处理事件,并通过事件的处理结果引发状态更新。如需详细了解界面事件,请参阅处理 ViewModel 事件。 |
| 使用单 activity 应用。
强烈建议 |
如果您的应用包含多个屏幕,请使用 Navigation 3 在屏幕以及指向您应用的深层链接之间导航。 |
| 使用 Jetpack Compose。
强烈建议 |
使用 Jetpack Compose 为手机、平板电脑、可折叠设备和 Wear OS 构建新应用。 |
以下代码段简要说明了如何以生命周期感知型方式收集界面状态:
@Composable
fun MyScreen(
viewModel: MyViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}
ViewModel
ViewModel 负责提供界面状态和访问数据层。以下是一些有关 ViewModel 的最佳实践:
| 建议 | 说明 |
|---|---|
| 使 ViewModel 独立于 Android 生命周期。
强烈建议 |
在 ViewModel 中,请勿存储对任何与生命周期相关的类型的引用。请勿将 Activity、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
)
// ...
}
生命周期
遵循有关如何使用 Activity 生命周期的最佳实践:
| 建议 | 说明 |
|---|---|
在可组合函数中使用生命周期感知型效应,而不是替换 Activity 生命周期回调。
强烈建议 |
请勿替换
|
以下代码段简要说明了如何在特定生命周期状态下执行操作:
@Composable
fun LocationChangedEffect(
locationManager: LocationManager,
onLocationChanged: (Location) -> Unit
) {
val currentOnLocationChanged by rememberUpdatedState(onLocationChanged)
LifecycleStartEffect(locationManager) {
val listener = LocationListener { newLocation ->
currentOnLocationChanged(newLocation)
}
try {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L,
1f,
listener,
)
} catch (e: SecurityException) {
// TODO: Handle missing permissions
}
onStopOrDispose {
locationManager.removeUpdates(listener)
}
}
}
处理依赖关系
在管理组件之间的依赖关系时,请遵循以下最佳实践:
| 建议 | 说明 |
|---|---|
| 使用依赖项注入。
强烈建议 |
尽可能使用依赖项注入最佳实践,主要是构造函数注入。 |
| 在必要时将作用域限定为某个组件。
强烈建议 |
如果类型包含多项需要共享的可变数据,或者类型初始化开销高昂且在应用中广泛使用,则将作用域限定为某个依赖项容器。 |
| 使用 Hilt。
建议 |
在简单应用中使用 Hilt 或手动依赖项注入。如果您的项目足够复杂,则使用 Hilt。例如,如果项目包含以下任一项:
|
测试
以下是一些有关测试的最佳实践:
| 建议 | 说明 |
|---|---|
| 了解要测试的内容。
强烈建议 |
除非项目像“Hello World”应用一样简单,否则请对其进行测试。至少应包含以下内容:
|
| 尽量采用虚假实现,而非模拟实现。 强烈建议 |
如需详细了解如何使用伪对象,请参阅 Android 文档中的“使用测试替身”。 |
| 测试 StateFlow。
强烈建议 |
测试 StateFlow 时,请执行以下操作:
|
如需了解详情,请参阅 Android 中要测试的内容和测试 Compose 布局。
模型
在应用中开发模型时,请遵循以下最佳实践:
| 建议 | 说明 |
|---|---|
| 对于复杂应用,要为每个层创建一个模型。
建议 |
在复杂应用中,必要时可以在不同的层或组件中创建新模型。请参考以下示例:
|
命名惯例
为代码库命名时,您应了解以下最佳实践:
| 建议 | 说明 |
|---|---|
| 命名方法。
可选 |
使用动词短语来命名方法,例如 makePayment()。 |
| 为属性命名。
可选 |
使用名词短语来命名属性,例如 inProgressTopicSelection。 |
| 为数据流命名。
可选 |
如果某个类公开了 Flow 流或任何其他流,则命名惯例为 get{model}Stream。例如 getAuthorStream(): Flow<Author>。如果函数返回模型列表,请使用复数模型名称:getAuthorsStream(): Flow<List<Author>>。 |
| 为接口实现命名。
可选 |
为接口实现使用有意义的名称。如果找不到更好的名称,请使用 Default 作为前缀。例如,对于 NewsRepository 接口,您可以使用 OfflineFirstNewsRepository 或 InMemoryNewsRepository。如果找不到合适的名称,请使用 DefaultNewsRepository。
为虚假实现添加前缀 Fake,例如 FakeAuthorsRepository。 |
其他资源
如需详细了解 Android 架构,请参阅下面列出的其他资源: