工作几年才发现:
text
真正折磨人的根本不是线程。
而是这些东西:
- callback 一层套一层
- Activity 都退出了任务还在跑
- 页面销毁了回调突然回来了
- 数据更新顺序乱掉
- Handler、Rx、LiveData、Flow 混着用
- 每个 SDK 都有自己的一套回调体系
最后项目慢慢变成:
一个巨大的异步缝合怪。
而这,其实才是 Android 大型项目真正的复杂度来源。
一、Android 最早其实没有统一异步方案
回头看看 Android 的发展历史,你会发现:
Android 从来都不缺异步方案。
缺的是:
一个统一的异步方案。
Thread + Handler 时代
最早大家都是这么写:
kotlin
Thread {
val data = request()
handler.post {
updateUi(data)
}
}.start()
刚学的时候觉得:
text
好简单啊。
后来项目大了以后发现:
- 线程谁创建?
- 谁销毁?
- 谁切主线程?
- 页面退出怎么办?
- 回调回来页面没了怎么办?
写着写着就开始出现:
text
if(activity.isFinishing) return
text
if(fragment.isAdded)
text
WeakReference<Activity>
整个代码开始变得越来越奇怪,也越来越难维护。
AsyncTask 时代
后来 Google 觉得:
text
直接操作 Thread 太难了。
于是推出了:
kotlin
doInBackground()
onPostExecute()
当时感觉简直就是神器。
结果几年后大家发现:
它其实只是:
text
把 callback 换了个地方写。
问题一个都没解决。
最后甚至被官方直接废弃。
Listener 大爆炸时代
后来 Android 系统越来越复杂。
各种 Listener 开始疯狂增长:
- LocationListener
- SensorListener
- BluetoothListener
- MediaPlayerListener
- CameraListener
- SurfaceListener
然后你会发现:
每个系统组件都有自己的玩法。
有的:
text
回调主线程
有的:
text
回调 Binder 线程
有的:
text
必须手动 unregister
有的:
text
生命周期自动结束
还有一些第三方 SDK:
text
文档都不告诉你在哪个线程回调。
于是整个项目开始进入:
万物皆回调时代。
二、真正复杂的,是线程之间的协作。
很多人觉得:
text
异步难 = 多线程难
其实完全不是。
举个最常见的业务:
text
请求用户信息
↓
拿到用户 ID
↓
请求订单列表
↓
写入数据库
↓
通知 UI 更新
↓
页面退出时自动取消
这里最难的地方其实根本不是:
text
开线程。
而是:
text
这一整套流程谁来管理?
什么时候取消?
异常怎么传?
页面销毁怎么办?
谁负责切线程?
谁负责回调 UI?
这些才是真正麻烦的地方。
三、最可怕的是每层都有自己的异步模型
比如一个老项目可能长这样:
网络层:
text
Callback
数据库:
text
RxJava
UI:
text
LiveData
系统事件:
text
Handler
IM:
text
Listener
最后业务代码变成:
text
Callback 套 Rx
Rx 转 LiveData
LiveData 再转 Flow
Flow 最后再 collect
整个项目像一个异步中转站。
你根本不知道:
数据到底从哪里来的。
又会在什么时候回来。
四、RxJava 第一次让大家看到希望
RxJava 当年为什么这么火?
因为它第一次告诉大家:
text
别管什么网络请求、按钮点击、数据库变化。
统统都是流。
网络请求是流。
数据库变化是流。
按钮点击也是流。
甚至定时器都是流。
突然之间:
整个项目终于开始说同一种语言了。
这也是为什么:
那几年 Android 基本就是:
text
不会 RxJava 都不好意思投简历。
五、但 RxJava 最大的问题是:
它终究只是一个库。
换句话说:
text
你会 Rx 才能玩。
不会?
那项目基本看不懂。
尤其这种代码:
kotlin
flatMap()
switchMap()
zip()
combineLatest()
observeOn()
subscribeOn()
新人看到基本直接投降。
而且:
Rx 世界。
Callback 世界。
Kotlin 世界。
很多时候还是三套体系同时存在。
项目依旧会慢慢变成:
text
异步缝合怪。
六、协程真正厉害的地方
Kotlin 协程最厉害的地方其实是:
text
异步终于变成语言能力了,以及把复杂的异步变成线性的可控的。
以前:
text
异步靠框架。
现在:
text
异步直接写进语言里。
于是:
kotlin
val user = api.getUser()
val order = api.getOrder(user.id)
dao.insert(order)
看起来像同步。
实际上全是异步。
代码终于开始重新变得像人写的了。
七、Flow 真正解决的是:
终于不用到处转来转去了。
以前:
text
Callback -> Rx -> LiveData -> UI
现在:
text
Flow -> UI
网络:
kotlin
Flow<User>
数据库:
kotlin
Flow<List<Article>>
WebSocket:
kotlin
Flow<Message>
蓝牙状态:
kotlin
Flow<BluetoothState>
UI 状态:
kotlin
StateFlow<UiState>
整个项目终于开始说一种语言。
这件事其实比性能优化重要得多。
因为:
架构最怕的就是乱
八、Compose 为什么疯狂拥抱 Flow
因为它俩本来就是一家的。
Compose:
text
状态变了
↓
UI 自动刷新
Flow:
text
数据变了
↓
状态自动更新
所以:
kotlin
val uiState by viewModel.uiState.collectAsState() 这里是举例,生产环境要使用正确的withlife 那个。
九、这些年 Android 架构升级,
其实一直都在解决同一个问题。
表面上看:
text
MVVM
MVI
Compose
Flow
StateFlow
UDF
像是各种新名词不断冒出来。
但本质上它们都在做一件事:
把失控的异步重新关进规则里。
因为大型项目真正可怕的从来不是:
text
功能太多。
而是:
text
异步失控。
什么时候执行?
什么时候取消?
谁先回来?
谁后回来?
谁负责更新状态?
这些问题如果没人管。
项目迟早会变成:
text
谁改谁背锅。
最后
Android 架构这些年的演化。
表面上看是在:
text
升级 API
更换框架
更新架构
但底层其实一直都在做同一件事:
消灭异步缝合怪。
从 Thread 到 Handler。
从 AsyncTask 到 RxJava。
从 RxJava 到 Coroutine。
从 LiveData 到 Flow。
从 XML 到 Compose。
技术一直在变。
但 Android 工程师和异步战斗这件事,
十几年了,到了协程,总算熬到了头。
参考
从 Callback 到 Coroutines:Android 异步并发方案的演进
"结构化"这个词,本质上就是------把混乱的东西变成有组织、有规则、有边界的东西
从"调用方的如履薄冰"到"接口的天然语义":Room/DataStore/Retrofit 的启示
现代 Android 官方为什么更推荐 Repository 暴露 suspend fun,而不是在内部 launch
# Android 架构指南之Data 层不要再暴露 start/stop 了:用 Flow 接管生命周期
别再 launch(IO) 了:协程线程切换的 3隐藏反模式
从送外卖看Android Clean架构:为什么老板不需要知道外卖员开什么车