Kotlin 已成为 Android 开发当之无愧的首选语言------Google 确立的 "Kotlin-first" 策略足以说明它的地位。但"会用"和"用好"之间,还隔着从语言特性到工程化实践的一整套认知体系。本文结合官方推荐、社区最佳实践和真实踩坑经历,梳理 Android Kotlin 开发中那些你需要注意的事项。
如果你是一个团队,在开始开发前花时间定义一套代码规范和约定,会让后续开发轻松很多------无论对你还是对未来的维护者而言。
一、语言特性篇:把 Kotlin 用得"地道"
1. 数据类:别再手写模板代码了
对于仅承载数据的模型类,必须使用 data class 。它能自动生成 equals()、hashCode()、toString() 和 copy() 方法。
kotlin
// ✅ 一行顶 Java 几十行
data class User(val name: String, val age: Int, val email: String)
配合 copy() 方法,可以优雅地生成修改后的新实例,无需手动调用构造函数:
kotlin
val updatedUser = user.copy(age = 26)
2. 空安全:请对 !! 说"不"
Kotlin 的空安全机制是从根源上减少 NullPointerException(NPE) 的利器。但 !! 操作符相当于主动放弃了空安全检查,它会在运行时直接抛出 NPE------这和 Java 没什么区别。
生产代码中应尽量避免使用 !!。安全的替代方案包括:
kotlin
// ✅ 安全调用 + Elvis 操作符
val name = user?.profile?.name ?: "Unknown"
// ✅ Elvis + 提前返回
val user = repo.findById(id) ?: return Result.failure("用户不存在")
// ✅ 带错误信息的 requireNotNull
val id = requireNotNull(request.userId) { "userId 不能为空" }
经验之谈 :设计 API 时,用
sealed class或Result<T>来表示"没有值"或"操作失败",比返回可空类型更语义清晰、更安全。
3. 不可变性优先:val 而非 var
优先使用 val(不可变引用),仅在确实需要重新赋值时才使用 var。不可变性可以防止意外的状态变更,让代码更容易推理。
kotlin
// ✅ 优先 val
val userList = mutableListOf<User>() // 引用不可变,但内容可变
// ❌ 除非真的需要重新赋值
var currentUser: User? = null
4. 作用域函数:选对场景用对工具
let、run、apply、also、with 各有适用场景,不要乱用:
| 函数 | 上下文对象 | 返回值 | 典型场景 |
|---|---|---|---|
let |
it |
Lambda 结果 | 空值检查 + 转换 |
run |
this |
Lambda 结果 | 对象配置 + 计算结果 |
apply |
this |
上下文对象本身 | 初始化/配置(Builder 模式) |
also |
it |
上下文对象本身 | 副作用(日志、埋点) |
with |
this |
Lambda 结果 | 对非空对象连续操作 |
kotlin
// apply:初始化配置
val server = Server().apply {
host = "localhost"
port = 8080
}
// let:空安全转换
user?.let { sendEmail(it.email, "欢迎") }
5. 扩展函数:替代工具类的更优方案
Kotlin 的扩展函数让你在不修改原类的情况下增加新功能,比 Java 的工具类更优雅、更符合面向对象风格。
kotlin
// 给 String 添加邮箱校验
fun String.isValidEmail(): Boolean = contains("@") && contains(".")
// 调用时就像原生方法
"test@example.com".isValidEmail() // true
建议将相关的扩展函数放在专用文件中(如
extensions/UserExtensions.kt)。
二、工程实践篇:让项目经得起时间考验
1. 命名约定:可读性 > 一切
- 类/接口 :使用 PascalCase (如
MainActivity) - 变量/函数 :使用 camelCase (如
fetchUserData) - 函数名 :应以动词 开头,清晰描述执行的动作(如
updateAccount,而不是updAccount) - 避免缩写:缩写会困惑开发者,极具描述性的长名字反而更友好
- 布尔变量 :使用
is、has、can前缀(如isLoading、hasError)
kotlin
// ❌ 不推荐
fun getData() { ... }
fun updAccount(account: Account) { ... }
// ✅ 推荐
fun fetchDashboardData() { ... }
fun updateAccount(account: Account) { ... }
2. 函数设计:保持简短,单一职责
- 保持函数在 20 条指令以内,单一职责
- 使用默认参数代替多个重载方法,避免 null 检查
- 当参数超过 2 个时,使用命名参数 和换行提升可读性
kotlin
// ✅ 命名参数 + 换行
drawSquare(
x = 10,
y = 10,
width = 100,
height = 100,
fill = true
)
3. Gradle 配置:跟上官方节奏
Android Gradle Plugin (AGP) 9.0 起,Kotlin 支持已内置 ,不再需要手动应用 org.jetbrains.kotlin.android 插件。如果你的项目还在用旧方式,请按官方指引迁移。
同时注意:
- 使用
build.gradle.kts(Kotlin DSL)替代 Groovy DSL 编写构建脚本 - 使用 版本目录(Version Catalog) 统一管理所有依赖版本(
gradle/libs.versions.toml) - 废弃的
kotlinOptions.jvmTarget应替换为kotlin.jvmToolchain(17)
4. 协程与生命周期:别用 GlobalScope
生产代码中永远不要使用 GlobalScope------它不会在组件销毁时自动取消,容易造成内存泄漏和资源浪费。
正确的做法是将协程绑定到生命周期感知的 scope 上:
kotlin
// ✅ ViewModel 中
viewModelScope.launch { loadDashboardData() }
// ✅ Activity/Fragment 中
lifecycleScope.launch { fetchData() }
// ❌ 永远不要在正式代码中使用
GlobalScope.launch { sendAnalytics() }
选择正确的 Dispatcher 也很关键:
Dispatchers.IO→ 网络、文件、数据库操作Dispatchers.Default→ CPU 密集型计算(排序、解析、加密)Dispatchers.Main→ UI 更新
5. ViewBinding:替代 findViewById 的标准做法
Kotlin 早期有 kotlin-android-extensions 插件可以"直接使用视图 ID",但该插件已被废弃 。官方推荐的标准做法是 ViewBinding。
启用方式(build.gradle):
gradle
android {
buildFeatures {
viewBinding = true
}
}
使用方式:
kotlin
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.buttonId.setOnClickListener { /* ... */ }
}
}
6. kapt → KSP:尽早迁移
kotlin-kapt 插件与 AGP 9.0 的内置 Kotlin 支持不兼容 。官方强烈建议将使用 kapt 的注解处理器(如 Room、Glide、Dagger 等)迁移到 KSP(Kotlin Symbol Processing),以获得更快的编译速度。
如果暂时无法迁移,可将 kotlin-kapt 替换为 com.android.legacy-kapt 作为过渡方案。
7. 内存管理:这些细节别忽略
Kotlin 的简洁语法有时会让人忽略内存管理的细节:
- 避免在 companion object 中持有 Activity/View 引用------这会导致内存泄漏,因为静态引用会阻止组件被 GC 回收
- ViewBinding 变量在 Fragment 的
onDestroyView()中置 null,否则 Fragment 销毁后仍持有 View 引用 - 注册的监听器/观察者必须在
onStop()或onDestroy()中取消注册(如 SensorManager、BroadcastReceiver) - 使用 LeakCanary 等工具定期检测内存泄漏
8. 异常处理:Kotlin 的不同之处
Kotlin 的异常处理与 Java 有一个关键区别:所有异常都是 Unchecked Exception ,不需要在方法签名上声明 throws。
对于需要从 Java 调用的 Kotlin 函数,可以用 @Throws 注解来声明可能抛出的异常,让 Java 调用方感知:
kotlin
@Throws(IOException::class)
fun createDirectory(file: File) { /* ... */ }
在协程中,可以使用 CoroutineExceptionHandler 作为全局的异常捕获策略。
三、测试与质量篇:好代码是测出来的
- 单元测试覆盖率达到 80%+(Domain 层和 Presentation 层)
- ViewModel、UseCase、Repository 都应该有单元测试,使用 MockK 进行模拟,用 Turbine 测试 Flow
- 使用 detekt 、ktlint 等工具做静态代码检查和自动格式化,保持代码风格一致
总结
Kotlin 让 Android 开发变得更高效、更安全,但用好它需要从"会写"升级到"会写对"。归纳本文的核心建议:
语言层面 :善用 data class、扩展函数、空安全特性,远离 !! 和 GlobalScope。工程层面 :建立统一的命名规范,跟上 AGP 和 KSP 的演进节奏,把协程绑定到正确的生命周期 scope。质量层面:重视内存管理,写单元测试,用工具链保证代码质量。
这些注意事项不是为了给你设限,而是帮你写出更健壮、更可维护、让团队更省心的 Android 应用。