14、异步处理:Thread、Handler、Looper、Message机制,以及AsyncTask的用法与缺陷
异步处理,说白了就是让App在后台干活,别卡住主线程。
你想想看,如果网络请求、文件读写、图片解码这些耗时操作都放在主线程,用户滑动列表时就会卡顿,甚至出现ANR。我早期做开发时就踩过这个坑——一个网络请求没处理好,直接导致应用无响应,被用户骂惨了。
14.1 为什么需要异步?
Android的主线程也叫UI线程。它负责处理用户交互、绘制界面。如果主线程被阻塞超过5秒,系统就会弹出ANR对话框。
所以,耗时操作必须扔到子线程去执行。但子线程不能直接更新UI,这就引出了Handler机制。
核心原则:主线程负责UI更新,子线程负责耗时任务。两者通过Handler+Looper+Message进行通信。
14.2 Thread、Handler、Looper、Message 四件套
这四兄弟是Android异步通信的基石。我习惯把它们比作一个工作流水线:
- Thread:干活的人,负责执行耗时任务
- Looper:流水线的传送带,不断循环取消息
- Message:传送带上的包裹,携带数据
- Handler:分拣员,负责发送和接收包裹
主线程默认自带Looper,所以我们可以直接创建Handler。但子线程需要手动调用Looper.prepare()和Looper.loop()。
// 主线程中使用Handler
Handler mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
// 这里更新UI
textView.setText(msg.obj.toString());
}
};
// 子线程发送消息
new Thread(() -> {
// 模拟耗时操作
String result = doHeavyWork();
Message msg = Message.obtain();
msg.obj = result;
mainHandler.sendMessage(msg);
}).start();
我的习惯:尽量使用Message.obtain()而不是new Message(),这样可以复用消息对象,减少内存分配。我在优化一个列表应用时,就靠这个技巧减少了30%的GC次数。
14.3 Handler的几种用法
Handler不只是用来发消息,它还有几个常用方法:
- post(Runnable):直接投递一个任务到主线程执行
- sendMessage(Message):发送消息,由handleMessage处理
- sendMessageDelayed:延迟发送,常用于倒计时
- removeCallbacks:取消未执行的任务,防止内存泄漏
// 使用post方式更新UI
new Thread(() -> {
String data = fetchData();
handler.post(() -> {
// 直接更新UI,不用处理Message
textView.setText(data);
});
}).start();
我曾经踩过的坑:在Activity销毁时没有移除Handler的回调,导致Activity泄漏。后来我养成了一个习惯——在onDestroy中调用handler.removeCallbacksAndMessages(null)。
14.4 AsyncTask:曾经的宠儿,如今的弃子
AsyncTask是Android早期提供的异步工具类。它封装了线程和Handler,让开发者可以轻松地在后台执行任务并更新UI。
private class DownloadTask extends AsyncTask<String, Integer, String> {
@Override
protected void onPreExecute() {
// 主线程执行,显示进度条
progressBar.setVisibility(View.VISIBLE);
}
@Override
protected String doInBackground(String... urls) {
// 子线程执行,不能更新UI
for (int i = 0; i < 100; i++) {
publishProgress(i); // 触发onProgressUpdate
}
return "下载完成";
}
@Override
protected void onProgressUpdate(Integer... values) {
// 主线程执行,更新进度
progressBar.setProgress(values[0]);
}
@Override
protected void onPostExecute(String result) {
// 主线程执行,任务结束
textView.setText(result);
progressBar.setVisibility(View.GONE);
}
}
// 使用
new DownloadTask().execute("https://example.com/file");
14.5 AsyncTask的四大缺陷
嗯,这里要注意。AsyncTask虽然用起来方便,但坑也不少。我在项目中吃过不少亏:
| 缺陷 | 说明 | 我的经历 |
|---|---|---|
| 生命周期问题 | Activity销毁后,AsyncTask仍在执行,导致UI更新崩溃 | 曾经在Fragment切换时,AsyncTask还在跑,直接Crash |
| 串行执行 | Android 3.0后默认串行,多个任务会排队 | 下载图片时,后面的任务要等前面的完成,用户体验差 |
| 内存泄漏 | 内部类持有Activity引用,Activity无法被回收 | 我排查过一个OOM问题,就是AsyncTask导致的 |
| 结果丢失 | 屏幕旋转后,AsyncTask的结果无法传递给新的Activity | 用户旋转屏幕,下载进度直接归零,很恼火 |
官方态度:AsyncTask在Android 11(API 30)中被标记为废弃。官方推荐使用Kotlin协程或Java的线程池+Handler组合。
14.6 现代替代方案
如果你还在用AsyncTask,我建议尽快迁移。目前主流的异步方案有:
- Kotlin协程:最推荐,轻量级,支持结构化并发
- RxJava:响应式编程,适合复杂的数据流操作
- 线程池+Handler:经典方案,可控性强
- WorkManager:适合后台任务,即使App退出也能执行
// 线程池+Handler的推荐写法
ExecutorService executor = Executors.newFixedThreadPool(4);
Handler mainHandler = new Handler(Looper.getMainLooper());
executor.execute(() -> {
String result = doHeavyWork();
mainHandler.post(() -> {
// 安全更新UI
textView.setText(result);
});
});
我的建议:新项目直接用Kotlin协程。如果是维护老项目,至少把AsyncTask替换成线程池+Handler,避免生命周期问题。
14.7 避坑指南
最后分享几个我实战中总结的要点:
- Handler内存泄漏:使用静态内部类+弱引用,或者在onDestroy中清理
- 线程数量控制:不要无限制创建线程,使用线程池管理
- UI更新检查:在子线程更新UI前,检查Activity是否已销毁
- 消息队列积压:Handler处理不过来时,消息会堆积,导致UI延迟
异步处理是Android开发的必修课。搞懂了Handler机制,你就掌握了Android的命脉。至于AsyncTask,就当它是个历史遗迹吧,了解即可,别在生产环境用了。