目录
- 消息机制是什么?为什么需要了解这个。
- 入门案例+源码流程分析;
- 面试题
一、消息机制是什么?
Android 的消息机制基于 Handler、Looper、MessageQueue 实现,用于同一进程内的线程间通信。其核心目的是将任务切换到指定线程执行(如子线程更新 UI)。
那么我们为什么需要学习这个呢?我们都知道UI的更新,需要放到主线程的,但耗时任务(如网络请求)需在子线程完成,那么怎么更新UI呢?所以呢,就需要通过Handler 切回主线程更新 UI。
很多框架的底层,子线程切换到主线程,都是使用到了Handler,比如RxJava,Retrofit等等。
那么Handler底层是如何实现的呢?后面我们在看看。
二、入门案例+流程分析
// 主线程创建 Handler(默认关联主线程 Looper)
Handler mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 处理消息(例如更新 UI)
if (msg.what == 1) {
textView.setText("Received message");
}
}
};
// 子线程发送消息到主线程
new Thread(() -> {
// 发送 Message
Message message = Message.obtain();
message.what = 1;
mainHandler.sendMessage(message);
}).start();
2.1 我们需要了解Handler的几个核心组件
-
Handler:消息的发送者和处理者。
-
Looper:消息循环器,负责从消息队列(MessageQueue)中取出消息并分发给 Handler。
-
MessageQueue:消息队列,存储待处理的消息(Message)。
2.2 执行流程
我们来看一张图
- 我们先看看这个looper,它在创建主线程ActivityThread的时候,就会被new出来,他是一个死循环,也就是说,他运行在主线程。看这里的源码我们就可以知道。
- 前面我们也说过,looper的作用就是从MessageQueue里面取出数据,然后给到handler,调用他的handleMessage方法。
- looper里面会维护一个MessageQueue,而Handler里面维护了一个looper,所以,就可以handler调用sendMessage往里面放数据,而looper从来里面取数据,我们看看源码。
- 那么如果我有多个handler,那么looper怎么知道发送的消息,怎么知道应该给谁呢?所以每个
Message
内部通过target
字段持有对应的Handler
。那么再取出来以后,就在发送数据。我们看看源码。
总结:
每个线程最多有一个 ``Looper
,通过ThreadLocal
实现线程私有存储。- 每个
Looper
内部持有一个唯一的MessageQueue
。 Handler
必须绑定一个Looper
(默认绑定当前线程的 Looper)。- 每个
Message
内部通过target
字段持有对应的Handler
。
Looper 为什么可以跨线程访问呢?他不是在主线程创建的?因为Looper线程共享的,存储到了Threadlocal里面,可以通过当前线程名取出来。而handler就是从这里取出来,所以就持有了looper,需要注意,如果handler不是在主线程创建的,那么没有对应的looper,需要自行创建。
三、面试题
3.1 Handler内存泄漏原因?
我们看看代码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
....
Handler mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 处理消息(例如更新 UI)
if (msg.what == 1) {
textView.setText("Received message");
}
//为什么handler里面可以访问setdata呢?就是因为他持有了外部类的引用
//当 Handler 是非静态内部类时,隐式持有 Activity 的强引用:
MainActivity.this.setdata();
}
};
// 子线程发送消息到主线程
new Thread(() -> {
// 发送 Message
Message message = Message.obtain();
message.what = 1;
mainHandler.sendMessage(message);
}).start();
}
public void setdata(){}
也就是这里,handler持有了Activity的引用,前面我们就知道Message持有了Handler的引用。
假如说,这个时候,我们是一个延迟发送message的命令,比如延迟了10秒。
mHandler.sendEmptyMessageDelayed(1, 10000);
但这个时候,Activity销毁了,并且你没有调用removeCallbacksAndMessages(null)
清空消息,那么就会出现内存泄漏了,长生命周期持有短生命周期Activity。就会导致内存泄漏,gc回收不了。需要等到消息处理完成后,target
被置空 → 引用链断开 → 下次 GC 时回收。
那么应该如何处理呢?
使用静态内部类 + 弱引用 + 清空所有消息 + 判断activity是否存活
public class MainActivity extends Activity {
private SafeHandler mHandler;
// 静态内部类 + 弱引用
private static class SafeHandler extends Handler {
private final WeakReference<MainActivity> mActivityRef;
SafeHandler(MainActivity activity) {
mActivityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
//`Activity` 销毁后,弱引用 (`WeakReference`) 失效 → `mActivityRef.get()` 返回 `null`。
MainActivity activity = mActivityRef.get();
if (activity != null && !activity.isDestroyed()) {
// 处理消息(例如更新 UI)
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new SafeHandler(this);
mHandler.sendEmptyMessageDelayed(1, 10000);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null); // 清空所有消息
}
}
-
弱引用:
WeakReference
允许Activity
被 GC 回收。Activity 的onDestroy()
调用后,系统会解除对它的强引用(如从 Activity 栈中移除)。若WeakReference
是唯一指向 Activity 的引用 → GC 会回收 Activity 内存。GC会将被弱引用指向的对象,直接加入回收队列。 -
消息清理:在
onDestroy()
中调用removeCallbacksAndMessages(null)
,确保MessageQueue
不再持有消息。 -
空指针检查:在
handleMessage()
中验证Activity
是否存活。 -
static为什么可以呢?因为静态类会比不具备持有外部类的引用。**
3.2为何主线程可以直接new handler?而子线程不可以如果想要在子线程中new handler要做什么准备?
主线程(即 UI 线程)在应用启动时,系统会自动初始化其 Looper 和 MessageQueue,因此可以直接创建 Handler。
子线程默认没有初始化 Looper 和 MessageQueue,直接创建 Handler 会抛出异常。Handler 构造函数会检查当前线程的 Looper,若未初始化则抛出异常。
在子线程中创建 Handler 需手动初始化 Looper 并启动消息循环
new Thread(() -> {
// 1. 初始化 Looper
Looper.prepare();
// 2. 创建 Handler
Handler subHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == EXIT_LOOP) { // 定义退出消息标识
Looper.myLooper().quitSafely(); // 安全退出循环
} else {
// 处理其他消息
}
}
};
// 3. 发送测试消息
subHandler.sendEmptyMessage(1);
// 4. 启动消息循环
Looper.loop();
}).start();
当子线程任务完成后,需主动退出 Looper 循环,否则线程会因 Looper.loop()
的无限循环而无法终止
// 退出 Looper 循环(安全方式)
Looper.myLooper().quitSafely();
// 强制退出(可能丢失未处理消息)
Looper.myLooper().quit();
3.3 存在多个handler往messageQueue中添加数据,可能各个handler在不同的线程,那么他是如何保证线程安全?
存取的时候,都使用了synchronized。
比如有可能多线程操作这个队列,那么数据可能会被覆盖。线程就不安全了。所以为了解决这个问题,入出队列的时候,加锁。
3.4 我们使用时,应该如何创建Messages?
都要obtainMessage,为什么不能new Message,因为obtainMessage里面有服用机制,避免频繁new,导致内存抖动。
Android 的 Message
类内部维护了一个静态消息池(链表结构),用于复用已回收的 Message
对象,减少频繁创建和销毁对象带来的性能开销。
进行回收
3.5 handler消息阻塞为什么不会导致ANR问题?looper死循环为什么不会导致应用卡死
其实和ANR无关。
-
无消息时休眠:当
MessageQueue
为空时,Looper
通过nativePollOnce()
进入 epoll 等待状态(底层基于 Linux 的epoll
机制),释放 CPU 资源,线程处于休眠状态。 -
新消息唤醒:当有新消息加入队列时,通过
nativeWake()
唤醒线程,继续处理消息。
消息处理是逐个执行的,只要每个消息处理时间足够短,即使循环持续运行,也不会触发 ANR。无消息时线程休眠,不会消耗资源。
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(() -> {
// 模拟耗时操作(超过 5 秒)
SystemClock.sleep(6000);
});
只有在单个消息处理耗时过长,只有在主线程在处理该消息时被阻塞,无法响应输入事件或 Activity
生命周期回调 → 触发 ANR。
主线程的 Looper
循环是 Android 应用的事件驱动核心:
- 处理 UI 更新:如
View
的绘制、触摸事件分发。 - 响应系统事件:如
Activity
生命周期回调、BroadcastReceiver
。 - 任务调度:通过
Handler
调度异步任务到主线程。
消息处理也有优先级
- 即时性消息优先:
MessageQueue
通过when
字段按时间顺序排列消息,但系统会插入高优先级消息(如VSYNC
触发的 UI 绘制消息),避免界面卡顿。 - 同步屏障(Sync Barrier):
通过postSyncBarrier()
插入屏障消息,优先处理异步消息(如Choreographer
的渲染消息)。
对比子线程的 Looper 循环
子线程的 Looper
循环不会导致应用卡死,原因:
- 不处理 UI 事件:子线程的阻塞不会影响主线程响应能力。
- 任务完成后主动退出:子线程可通过
Looper.quitSafely()
终止循环,释放资源。
3.6 子线程中维护的looper,消息队列无消息的时候的处理方案是什么?
在子线程中,当 Looper
维护的 MessageQueue
无消息时,线程会进入阻塞状态,通过 epoll
机制释放 CPU 资源,避免空转。
3.7 前面我们提到的epoll是什么?
在 Android 消息机制中,epoll
用于实现 MessageQueue
的阻塞-唤醒机制,具体应用如下:
1. 消息队列的阻塞与唤醒
-
当队列无消息时:
MessageQueue.next()
调用nativePollOnce()
,底层通过epoll_wait()
使线程休眠,释放 CPU 资源。 -
当新消息到达时:
MessageQueue.enqueueMessage()
调用nativeWake()
,底层通过向epoll
监听的 FD 写入数据,唤醒线程处理消息。Android 将
epoll
的复杂性封装在 JNI 层 和 系统库 中,开发者只需通过高层 API(如Handler
、Looper
)操作消息队列,无需直接接触epoll
。
2. 为什么选择 epoll
而非其他机制?
其实还有select
/poll
但对比来比较低效,所以选择的是epoll,举例:
快递柜监控系统:
select
/poll
:快递员每隔 5 分钟检查所有格子(低效)epoll
:哪个格子有快递放入,系统立即通知快递员(高效)
3.8 消息屏障是什么?
一种同步控制机制,用于确保消息队列中的某些特殊消息(如系统消息、高优先级消息)能被立即处理,不受普通消息积压影响。
核心作用:
- 在异步消息系统中插入“栅栏”,强制消息按特定顺序执行。
- 确保关键消息(如界面刷新、生命周期事件)不被低优先级消息阻塞。UI message的优先级最高的。,所以这个时候就需要消息屏障。
3.9 Handler之IdleHandler
只有再消息队列无事情可以做的时候,才会执行,所以叫Idlehandler。
在 Activity
或 Fragment
中,通常在 onCreate()
或 onResume()
中添加 IdleHandler,并在任务完成后移除:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 添加 IdleHandler
Looper.myQueue().addIdleHandler(() -> {
// 执行低优先级任务
preloadSecondaryData();
return false; // 执行一次后移除
});
}