Kotlin入门:一个Android开发者的实战笔记
我第一次在项目中用上Kotlin,还是2017年那会儿。那个时候Google官方刚宣布支持Kotlin为Android开发的一等语言,社区里各种声音此起彼伏——有人觉得这是“Java的救命稻草”,也有人觉得不过是又一门“语法糖堆砌的新玩具”。作为一个已经写了五六年Java的Android开发者,我当时心里也是五味杂陈。
但真正动手写起来才发现,这真的是我用过最舒服的Android开发语言。它没有陡峭的学习曲线,也没有颠覆性的思想冲击,但却在细节处不断给你带来惊喜。今天我就想结合自己亲身经历的一个项目,聊聊怎么快速上手Kotlin,并且写出可维护、性能好、用户体验佳的Android应用。
项目背景:重构一个老的电商App核心模块

事情要从2020年说起,公司决定对我们一款运营了多年的电商类App的核心交易模块进行重构。这套系统之前完全是用Java写的,代码量庞大,耦合严重,而且经常出现空指针异常和生命周期管理混乱的问题。
当时团队里有位来自欧洲的技术总监提了个建议:“要不要试试Kotlin?”这个提议一开始还引发了争议,毕竟大家都习惯了Java那一套,贸然引入新语言会不会拖慢进度?不过最后我们还是决定小规模试水一下——先拿订单提交页面开刀。
遇到的挑战:从Java到Kotlin的过渡并不总是顺畅

说真的,刚开始切换到Kotlin的时候,还真有点不适应。
首先是空安全机制这一块,让我吃了不少苦头。Java是允许变量为null的,但在Kotlin里,编译器强制你面对这个问题。比如下面这段代码:
val name: String = user.name // 如果user.name可能为null怎么办?
这时候就得改成:
val name: String? = user?.name
刚开始我还很抵触这种做法,觉得“多写几个?有什么用?Runtime照样崩!”后来有一次,在处理用户地址选择逻辑时,因为没检查null导致线上出现了Crash。那一刻我才真正意识到,Kotlin通过编译期强制检查null,其实是帮我们把问题提前暴露了出来。
其次就是协程(Coroutines)的使用,这玩意真得仔细琢磨。虽然文档讲得天花乱坠,说它比线程轻量、可以简化异步回调。但我们项目里原来全靠RxJava来处理网络请求和UI更新,突然换一套思维方式,一时间很多人摸不着头脑。
比如我们之前处理下单流程的伪代码大概是这样:
// Java版伪代码
requestCartData(userId, new CartCallback() {
@Override
public void onSuccess(Cart cart) {
showCart(cart);
requestAddress(new AddressCallback() {
@Override
public void onSuccess(Address address) {
checkStock(address, new StockCallback() {
...
});
}
});
}
});
典型的“回调地狱”。换成Kotlin后,我们要用协程改写成类似:
// 协程版
viewModelScope.launch {
try {
val cart = withContext(Dispatchers.IO) { api.getCart(userId) }
showCart(cart)
val address = withContext(Dispatchers.IO) { api.getAddress() }
checkStock(address)
} catch (e: Exception) {
handleError(e)
}
}
结构清晰了不止一点。但刚开始大家对launch、withContext这些概念都很懵,甚至有人直接在线程里嵌套用了runBlocking,结果整个UI卡死……
我们是怎么解决这些问题的?

1. 逐步替换 + 混合编程策略
我们没有一刀切地全部重写旧代码,而是采用了渐进式迁移的方式:
- 新功能完全用Kotlin写
- 老模块封装关键逻辑成接口,逐渐抽离出Kotlin可调用的服务层
- Java和Kotlin文件共存于同一项目中,通过工具辅助转换(IntelliJ内置)
这样的好处是避免了推翻重来的风险,也让团队成员有一个缓冲期。
2. 强化培训 + 实战演练
为了让团队更快上手,我们在每周站会上专门安排了一个小时讲解Kotlin的核心特性,比如:
- Null Safety(空安全)
- Extension Functions(扩展函数)
- Data Classes(数据类)
- Sealed Class(密封类)
- Lambda表达式与高阶函数
- Coroutines与Flow
- 与Java互操作性
每讲完一个知识点,就让组员现场改写一小段Java代码为Kotlin风格。比如:
原始Java代码:
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
改为Kotlin后:
data class User(val name: String, val age: Int)
光一行就省去了N多样板代码,看着真的很爽 😄
关键代码示例


数据绑定 + ViewModel架构
我们使用Kotlin + ViewModel + LiveData作为架构基础。举个实际例子:
class OrderViewModel : ViewModel() {
private val _orderState = MutableLiveData<OrderState>()
val orderState: LiveData<OrderState> get() = _orderState
fun submitOrder(order: Order) {
viewModelScope.launch {
try {
val response = apiService.submitOrder(order)
_orderState.postValue(OrderState.Success(response))
} catch (e: Exception) {
_orderState.postValue(OrderState.Error(e.message))
}
}
}
}
sealed class OrderState {
data class Success(val orderId: String) : OrderState()
data class Error(val message: String?) : OrderState()
}
这样在Activity或Fragment中只需订阅即可:
viewModel.orderState.observe(this, Observer {
when (it) {
is OrderState.Success -> showSuccess(it.orderId)
is OrderState.Error -> showError(it.message ?: "提交失败")
}
})
使用协程替代嵌套回调
刚才说的那个下单回调地狱,最终被我们简化成了:
fun checkout() {
viewModelScope.launch {
val cart = fetchCart()
val address = fetchAddress()
val result = validateInventory(cart, address)
if (result.isValid) {
placeOrder(cart, address)
} else {
showOutOfStockError()
}
}
}
是不是清爽多了?再也不用写一堆callback接口啦!
开发过程中遇到的坑 & 应对方法
✅ 坑一:Java调用Kotlin带默认参数的方法报错
例如:
fun saveUser(user: User, notify: Boolean = true) {
userRepository.save(user)
if (notify) sendNotification()
}
如果在Java中调用:
User user = new User("Tom");
saveUser(user); // 报错!找不到对应方法
解决办法: 加上@JvmOverloads注解,让Kotlin为每个默认参数生成多个Java兼容的重载方法:
@JvmOverloads
fun saveUser(user: User, notify: Boolean = true) { ... }
❌ 坑二:在非协程上下文随意使用挂起函数
有时候我们会不小心在一个普通函数里直接调用挂起函数:
fun loadData() {
val data = asyncLoad() // 编译错误:必须在协程中调用suspend函数
}
解决方案是确保所有suspend函数都在协程作用域内执行,比如用viewModelScope或lifecycleScope包裹:
fun loadData() {
viewModelScope.launch {
val data = asyncLoad()
updateUI(data)
}
}
⚠️ 坑三:List vs MutableList 的混淆
这点特别容易踩坑。比如我们有时候会这样写:
var users: List<User> = mutableListOf()
users.add(User("A")) // 报错!List不能添加元素
正确的写法应该是:
var users: MutableList<User> = mutableListOf()
users.add(User("A")) // 正确
或者:
val userList: List<User> = getUsers()
val mutableUsers = userList.toMutableList()
mutableUsers.add(newUser)
效果总结:Kotlin给项目带来的提升

经过几个月的努力,我们完成了订单模块的全面Kotlin化改造,效果非常显著:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 代码行数 | 8500+ 行 | 6200 行左右 |
| Crash率 | 约0.3% | 下降到0.05% |
| 接口调用链路复杂度 | 多层回调 | 结构清晰 |
| 开发效率 | 团队初期略降 | 中后期提高30% |
更难得的是,新入职的同事普遍反馈阅读Kotlin代码更容易理解业务逻辑,尤其是那些用了sealed class和when表达式的部分。
给读者的几点建议
如果你正准备学习Kotlin并开始Android开发,以下是我在实战中总结的一些实用建议:
1. 先掌握基本语法 + 和Java的区别
别一开始就冲着MVVM、Jetpack Compose去学。先把null safety、函数类型、扩展函数、对象声明、协程基础这些东西弄清楚。
推荐一个学习路径:
- 官方Kotlin Playground(https://play.kotlinlang.org/)
- Kotlin for Android 开发者课程(Udacity 或 JetBrains Academy)
- 一步步把已有Java类转成Kotlin看看生成的代码
2. 在已有项目中逐步引入
没必要把整个项目推倒重来。可以从新功能入手,比如:
- 新建Fragment用Kotlin写
- 创建工具类时优先用Kotlin实现
- 用extension function优化原有utils类
3. 协程不要滥用,讲究scope和context
很多同学一开始看到async就激动地满屏用,结果出现并发问题。记住几个原则:
- UI相关操作放在主协程(lifecycleScope/viewModelScope)
- IO密集型操作放Dispatchers.IO
- 不要在GlobalScope里随意启动任务(除非你真的知道后果)
4. 学会配合Android Jetpack组件
现在Kotlin几乎是Jetpack的最佳搭档。建议搭配使用:
- ViewModel + LiveData(或StateFlow)
- Room数据库 + Coroutines
- Navigation Component + SafeArgs
- Retrofit + Coroutine + Flow
5. 注意适配不同Android版本
特别是从Java迁移到Kotlin的过程中,要注意API兼容性和运行环境问题。比如有些Kotlin特性需要minSdkVersion ≥ 21才能用,否则会导致低版本崩溃。
可以在build.gradle里加上:
android {
kotlinOptions {
jvmTarget = '1.8'
}
}
另外,如果你打算发布到应用市场,一定要注意以下几点:
- Kotlin运行时是否被打包进APK
- ProGuard/R8配置是否正确排除Kotlin类
- 启动冷热修复时Kotlin反射行为是否受影响
写在最后:技术选型背后的思考
这几年回头看,当初决定用Kotlin做项目重构,可以说是一次成功的冒险。它不仅帮助我们降低了代码冗余、提升了质量,更重要的是改变了团队的开发思维。
Kotlin并不是用来“炫技”的,而是为了解决真实问题。就像我们那个订单模块,正是因为有了Null Safety和协程,才让我们能更专注业务,而不是花时间写防御性代码。
所以我的建议是:如果你想写出高质量的Android应用,Kotlin是必经之路。它的学习曲线不算陡峭,但回报却很高。尤其是在Jetpack和Compose生态日益成熟的当下,掌握Kotlin几乎成了现代移动开发的标准动作。
希望这篇文章能帮你少走弯路,快速进入状态。如果你有关于Kotlin、Android开发的问题,欢迎留言交流,我也很乐意分享更多一线经验。

评论 0