19、Android多线程机制:AsyncTask、HandlerThread、IntentService、线程池与协程
多线程,说白了就是让App同时干好几件事。你想想看,如果所有任务都挤在主线程里,用户点个按钮就得等网络请求回来才能动,那体验得多糟糕。我早期做Android开发时,就吃过这个亏——一个图片加载没处理好,直接ANR,用户差评如潮。
Android的多线程方案,从最早的AsyncTask,到HandlerThread、IntentService,再到线程池和现在的协程,每个方案都有它的适用场景。今天我就带你把这些东西捋清楚。
19.1 AsyncTask:曾经的宠儿,如今的弃子
AsyncTask是Android早期提供的轻量级异步任务工具。它封装了线程和Handler,让你在后台做耗时操作,然后轻松更新UI。
class MyTask extends AsyncTask<Void, Integer, String> {
@Override
protected void onPreExecute() {
// 主线程,准备工作
}
@Override
protected String doInBackground(Void... params) {
// 后台线程,做耗时操作
publishProgress(50); // 更新进度
return "完成";
}
@Override
protected void onProgressUpdate(Integer... values) {
// 主线程,更新进度条
}
@Override
protected void onPostExecute(String result) {
// 主线程,更新UI
}
}
嗯,这里要注意。AsyncTask在Android 11已经被标记为废弃了。为什么?
- 生命周期问题:Activity销毁后,AsyncTask还在跑,容易内存泄漏。我在项目中遇到过,用户旋转屏幕,Activity重建,结果AsyncTask还在更新旧的UI,直接崩溃。
- 串行执行坑:Android 1.6到3.0是并行,3.0之后默认串行。如果你同时启动多个AsyncTask,它们会排队执行,不是你想象的那样一起跑。
- 结果丢失:屏幕旋转后,AsyncTask的结果无法传递给新的Activity。
19.2 HandlerThread:带消息循环的工作线程
HandlerThread本质上就是一个自带Looper的Thread。它适合那种需要反复执行任务、且任务之间有顺序依赖的场景。
HandlerThread handlerThread = new HandlerThread("WorkThread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
// 这里已经在子线程了
// 处理耗时任务
}
};
// 发送任务
handler.sendEmptyMessage(1);
我个人习惯用HandlerThread来处理一些后台定时任务,比如本地数据库的批量写入。你想想看,如果每次写数据库都开一个新线程,那线程开销得多大。用HandlerThread,所有写操作都在同一个线程里排队执行,既省资源又保证顺序。
19.3 IntentService:自动停止的后台服务
IntentService是Service的子类,它内部使用HandlerThread来处理任务。任务执行完后,Service会自动停止,不需要你手动调用stopSelf()。
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 这里在子线程执行
// 处理耗时任务
}
}
IntentService最大的好处就是省心。你只管发Intent,它帮你排队执行,执行完自动销毁。我在做文件下载功能时用过它,用户点击下载,发个Intent,后台默默下载,下载完Service自己停掉,非常干净。
不过要注意,IntentService在Android 8.0之后也被标记为废弃了。官方推荐用JobIntentService或者直接使用WorkManager。
19.4 线程池:掌控全局的调度器
线程池是Java提供的线程管理工具。它解决了频繁创建和销毁线程的开销问题。Android中常用的线程池有四种:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| FixedThreadPool | 固定线程数,超出的任务排队 | 控制并发数,比如数据库连接池 |
| CachedThreadPool | 按需创建,空闲线程60秒回收 | 大量短时任务,比如网络请求 |
| ScheduledThreadPool | 支持定时和周期性任务 | 定时检查、心跳包 |
| SingleThreadPool | 只有一个线程,任务串行执行 | 需要顺序执行的场景 |
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.execute(new Runnable() {
@Override
public void run() {
// 执行耗时任务
}
});
我建议你在项目里尽量使用线程池,而不是直接new Thread()。为什么呢?因为线程池可以复用线程,减少开销。我曾经优化过一个项目,把所有的new Thread()替换成线程池,内存占用直接降了30%。
19.5 协程:Kotlin带来的革命
协程是Kotlin提供的轻量级并发方案。它不是线程,而是一种可以在单线程内实现并发的能力。说白了,协程就是让代码看起来像同步的,但实际上是异步执行的。
// 在ViewModel中启动协程
viewModelScope.launch {
val result = withContext(Dispatchers.IO) {
// 在IO线程执行网络请求
apiService.fetchData()
}
// 自动回到主线程更新UI
textView.text = result
}
协程最大的优势是什么?结构化并发。你不需要手动管理线程的生命周期,协程会自动处理。比如viewModelScope,当ViewModel被清除时,所有协程自动取消,不会内存泄漏。
我个人现在做新项目,基本都用协程了。它比线程池更轻量,一个线程可以跑成千上万个协程。而且代码可读性极强,没有回调地狱。
19.6 知识体系总览
下面这张图,是我梳理的Android多线程知识体系。你可以把它当作一个导航图,遇到多线程问题,先看这张图,找到对应的方案。
从这张图可以看出,Android多线程方案经历了从重量级到轻量级的演变。AsyncTask和IntentService已经逐渐被淘汰,线程池依然稳定可靠,而协程是未来的趋势。
最后说一句,没有最好的方案,只有最合适的方案。如果你的项目还在用Java,线程池是你的不二选择。如果你已经切换到Kotlin,那就拥抱协程吧。
公众号:蓝海资料掘金营,微信deep3321