本页介绍了一些与架构有关的最佳实践和建议。采用这些最佳实践和建议不仅可以提高应用的质量、稳健性和可伸缩性,还可以让您的应用更便于维护和测试。
以下最佳实践按主题分组。每项最佳实践都具有对应的优先级,反映了我们团队的建议程度。优先级列表如下:
- 强烈建议:只要未与您的做法发生根本冲突,您就应该按照相应实践的要求操作。
- 建议:按照相应实践的要求操作很有可能让您的应用变得更优秀。
- 可选:按照相应实践的要求操作在某些情况下能让您的应用变得更优秀。
分层架构
我们建议采用的分层架构有助于实现关注点分离。这种架构可以通过数据模型来驱动界面,符合单一可信来源原则,也符合单向数据流原则。以下是一些与分层架构有关的最佳实践:
建议 | 说明 |
---|---|
使用明确定义的数据层。
强烈建议 |
数据层用于向应用的其余部分公开应用数据,并且包含应用的绝大部分业务逻辑。
|
使用明确定义的界面层。
强烈建议 |
界面层用于在屏幕上显示应用数据,并充当主要的用户互动点。
|
数据层应该使用代码库来公开应用数据。
强烈建议 |
界面层中的组件(如可组合项、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 时执行工作,请使用 repeatOnLifecycle API。 |
以下代码段简要说明了如何在特定生命周期状态下执行操作:
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 。 |