Android 消息机制:什么是消息机制;入门案例+源码流程分析;Handler内存泄漏原因是什么?面试题

26 阅读9分钟

目录

  1. 消息机制是什么?为什么需要了解这个。
  2. 入门案例+源码流程分析;
  3. 面试题

一、消息机制是什么?

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 执行流程

我们来看一张图

图片.png

  1. 我们先看看这个looper,它在创建主线程ActivityThread的时候,就会被new出来,他是一个死循环,也就是说,他运行在主线程。看这里的源码我们就可以知道。

图片.png

图片.png

图片.png

  1. 前面我们也说过,looper的作用就是从MessageQueue里面取出数据,然后给到handler,调用他的handleMessage方法。
  2. looper里面会维护一个MessageQueue,而Handler里面维护了一个looper,所以,就可以handler调用sendMessage往里面放数据,而looper从来里面取数据,我们看看源码。

图片.png

  1. 那么如果我有多个handler,那么looper怎么知道发送的消息,怎么知道应该给谁呢?所以每个 Message 内部通过 target 字段持有对应的 Handler。那么再取出来以后,就在发送数据。我们看看源码。

图片.png

图片.png

图片.png

图片.png

总结:

  1. 每个线程最多有一个 ``Looper,通过 ThreadLocal 实现线程私有存储。
  2. 每个 Looper 内部持有一个唯一的 MessageQueue
  3. Handler 必须绑定一个 Looper(默认绑定当前线程的 Looper)。
  4. 每个 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 时回收​​。

图片.png

那么应该如何处理呢?​ 使用静态内部类 + 弱引用 + 清空所有消息 + 判断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,若未初始化则抛出异常。

图片.png

在子线程中创建 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。

比如有可能多线程操作这个队列,那么数据可能会被覆盖。线程就不安全了。所以为了解决这个问题,入出队列的时候,加锁。

图片.png

3.4 我们使用时,应该如何创建Messages?

都要obtainMessage,为什么不能new Message,因为obtainMessage里面有服用机制,避免频繁new,导致内存抖动。

Android 的 Message 类内部维护了一个​​静态消息池​​(链表结构),用于复用已回收的 Message 对象,减少频繁创建和销毁对象带来的性能开销。

图片.png 进行回收 图片.png

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(如 HandlerLooper)操作消息队列,无需直接接触 epoll

​2. 为什么选择 epoll 而非其他机制?​

其实还有select/poll但对比来比较低效,所以选择的是epoll,举例: 快递柜监控系统:

  • select/poll:快递员每隔 5 分钟检查所有格子(低效)
  • epoll:哪个格子有快递放入,系统立即通知快递员(高效)

3.8 消息屏障是什么?

一种同步控制机制用于确保消息队列中的某些特殊消息(如系统消息、高优先级消息)能被立即处理,不受普通消息积压影响。

核心作用

  • 在异步消息系统中插入“栅栏”,强制消息按特定顺序执行。
  • 确保关键消息(如界面刷新、生命周期事件)不被低优先级消息阻塞。UI message的优先级最高的。,所以这个时候就需要消息屏障。

3.9 Handler之IdleHandler

只有再消息队列无事情可以做的时候,才会执行,所以叫Idlehandler。

ActivityFragment 中,通常在 onCreate()onResume() 中添加 IdleHandler,并在任务完成后移除:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 添加 IdleHandler
    Looper.myQueue().addIdleHandler(() -> {
        // 执行低优先级任务
        preloadSecondaryData();
        return false; // 执行一次后移除
    });
}