引言
Android 系统基于 Java 的多线程机制,但又在其基础上发展出了许多特有的多线程和异步通信机制。这些机制旨在解决 Android 开发中常见的并发问题,特别是 UI 更新、网络请求、数据库操作等耗时任务的处理。由于 Android ANR 问题的存在,Android的多线程非常常用。
介绍
Java多线程
在 Android 中,Java 原生的多线程机制依然可用,比如:
Thread
类:用于创建新线程执行任务Runnable
接口:定义线程要执行的任务ExecutorService
:线程池管理类,提供更高效的线程管理Future
和Callable
:支持返回值的异步任务
这些类的使用方式和Java下的相同。
Handler (句柄)
handler
是 Android 开发中用于线程间通信的核心机制之一,像是一种特殊的消息队列。其基于特有的消息循环机制来进行异步通信。它的核心功能是:
- 发送消息(Message) :将任务封装为 Message 对象,排入消息队列。
- 处理消息:在指定线程中执行 Message 对应的回调逻辑。
- 异步调度:通过延迟发送、定时发送等方式控制任务执行时机。
消息循环机制
消息循环机制一共涉及以下几个关键类:
类名 | 作用 |
---|---|
Handler |
发送和处理消息的对象 |
Message |
消息载体,携带要传递的数据 |
MessageQueue |
消息队列,用来存放由 Handler 发送的消息 |
Looper |
消息循环器,负责从 MessageQueue 中取出消息并分发给对应的 Handler 处理 |
Thread |
线程,每个拥有 Looper 的线程都具有一个消息循环 |

Message
Message
是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间传递数据。其可以使用what
指定消息类型,除此之外还可以使用arg1
和arg2
字段来携带一些整型数据,使用obj
字段携带一个Object
对象。
Handler
Handler
顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用Handler
的sendMessage()
方法、post()
方法等,而发出的消息经过一系列地辗转处理后,最终会传递到Handler
的handleMessage()
方法中。
MessageQueue
MessageQueue
是消息队列的意思,其数据结构为一个先进先出的队列,它主要用于存放所有通过Handler
发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue
对象。其基于无锁设计,通过 native
层的epoll
机制实现阻塞式获取消息,避免 CPU 空转。
Looper
Looper
是每个线程 中的MessageQueue
的管家,调用Looper
的loop()
方法后,就会进入一个无限循环 当中,然后每当发现MessageQueue
中存在一条消息时,就会将它取出,并传递到Handler
的handleMessage()
方法中。
一个线程可以拥有多个 Handler
,但一个线程只能有一个 Looper
对象,一个Looper
对象也仅有一个相对应的MessageQueue
多个Handler
可共享一个Looper
与MessageQueue
;这些不同的 Handler
可以根据 msg.what
、msg.obj
等字段区分处理不同类型的消息。
其中主线程(UI 线程)在应用启动时自动创建了Looper
,子线程需要手动调用 Looper.prepare()
和 Looper.loop()
来开启消息循环。
java
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); // 准备 Looper
Handler handler = new Handler();
Looper.loop(); // 启动消息循环。此时会陷入死循环
}
}).start();
会不会有队头阻塞问题?
小结
在使用handler时,其流程为:Handler(发送msg到绑定looper) → (MessageQueue → Looper) → Handler 对应回调处理
。主要分为以下两种模型
- 单线程下,各类之间使用同一线程的Handler通信。用于分发事件。
css
类 A 调用主线程 handler.sendMessage()
↓
消息进入主线程的 MessageQueue
↓
主线程 Looper 取出消息
↓
主线程 Handler.handleMessage() 被调用 → 类 B 或其他模块处理逻辑
- 多线程下,子线程持有主线程的Handler向主线程通信。
plain
子线程任务完成
↓
使用主线程的 handler.sendMessage()
↓
消息进入主线程的 MessageQueue
↓
主线程 Looper 取出消息
↓
主线程的 Handler.handleMessage() 被调用 → 安全更新 UI
AsyncTask(异步任务)
已被 Android 标记遗弃,可用但更推荐Kotlin 协程或 RxJava
AsyncTask
是 Android 提供的一个轻量级异步任务类,用于在后台线程执行任务并在主线程更新 UI,其继承自 Object
,本质上是对 Thread + Handler
的一个封装,适用于短时间的后台任务,比如网络请求、数据库查询等。
其通常由主线程启动 ,后台线程执行 ,并将结果返回给主线程
使用
AsyncTask是一个抽象类,所以如果想使用它,就必须创建一个子类去继承它。在继承时可以为AsyncTask类指定三个泛型参数,并重写相关方法。
三个泛型
Params
:任务参数类型Progress
:执行进度类型Result
:结果类型
相关方法
方法名 | 线程 | 描述 |
---|---|---|
onPreExecute() |
主线程 | 执行前准备工作(如显示进度条) |
doInBackground(Params...) |
子线程 | 执行耗时操作(不能更新 UI) |
onProgressUpdate(Progress...) |
主线程 | 更新进度(通过 publishProgress() 触发) |
onPostExecute(Result) |
主线程 | 耗时任务完成后回调,用于更新 UI |
onCancelled(Result) |
主线程 | 当任务被取消时调用 |
前三个从上往下依次执行,第四个以及第五个选一个。
java
public class DownloadTask extends AsyncTask<String, Integer, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();
// 显示进度条或提示
progressBar.setVisibility(View.VISIBLE);
}
@Override
protected String doInBackground(String... urls) {
// 子线程执行耗时任务
for (int i = 0; i < urls.length; i++) {
if (isCancelled()) {
break; // 如果任务被取消,则退出循环
}
publishProgress(i);
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
return "failed";
}
}
return "success";
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// 主线程更新进度条
progressBar.setProgress(values[0]);
}
@Override
protected void onPostExecute(String result) {
// 主线程更新 UI
textView.setText(result);
progressBar.setVisibility(View.GONE);
}
@Override
protected void onCancelled(String result) {
// 任务被取消时的逻辑。downloadTask.cancel(true)
Toast.makeText(context, "Download cancelled", Toast.LENGTH_SHORT).show();
}
}
java
new DownloadTask().execute(url);
EventBus(事件总线)
Android下面著名的异步消息组件,是一个基于发布/订阅模式的事件总线框架,简化了组件间的通信。使用方式非常简单。使用implementation "org.greenrobot:eventbus:3.2.0"
启用
java
@Override
protected void onStart() {
super.onStart();
// 把自己绑定到 消息总线 上
EventBus.getDefault().register(this);
}
@Override
protected void onStop() {
super.onStop();
// 解绑
EventBus.getDefault().unregister(this);
}
发送
java
// 定义事件类,用于承载数据
public class MessageEvent {
public String message;
public MessageEvent(String message) {
this.message = message;
}
}
java
// 发送普通事件
EventBus.getDefault().post(new MessageEvent("Hello, EventBus!"));
java
// 发送粘滞事件
EventBus.getDefault().postSticky(new MessageEvent("I'm sticky"));
接收
java
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
// 在主线程接收事件
textView.setText(event.message);
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
// 即使订阅晚于发送也能接收到
}
线程模型(ThreadMode)
EventBus 支持五种线程模型,控制事件在哪个线程中被处理:
ThreadMode | 描述 |
---|---|
POSTING (默认) |
事件在发送线程中执行(可能是主线程也可能是子线程) |
MAIN |
保证在主线程执行,适合更新 UI |
MAIN_ORDERED |
同 MAIN,但事件按顺序排队执行(API 28+) |
BACKGROUND |
如果事件是从主线程发送 的,那么订阅方法会在后台线程 执行; 如果事件是从子线程发送 的,订阅方法就在该子线程执行 |
ASYNC |
总是在异步线程中执行(新开一个线程) |
粘滞事件
- 持久性 :粘滞事件发布后会一直保存 在 EventBus 中,直到被明确移除或被新的粘滞事件覆盖。
- 自动分发 :当有新的订阅者订阅了某个类型的粘滞事件时,
EventBus
会自动将该类型的最新粘滞事件发送给这个订阅者。 - 单一实例 :对于每种类型的粘滞事件,
EventBus
只会保存最近一次发布的那个事件。
从上面可以得知,如果滥用粘滞事件,可能会导致内存不足
java// 移除指定粘滞事件 EventBus.getDefault().removeStickyEvent(MessageEvent.class); // 或者移除所有粘滞事件 EventBus.getDefault().removeAllStickyEvents();