整理旧电脑,发现在待发布文章的文件夹下还躺着好几篇博客,整理下也补发上来。
代码使用 Kotlin 重写,API 也更新为新版本。
一、前言
如果你是一名10年经验的 Android 开发者,或者打开那些 2018 年前后的 Android 教程,会发现几乎每篇都是采用了 AsyncTask 去做异步操作。但当你在 Android Studio 输入AsyncTask,IDE 会立刻给你一个删除线,并提示:"This class was deprecated in API level 30."

AsyncTask 在 Android 11(API 30)中被正式标记为废弃。官方给出的替代建议非常明确:使用标准的 java.util.concurrent 或 Kotlin 协程。
这个从 Android 1.5 (API 3,2009) 就被引入的控件,可谓是解决 Android 异步任务的不二之选。英雄迟暮,好好的 AsyncTask 为何就不建议使用并且要淘汰它,那么建议的替代方案又是什么?这篇文章会给出答案。

二、AsyncTask 为何被弃用
kotlin
class LoadTask(private val textView: TextView) : AsyncTask<String, Int, String>() {
override fun onPreExecute() { /* 显示进度条 */ }
override fun doInBackground(vararg params: String): String {
publishProgress(50)
return download(params[0]) // 真实耗时操作
}
override fun onProgressUpdate(vararg values: Int) { /* 更新进度 */ }
override fun onPostExecute(result: String) { textView.text = result }
}
🎓展示下经典
AsyncTask代码长什么样。
2.1 异步任务和生命周期的天然冲突
AsyncTask 的核心设计是:doInBackground() 在后台线程执行,onPostExecute() 回到主线程更新UI。这个模型在"短生命周期"的场景下使用没有问题,但 Activity 和 Fragment 随时可能因为配置变更(如屏幕旋转)或用户返回操作而被销毁和重建。
Activity 被销毁后,AsyncTask 会仍然持有对它的引用,这就造成了 Context 泄漏(旧Activity 没有回收)。有经验的开发者会自己写 WeakReference、判断 isFinishing、处理重建后的状态恢复等。
2.2 异常处理形同虚设
AsyncTask 有一个非常隐蔽的问题:doInBackground() 中抛出的异常会被 AsyncTask 自身吞掉,不会传播到调用方。如你的网络请求在后台线程中抛出了 IOException,你不会在 onPostExecute() 中看到任何异常信息,只会发现结果"莫名其妙地没有返回"。想要获取到异常必须自己去查 get(),但这是较难排查到的。
2.3 全局串行队列的性能瓶颈
AsyncTask 在 Android 3.0 (API 11) 之后默认使用 SERIAL_EXECUTOR(全局串行),这意味着你启动的多个 AsyncTask 会被依次排队执行,而不是并发执行。开发者为了并行常常改成 executeOnExecutor(THREAD_POOL_EXECUTOR),它允许手动指定线程池。而这个线程池的核心/最大线程数被硬编码为 128 ,队列长度也是 128,任务一多就会出现 RejectedExecutionException;同时因为它是全局共享的,你无法按业务隔离优先级------【上传大文件】会把【加载小缩略图】一起堵死。
2.4 平台版本间的差异
AsyncTask 在不同 Android 版本上的行为并不一致。如执行器的类型(串行 vs 并行)、线程池大小、以及取消行为的差异。一旦我们的 APP 需要兼容多个 Android 版本,无疑会增加代码的复杂度。
2.5 还有其他的问题
除了上面的4条,AsyncTask 还有如下问题/不足:
- 结果类型受限 :泛型参数是
AsyncTask<Params, Progress, Result>,一旦结果是「进度 + 数据 + 错误」的复合状态,就只能靠Object强转或额外的成员字段,类型安全直接丢失。 - 不可组合 :想实现「先查缓存,命中则用,未命中请求网络,最后合并两个结果」,
AsyncTask只能靠回调套回调。 - 取消不彻底 :
cancel()只是把状态置为CANCELLED,并在下一次onPostExecute时改调onCancelled;正在执行的doInBackground不一定真的停止,阻塞的 IO 会继续跑。 - 单元测试受限 :
AsyncTask与Activity、View强耦合,脱离 Android 运行时几乎无法做单元测试。
这一些列的问题/不足,说明了AsyncTask 是一个从架构层面就与 Android 应用模型不匹配。完成指定时期的历史任务后,它的退役是必然要到来的。
🧑💻截至 2026 年,
AsyncTask只是被标记@Deprecated,源码仍在,编译会有警告但不至于立刻报错。新项目肯定是不建议继续使用,旧项目则应该考虑逐渐重写。
三、五条替代方案
3.1 方案一:Executor + Handler
这是最"原始"的替代方案,如果你只想最小改动地替换掉 AsyncTask,用 ExecutorService 承担后台线程,用 Handler(Looper.getMainLooper()) 切回主线程即可。它也是 Android 官方在 AsyncTask 弃用说明中提到的 java.util.concurrent 方案的具体实践。
kotlin
// 创建一个固定大小的线程池
private val executor = Executors.newFixedThreadPool(4)
private val mainHandler = Handler(Looper.getMainLooper())
fun fetchData() {
executor.execute {
// 后台线程:执行耗时操作
val result = doNetworkRequest()
// 切换回主线程更新 UI
mainHandler.post {
updateUi(result)
}
}
}
fun cleanup() {
executor.shutdown() // 页面销毁时关闭线程池
}
优点:不引入任何第三方库;线程池大小、拒绝策略、优先级队列都可以自己控制
缺点:依旧不感知生命周期,旋转屏幕问题照样存在;错误处理、取消、结果合并全部要手写;回调嵌套一多容易导致"回调地狱"。
适用场景:不引入任何第三方库、需要精确控制线程池行为的场景,如自定义线程优先级、队列策略,或者在没有 Kotlin 协程依赖的纯 Java 项目中。
3.2 方案二:RxJava
RxJava 在 2016---2020 年几乎是异步编程的行业标准,它的核心价值不是切线程,而是把异步数据当作可组合的流 :map / flatMap / zip / debounce / retryWhen 这些操作符,能把复杂的时序逻辑声明式地写出来。
虽然 Kotlin 协程在 2026 年已经占据主导地位,但 RxJava 在存量项目中仍然广泛存在。
kotlin
// build.gradle.kts
// implementation("io.reactivex.rxjava3:rxjava:3.1.12")
// implementation("io.reactivex.rxjava3:rxandroid:3.0.2")
class SearchViewModel(private val api: ApiService) : ViewModel() {
private val disposables = CompositeDisposable()
private val querySubject = PublishSubject.create<String>()
val results: LiveData<List<Repo>> = querySubject
.debounce(300, TimeUnit.MILLISECONDS) // 防抖
.filter { it.length >= 2 }
.distinctUntilChanged()
.switchMapSingle { q ->
api.searchRepos(q) // 新输入到来时自动取消上一次请求
.subscribeOn(Schedulers.io())
.onErrorReturnItem(emptyList())
}
.toFlowable(BackpressureStrategy.LATEST)
.toLiveData()
fun onQueryChanged(text: String) = querySubject.onNext(text)
override fun onCleared() {
disposables.clear() // 统一取消,避免泄漏
}
}
优点:操作符生态强大,天然的取消/背压/重试支持;非常适合多个异步源合并、复杂事件流;生态成熟(Room、Retrofit 都有一等支持)。
缺点 :学习曲线陡,栈追踪可读性差;一个 subscribe 忘了加入 CompositeDisposable 就是内存泄漏;对"只是想把一段耗时逻辑挪到后台"这种简单需求来说,属于典型的杀鸡用牛刀。
适用场景 :已有大量 Rx 代码的存量项目、事件流/多源合并密集的业务。新项目不建议选它 ,Kotlin 项目里 Flow 能覆盖绝大多数同类场景。
3.3 方案三:Kotlin 协程
这是 2026 年 Android 异步编程的默认答案 。Kotlin 协程提供了一种用同步代码风格编写异步逻辑的方式,并且与 Android 的生命周期组件深度集成------viewModelScope、lifecycleScope 会随组件销毁自动取消子协程。
kotlin
// 依赖(版本请以官方最新稳定版为准)
// implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
// implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.4")
// implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.4")
interface ApiService {
@GET("search/repositories")
suspend fun searchRepos(@Query("q") query: String): List<Repo> // 直接声明为 suspend
}
class RepoViewModel(private val api: ApiService) : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Idle)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
init {
load("android")
}
fun load(keyword: String) {
viewModelScope.launch { // 自动绑定 ViewModel 生命周期
_uiState.value = UiState.Loading
runCatching { api.searchRepos(keyword) } // 异常不再是「静默吞掉」
.onSuccess { _uiState.value = UiState.Success(it) }
.onFailure { _uiState.value = UiState.Error(it.message ?: "未知错误") }
}
}
}
sealed interface UiState {
data object Idle : UiState
data object Loading : UiState
data class Success(val repos: List<Repo>) : UiState
data class Error(val message: String) : UiState
}
Activity 侧用 repeatOnLifecycle 收集状态,界面不可见时自动停止收集,可见时自动恢复:
kotlin
class RepoActivity : AppCompatActivity() {
private val viewModel: RepoViewModel by viewModels()
private lateinit var binding: ActivityRepoBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityRepoBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnLoad.setOnClickListener { viewModel.load(binding.etKeyword.text.toString()) }
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) { // 关键:生命周期感知的收集
viewModel.uiState.collect { state -> render(state) }
}
}
}
private fun render(state: UiState) = when (state) {
UiState.Idle -> binding.progressBar.visibility = View.GONE
UiState.Loading -> binding.progressBar.visibility = View.VISIBLE
is UiState.Success -> {
binding.progressBar.visibility = View.GONE
binding.tvResult.text = state.repos.joinToString("\n") { it.fullName }
}
is UiState.Error -> {
binding.progressBar.visibility = View.GONE
binding.tvResult.text = state.message
}
}
}
再进一步,用 Flow 直接把【搜索防抖 + 自动取消上一次】写出来,功能和上面 RxJava 那段等价,但更直观:
kotlin
class SearchViewModel(private val api: ApiService) : ViewModel() {
private val queryFlow = MutableStateFlow("")
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
val results: StateFlow<List<Repo>> = queryFlow
.debounce(300) // ⭐防抖
.filter { it.length >= 2 }
.distinctUntilChanged()
.flatMapLatest { q -> flow { emit(api.searchRepos(q)) } } // ⭐新输入自动取消旧请求
.catch { emit(emptyList()) } // 统一兜底
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
fun onQueryChanged(text: String) { queryFlow.value = text }
}

优点 :语法接近同步代码,可读性最好;结构化并发天然解决取消与泄漏;suspend / Flow 已经是一等公民,Retrofit、Room、DataStore 都原生支持;测试友好(runTest + 虚拟时间)。
缺点 :需要理解 CoroutineScope、调度器、repeatOnLifecycle 等概念,用错作用域(比如用 GlobalScope)同样会泄漏;对必须保证执行完成的任务不负责(进程被杀就没了)。
适用场景 :新项目的默认选择------网络请求、数据库读写、文件 IO、UI 状态流。
3.4 方案四:WorkManager
对于需要在后台长时间运行的任务(即使应用已关闭),可以使用 WorkManager。它适用于周期性和链式任务:上传日志、同步数据、清理缓存。
kotlin
// implementation("androidx.work:work-runtime-ktx:2.10.5")
class UploadLogWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) { // 注意是 CoroutineWorker,直接写 suspend
override suspend fun doWork(): Result {
val payload = inputData.getString(KEY_PAYLOAD) ?: return Result.failure()
return try {
uploadLog(payload) // suspend 函数,可安全取消
Result.success()
} catch (e: IOException) {
if (runAttemptCount < 3) Result.retry() else Result.failure() // 指数退避重试
}
}
companion object {
const val KEY_PAYLOAD = "payload"
private const val WORK_NAME = "upload-log"
fun enqueue(context: Context, payload: String) {
val request = OneTimeWorkRequestBuilder<UploadLogWorker>()
.setInputData(workDataOf(KEY_PAYLOAD to payload))
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
WORK_NAME,
ExistingWorkPolicy.KEEP, // 避免重复入队
request
)
}
}
}
优点 :进程被杀、设备重启后依然会补执行;内置约束、重试退避、唯一任务、任务链(beginWith().then())和进度上报;WorkManagerTestInitHelper 可测试。
缺点 :不保证立刻执行,只能保证条件满足时最终执行,因此绝不能拿它做用户正在等的操作;有最小调度间隔(周期任务最短 15 分钟)。
适用场景:日志/埋点上报、数据同步、大文件上传、定期清理。
🎓协程和
WorkManager不是竞争关系,而是分工关系 。协程负责"这一次运行期间怎么优雅地异步",生命周期跟进程绑定;WorkManager负责"这一次没跑完,下次开机还要接着跑",生命周期跟系统调度绑定。
3.5 方案五:ViewModel + LiveData/StateFlow
结合 ViewModel 和 LiveData,可以在视图层中观察数据变化,并在后台处理数据。它解决了 AsyncTask 的最大痛点:生命周期安全的 UI 更新。
ViewModel:数据在配置变更(旋转、切语言)后依然存活,异步结果不再丢失;LiveData:生命周期感知,只有STARTED以上状态才回调,天然免疫 "Activity 已销毁却更新 UI";StateFlow:协程时代的等价物,支持Flow操作符,官方目前推荐在 Kotlin 项目中优先使用 ;LiveData仍在维护,适合 Java 代码或已有LiveData生态。
kotlin
// LiveData 版本(与协程配合)
class ProfileViewModel(private val repo: ProfileRepository) : ViewModel() {
private val _profile = MutableLiveData<UiState>()
val profile: LiveData<UiState> = _profile
fun refresh(userId: String) {
viewModelScope.launch {
_profile.value = UiState.Loading
_profile.value = runCatching { repo.load(userId) }
.fold({ UiState.Success(it) }, { UiState.Error(it.message.orEmpty()) })
}
}
}
// Activity 中观察,无需手动 removeObserver
viewModel.profile.observe(this) { state -> render(state) }
kotlin
// StateFlow 版本
class SearchViewModel(private val repository: SearchRepository) : ViewModel() {
private val _query = MutableStateFlow("")
val query: StateFlow<String> = _query.asStateFlow()
val results: StateFlow<List<SearchResult>> = _query
.debounce(300) // 防抖:300ms 内的连续输入只触发最后一次
.filter { it.length >= 2 }
.flatMapLatest { keyword -> // 取消上一次搜索,只保留最新结果
flow {
emit(repository.search(keyword))
}
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList()
)
fun onQueryChanged(newQuery: String) {
_query.value = newQuery
}
}
StateFlow 与 LiveData 都能解决谁在什么时候能收到回调,区别只在于前者是纯 Kotlin/KMP 友好的流,后者是 Android 专属且带生命周期感知的容器。习惯做法是:Kotlin 项目统一用 StateFlow + repeatOnLifecycle;Java 存量代码继续用 LiveData。

四、方案对比和选型
| 方案 | 抽象层级 | 生命周期感知 | 取消/组合能力 | 保证执行 | 推荐场景 |
|---|---|---|---|---|---|
Executor + Handler |
最低 | ❌ 需手写 | 弱 | ❌ | SDK 内部、极简工具类 |
| RxJava | 中 | 需配合(CompositeDisposable) |
强(操作符丰富) | ❌ | 存量 Rx 项目、复杂事件流 |
| Kotlin 协程 | 中高 | ✅(viewModelScope / repeatOnLifecycle) |
强(结构化并发 + Flow) |
❌ | ⭐新项目默认选择 |
| WorkManager | 高 | 与 UI 解耦 | 中(约束/重试/链式) | ✅ | 必须完成的后台任务 |
ViewModel + LiveData / StateFlow |
架构层 | ✅ | 状态订阅为主 | ❌ | UI 状态持有与订阅 |
五、总结
AsyncTask 的退役不是 Android 开发的一个"小变化",而是一次范式转换。从"手动管理线程和回调"转向"声明式地描述异步操作和生命周期约束"。
如果你对 Android 异步编程有兴趣,可以试试下面的步骤:
- Kotlin 协程必学。这是现代 Android 开发的基础设施,几乎所有 Jetpack 组件都已经原生支持协程。
- 再学 StateFlow 和 Flow。它们取代 LiveData 成为状态管理的标准。
- 了解 WorkManager。当你的任务需要"即使应用关闭也要完成"时,它就是唯一的正确选择。
- Executors + RxJava 作为补充知识。如果你被派去维护老项目,那这两个会对你有所帮助的。
异步编程的本质是管理时间和生命周期 。AsyncTask 的问题在于它试图用一个"静态的、线性的"模型来管理动态的、受系统约束的 Android 生命周期。协程和 Flow 之所以成为标准,是因为它们用结构化并发和声明式数据流,从语言层面解决了这个问题。
参考资料
- Android Developers. "AsyncTask." Android API Reference: https://developer.android.com/reference/android/os/AsyncTask
- Android Developers. "Use Kotlin coroutines with lifecycle-aware components.": https://developer.android.com/topic/libraries/architecture/coroutines
- Android Developers. "Task scheduling." Background work:https://developer.android.com/develop/background-work/background-tasks/persistent
- Android Developers. "Background tasks overview." :https://developer.android.com/develop/background-work/background-tasks
- Android Developers. "Support for long-running workers.": https://developer.android.com/develop/background-work/background-tasks/persistent/how-to/long-running
- RxJava 官方仓库:https://github.com/ReactiveX/RxJava
- kotlinx.coroutines 官方仓库:https://github.com/Kotlin/kotlinx.coroutines
- ViewModel 概览:https://developer.android.com/topic/libraries/architecture/viewmodel