
在该系列的 第一篇 和 第二篇 中,我们探讨了 Kotlin 的基础知识、高级语言特性,以及编写简洁、可维护代码的模式。
在这篇文章中,我们将转向实际开发实践,包括测试、性能优化、架构以及 Kotlin 多平台(KMP)。
这些实践对于可投入生产的应用程序和大型代码库至关重要。
使用协程编写单元测试
Kotlin 协程和 Flow
很棒,但它们需要进行恰当的测试。使用runTest
(来自kotlinx-coroutines -test
)来确定性地测试挂起函数和数据流。
Kotlin
@Test
fun testFlowEmitsValues() = runTest {
val flow = flowOf(1, 2, 3)
val results = flow.toList()
assertEquals(listOf(1, 2, 3), results)
}
最佳实践:始终将业务逻辑放在
ViewModel
/用例层中,使其在不依赖安卓的情况下也能进行测试。
避免过度设计
这条规则不仅仅适用于 Kotlin,甚至所有的项目或者语言都适用。
虽然 MVVM 、MVI 和整洁架构很受欢迎,但除非有需要,否则不要增加复杂性。
- 对于原型或演示应用 -> 一个
Activity
的结构够用了。 - 对于小型应用程序 -> MVVM 就足够了。
- 对于大型应用程序 -> 可以考虑整洁架构,但要务实一些。
- 避免不必要的深层级(例如,一个只有 3 个屏幕的应用程序设置 存储库->用例->管理器->服务->数据访问对象 这样的层级架构,这会显著增加维护成本,得不偿失。)。
Compose 中的性能优化
Jetpack Compose 是声明式的,但使用不当会影响性能。
- 使用
remember
来避免重新组合。 - 提升状态------将状态保存在
ViewModel
或父级可组合项中,而不是放在 UI 的深层。 - 使用
LazyColumn
而不是手动创建可滚动列表。 - 尽可能使用不可变集合。
Kotlin
val counter = remember { mutableStateOf(0) }
Button(onClick = { counter.value++ }) {
Text("Clicks: ${counter.value}")
}
状态管理中优先使用不可变性
不可变状态有助于避免棘手的 UI 错误。
- 用密封类或数据类来表示用户界面状态。
- 避免直接从
ViewModel
中暴露可变对象。
Kotlin
data class UiState(val isLoading: Boolean, val data: List<String> = emptyList())
实际示例:运用最佳实践加载用户数据
让我们在一个实际场景中结合密封的用户界面状态(UiState
)、数据流(Flow
)、Compose 和单元测试。
密封的用户界面状态
Kotlin
sealed class UserUiState {
object Loading : UserUiState()
data class Success(val users: List<String>) : UserUiState()
data class Error(val message: String) : UserUiState()
}
在 ViewModel 中使用 Flow
Kotlin
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState
init { loadUsers() }
private fun loadUsers() {
viewModelScope.launch {
try {
val users = repository.getUsers()
_uiState.value = UserUiState.Success(users)
} catch (e: Exception) {
_uiState.value = UserUiState.Error("Failed to load users")
}
}
}
}
存储
Kotlin
class UserRepository {
suspend fun getUsers(): List<String> {
delay(1000) // 模拟网络请求
return listOf("Alice", "Bob", "Charlie", "John")
}
}
Compose UI
Kotlin
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val state by viewModel.uiState.collectAsState()
when (state) {
is UserUiState.Loading -> CircularProgressIndicator()
is UserUiState.Success -> {
val users = (state as UserUiState.Success).users
LazyColumn {
items(users) { user ->
Text(user, modifier = Modifier.padding(16.dp))
}
}
}
is UserUiState.Error -> {
Text(
text = (state as UserUiState.Error).message,
color = Color.Red,
modifier = Modifier.padding(16.dp)
)
}
}
}
单元测试
Kotlin
@OptIn(ExperimentalCoroutinesApi::class)
class UserViewModelTest {
private val testDispatcher = StandardTestDispatcher()
@Before fun setup() { Dispatchers.setMain(testDispatcher) }
@After fun tearDown() { Dispatchers.resetMain() }
@Test
fun testLoadUsers_success() = runTest {
val repository = UserRepository()
val viewModel = UserViewModel(repository)
val state = viewModel.uiState.value
assertTrue(state is UserUiState.Success)
assertEquals(listOf("Alice", "Bob", "Charlie", "John"), (state as UserUiState.Success).users)
}
}
此示例展示了:
- 用于不可变状态的密封类。
ViewModel
中的数据流(Flow
)和协程。- 对状态做出响应的 Jetpack Compose 用户界面。
- 使用
runTest
- 和协程实现的可测试性。
合理使用依赖注入(DI)
依赖注入可提高可测试性和模块化程度。在安卓开发中,推荐使用 Hilt (如果你想要更轻量级的替代方案,也可以选择 Koin)。
最佳实践:避免过度注入。仅注入需要共享的内容(例如存储库、API 客户端)。
最小化 APK/应用包大小
- 启用 R8 和 ProGuard。
- 将
shrinkResources
设置为true
以移除未使用的资源。 - 使用
const val
定义键,而不是使用大型配置对象。 - 对于重量级对象,优先使用
lazy {}
进行初始化。
Kotlin 多平台(KMP)最佳实践
2025 年,Kotlin 多平台技术发展迅速。为保证代码整洁,需做到:
- 共享业务逻辑(模型、仓储、用例)。
- 保持用户界面特定于平台(安卓使用 Jetpack Compose,iOS 使用 SwiftUI)。
- 网络通信使用 Ktor ,共享数据库使用 SQLDelight ,序列化处理使用 kotlinx-serialization。
文档使用 KDoc
优质的文档能避免未来的麻烦。对公共函数、类和模块使用 KDoc(/** ... */)。
Kotlin
/**
* Fetches user profile data from the server.
*
* @param userId ID of the user to fetch.
* @return A [User] object containing profile information.
*/
suspend fun getUserProfile(userId: String): User
KDoc 是 Kotlin 的标准文档注释格式,编写方式以 Markdown 语法为主。
静态代码分析
使用以下工具:
- ktlint -> 代码格式化
- detekt -> 检测代码异味和复杂度
- SonarQube -> 评估可维护性指标
最佳实践:将这些工具集成到持续集成/持续交付(CI/CD)管道中。
时刻考虑可扩展性
在使用 Kotlin 进行编码时,要问自己:
- 这段代码会随着应用程序的发展而具备可扩展性吗?
- 它是否可测试且易于维护?
- 新团队成员能否轻松理解它?
一些小的决策(比如命名、分层和不可变性)日后会累积带来巨大的生产力提升。
总结
通过第三部分的内容,我们涵盖了测试、架构、性能、Kotlin 多平台编程(KMP)以及代码质量等方面。到现在,你应该不仅对编写 Kotlin 代码有了扎实的掌握,更能够编写符合生产标准的 Kotlin 代码。
但这仅仅是个开始。Kotlin 的发展日新月异 ------ 新的编程模式和工具不断涌现。