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,就当它是个历史遗迹吧,了解即可,别在生产环境用了。

Android异步处理核心机制 主线程(UI线程) Looper.prepareMainLooper() Handler.sendMessage() handleMessage() 更新UI 子线程(工作线程) new Thread() / 线程池 执行耗时操作 发送Message到主线程 Message Looper.loop() 消息循环 从MessageQueue中取出消息,分发给Handler处理 AsyncTask(已废弃) 缺陷:生命周期问题、串行执行、内存泄漏 替代方案:Kotlin协程 / 线程池+Handler 历史演进

公众号:蓝海资料掘金营,微信deep3321