Android进阶知识树——Android Handler消息机制

本文深入解析Android中的Handler机制,包括其工作原理、消息传递流程、关键组件Looper与MessageQueue的作用及交互过程。并通过源码分析解答了为何子线程直接创建Handler会抛出异常等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、概述

在安卓程序启动时,会默认在主线程中 运行程序,那如果执行一些耗时的操作则UI就会处于阻塞状态,出现界面卡顿的现象,再者用户的多种操作,系统是如何做到一一处理的,系统又是如何管理这些任务的,答案这就是今天的主题Android的消息机制;

  •  Android处理消息的方式——handler、Looper 和 MessageQueue
  1. Handler :负责将任务添加到队列,执行结束后在主线程执行UI操作
  2. Looper :为绑定的线程开启循环消息队列,并获取消息
  3. MessageQueue :任务队列,保存发送的消息
  • 在进行源码分析之前先提出两个常见的疑问
  1. 为什么子线程直接创建Handler会抛出异常?

  2. Handler究竟执行在哪个线程中?

2、源码分析

对于Handler的使用方法此处不做介绍,在使用是只要注意避免内存泄漏需创建静态Handler,此处从执行过程和原理的角度简单分析Handler的源码,首先大家都知道在主线程中可以直接使用Handler,而在子线程中不可以,有没有想过为什么呢?现在就先看看主线程究竟有什么不同吧,从头看安卓的入口方法ActivityThread.main()

​
public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
      ...
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

​
  • 上述代码看出在程序启动后,执行三个步骤:
  1. 调用Looper.prepareMainLooper();创建消息循环Looper
  2. 使用 thread.getHandler(); 获取UI线程的Handler
  3. 使用 Looper.loop()开启消息循环

Handler与消息队列关联,而消息队列被封装在Looper中,每个Looper关联一个线程,所以Handler的消息大致为handler作为一个消息处理器,将消息传递到消息队列中,线程中的Looper逐个取出消息并执行,此处的线程即为UI线程,

  • Handler最基本的用法是创建Handler 发送Message到队列,现在看看创建Handler时都做了哪些事
​
 public Handler(Callback callback, boolean async) {
        
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

​
  1. 调用Looper.myLooper():获取Looper对象,如果为空则抛出异常,然后调用lopper.mQuene开启消息队列
  • 查看myLooper()方法 
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
  1. 在myLooper中看出,Looper从sThreadLocal中获取,sThreadLocal是静态创建实例,它的作用就是以线程为单位保存数据,那此处的Looper对象什么时候设置进去的呢?另外从这也可以看出来,只要sThreadLocal没有Looper的实例就会抛出异常,此时sThreadLocal就是在当前的执行线程中,换句话说只要Handler所在的线程中没有Looper的实例就会异常;
  • 接下来看看UI线程开始时创建的Looper对象的方法prepareMainLooper()
​public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
  • prepareMainLooper() 调用了prepare()方法:
​private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }


private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed); // 创建消息队列MessageQueue
        mThread = Thread.currentThread(); // 保存当前线程
    }
  1. 看到了 sThreadLocal.set(new Looper(quitAllowed)),此处创建Looper对象然后保存进sThreadLocal中,那此时在当前线程中已经初始化了Looper对象,到此是不是明白了上面的sThreadLocal.get()获取的Looper是哪里的了?
  2. 在创建Looper对象的同时,系统会实例化一个MessageQueue对象,作为消息对列保存发送的消息事件;

到这里我们介绍了Handler 和 Looper的创建,以及各自对线程的关联,下面查看如何启动消息循环获取任务并执行,在入口方法的最后调用了Loop.loop()方法开启消息队列循环,下面查看Looper.loop()方法:

public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
for (;;) { //使用死循环获取消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }
...
 msg.target.dispatchMessage(msg);
 msg.recycleUnchecked();
}
  1. 调用myLooper()获取Looper的对象,从Looper对象中获取消息队列MessageQueue,然后在循环内依次调用MessageQueue.next()依次取出其中的Message,注意这里是个死循环;
  2. 调用Messagemsg.target.dispatchMessage(msg)传递消息;
  • 查看Message的源码便知此处调用的target实际是Handler的对象,说明绕了一圈最后还是Handler的dispatchMessage(msg)处理逻辑
Handler target;
Runnable callback;
Message next;
  • Handler的dispatchMessage(msg)方法
​public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
private static void handleCallback(Message message) {
        message.callback.run();
    }

总结一下:looper从MessageQuene中依次取出Message,然后调用Msg绑定的Handler的dispatchMessage(msg)处理,dispatchMessage()中依次判断执行:

  1. 先判断Message的Runable对象callback是否为null,若不为空使用handleCallback(),在handleCallback直接调用Runnable.run()方法执行程序;
  2. 若callback为空判断mCallBack,若mCallBack不为空则调用mCallBack.handleMessage()处理事件,这里的mCallBack是Callback接口对象,在创建Handler时传入的回调方法,不经常使用但某些条件下需要拦截或代理Handler时可以使用;
  3. 如果上面两者都为null,则执行handleMessage(),handlerMessage()在Handler中默认为空实现,一般在创建Handler()都需要重写的handlerMessage(),然后处理相应的事件;

到此Android中Handler的消息处理,从创建Handler、Looper和MessageQueue到消息的传递和事件的处理都介绍完了,下面我们看看使用时如何发送消息到MessageQueue以及上述的callback、mCallBack什么情况下为空?

  • 创建的Handler后发送消息方法:sendMessage(Message msg)、post(Runnable r)
  1. sendMessage():发送Message消息;
public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

public boolean sendMessageAtTime(Message msg, long uptimeMillis) { //设置延迟发送时间
        MessageQueue queue = mQueue;
        return enqueueMessage(queue, msg, uptimeMillis);
    }

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;   // 此处设置target为Handler对象
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis); // 添加Message
    }

上面的执行过程代码从上到下一目了然,在enqueueMessage()中设置了Message.target即为调用方法的Handler,所以在调用dispatchMessage()时,会调用Handler的handleMessage()处理,然后根据当前时间和发送时设置的延迟时间计算时间发送的准确时间,然后将消息Message保存到MessageQueue中;

  • post(Runnable r)
public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain(); // 创建Message封装Runnable
        m.callback = r;   // 设置Message的callback为r
        return m;
    }
  1. 调用getPostMessage()设置Message中的callback,所以callback就是发送的runnable,其余方法和sendMessage()一致;

3、源码细节

  •  Message的保存形式
public final class Message implements Parcelable {
Message next;
}

由Message的源码中看出,在Message的内部中存在Message类型的next属性,也就是说对象中指向对象,很显然这是链表结构,所以Handler中的Message是以单向链表形式保存数据的;

  • MessageQueue中任务的保存和获取

由上面的分析知道当Handler发送Message后,程序会执行enqueueMessage()向MessageQueue中添加任务

   boolean enqueueMessage(Message msg, long when) {
  
        synchronized (this) {
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when; // 保存要发送的时间,主要针对延迟发送
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) { // 与链表头部对比时间
                msg.next = p; // 放在链表头部
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) { // 循环遍历队列中的任务,将当前任务插入到对应的时间位置
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

从上面添加过程可以看出MessageQueue中存在属性mMessages,mMessages始终指向链表的第一条数据,在插入Message对象msg时使用mMessages的发送时间和msg时间对比,如果msg立即发送或小于mMessage则将msg防止链表头部,否则循环遍历MessageQueue中的队列将Msg插入合适的位置;

对于Message的获取,因为Message是以链表结构保存的,所以在MessageQueue中只需要保存链表头部的一个Message对象即可,然后利用链表的遍历依次取出next的对象,只是在获取到Message对象时会比较当前时间和Message的执行时间,然后通过阻塞实现延迟发送;

  • Message 缓存获取

在使用Handler发送消息时先需要创建Message对象,一般创建都使用Message.obtain()方法,这里有没有想过为什么不直接new出对象呢?答案就在Message的内部,为了提高程序的性能节省创建的时间,Message内部使用缓存机制实现对象的复用;

Message next // message使用链表结构保存,next用于指向下一个对象
private static Message sPool; // 当前缓存复用的队象
private static int sPoolSize = 0; // 缓存message对象大小
public static final Object sPoolSync = new Object(); // 锁
private static final int MAX_POOL_SIZE = 50;

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

从上面代码看出Message使用链表结构保存数据,内部使用sPool缓存当前空闲的、准备复用的Message对象,当调用obtain()时如果sPool 为空则创建Message对象,否则系统会将sPool返回,同时将sPool.next对象复制给sPool作为下一个空闲的对象,那使用完的Message是如何缓存的呢?

void recycleUnchecked() {
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

在Message使用后会调用recycler()回收对象,recycler中会调用recycleUnchecked(),recycleUnchecked中会清除使用的Message中的信息,判断如果缓存对象数量未达到最大值,则将当前对象复制给sPool缓存起来,同时将原来的缓存放到链表的next中,实现链式缓存;

 

Handler消息机制的原理和过程介绍完毕了,带着上面的了解现在来解决上面的两个疑问:

  • 为什么不能在子线程直接创建创建handler吗?

通过上面的源码分析,我们知道抛出的异常在上述代码中也出现了

mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }

当Looper.myLooper()取出为空时抛出异常,而myLooper()中时从sThreadLocal.get()中获取,即此时sThreadLocal中没有设置Looper对象,所以也就没有后面的队列等操作,从上面知道创建并设置Looper对象是在prepare()方法中,所以要在子线程中使用Handler要先调用Looper.prepare()创建Looper对象,后调用Looper.loop()开启消息循环。

  • Handler究竟执行在哪个线程中?

其实针对这个问题,网上很多答案是:执行在Handler()所在的线程,即在哪个线程创建Handler实例,handler后续的任务就执行在哪个线程,其实这个答案可对可错,从我们平时使用来看无论是在子线程初始化Loop后创建Handler(),还是在主线程直接创建,最终Handler所执行的线程就是创建线程,从这个角度说答案是正确的;

但这只是其中一种情况,下面看看Handler的另一种创建方式:从上面我们知道对于任务的处理在looper.loop()中,以及后面对任务的处理都在同一线程中,整个过程知道HandlerMessage()都没有执行线程切换处理,也就是说调用loop()方法的Looper实例在哪个线程创建就会在哪个线程执行,同时消息队列mQueue也在相同线程,之所以会有人说执行的线程会是Handler创建的线程,其实因为在主线程或子线程使用无参构造函数创建Handler()时,Looper实例和Handler实例在同一线程,所以任务和Handler执行在此线程,到从可以得出结论:Handler真正执行的线程是消息队列Looper()实例的所在线程,关于此结论的验证,代码很简单请读者自行验证;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值