Android—Handler源码解析

前言

Handler是Android开发中使用常用的机制,自然也是面试中的高频考点,大家都清楚,在问到Handler时,都会供出他的好伙伴MessageMessageQueneLooper

Handler中会延生出一些问题,这些问题包括:

  1. postDelay是如何做到延迟的?
  2. Handler是如何做到线程切换的?
  3. 主线程Looper#loop()死循环会卡顿吗?
  4. Handler内存泄露如何处理?
  5. Handler、Looper、MessageQueue、Thread的对应关系?

接下来我们带着问题来看看Handler的一些源码吧。

Base on Android12

基本使用

先来复习一下Handler的基础用法。

 ​
 // 直接在主线程中创建Handler
 @SuppressLint("HandlerLeak")
 final Handler handler = new Handler() {
     @Override
     public void handleMessage(@NonNull Message msg) {
         LogUtils.INSTANCE.error(TAG, "来消息了");
     }
 };
 ​
 // 在一个子线程中使用
 new Thread(() -> handler.sendEmptyMessage(10086)).start();

示例中的Handler在主线程初始化,如果是子线程中的Handler,记得调用Looper#prepareLooper.loop(), 后面说为什么。

Handler的创建相关

首先来看一下Handler构造的方法。

     // 【无参构造方法,高版本已经废弃,不建议使用】
     @Deprecated
     public Handler() {
         this(null, false);
     }
     // 【带Looper参数的构造方法】
     public Handler(@NonNull Looper looper) {
         this(looper, null, false);
     }

我们知道Handler需要和LooperMessageQueue关联。直观的看,Handler对象中需要持有对应的对象引用

 public class Handler {
   ……
     final Looper mLooper;
     final MessageQueue mQueue;
 }

所以带Looper参数的的构造方法意图就很明显了——你的Handler需要和哪个Looper关联。

主要看一下无参数的构造方法是如何和Looper关联上的。

     public Handler(@Nullable Callback callback, boolean async) {
         ……
         //【关键点1】调用myLooper方法获取Looper对象
         mLooper = Looper.myLooper();
         if (mLooper == null) {
             throw new RuntimeException(
                 "Can't create handler inside thread " + Thread.currentThread()
                         + " that has not called Looper.prepare()");
         }
         mQueue = mLooper.mQueue; // 从Looper中取MessageQueue
         mCallback = callback;
         mAsynchronous = async;
     }
 ​
     static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 ​
     public static @Nullable Looper myLooper() {
       // 【关键点2】从ThreadLocal中获取Looper
         return sThreadLocal.get();
     }

可见当构建Handler没有指定Looper时,会从ThreadLocal中去获取当前线程的Looper对象,如果为空,会抛异常,相信不少小伙伴初学Android时都遇到过。那么当前线程的Looper是何时存储到ThreadLocal中的呢?当然就是调用Looper#prepare的时候。

     public static void prepare() {
         prepare(true);
     }
     private static void prepare(boolean quitAllowed) {
         if (sThreadLocal.get() != null) {
             throw new RuntimeException("Only one Looper may be created per thread");
         }
       // 【关键点3】存储当前线程的Looper对象,且只能初始化一次。
         sThreadLocal.set(new Looper(quitAllowed));
     }

这里回答了为什么在子线程中创建Looper时,需要先调用prepare。那么为什么在主线程中创建的Handler不需要呢?

废话不多说,因为主线程在ActivityThread#main方法中已经调用了

    public static void main(String[] args) {
        ……
          // 【关键点4】
         Looper.prepareMainLooper();
 ​
         ……
         
         // 【关键点5】
         Looper.loop();
     }

所以在主线程中创建Handler不再需要初始化Looper等。

消息入队

我们用得比较多的方法就是postsendMessagesendMessageDelay方法,先挨个看看

post方法

     public final boolean post(@NonNull Runnable r) {
        return  sendMessageDelayed(getPostMessage(r), 0);
     }
     
     private static Message getPostMessage(Runnable r) {
         Message m = Message.obtain();
         m.callback = r;
         return m;
     }

  • 可以看到,post接受一个Runnable类型的参数,并将其封装为一个Message对象,并且将Runnable参数赋值给msg的callback字段,这里要记住,后面有用——Runnable什么时候执行的呢?
  • 最后调用的就是sendMessageDelayed

sendMessage方法

     public final boolean sendMessage(@NonNull Message msg) {
         return sendMessageDelayed(msg, 0);
     }

  • 最后调用的也是sendMessageDelay,第二个参数是0。

sendMessageDelay方法

可见无论是post还是sendMessage方法,最后都走到了sendMessageDelayed

     public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
         if (delayMillis < 0) {
             delayMillis = 0;
         }
         return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
     }
 ​
   // 这里注意第二个参数 @param updateMillis 是一个具体的时间点。
     public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
         MessageQueue queue = mQueue;
         ……
         return enqueueMessage(queue, msg, uptimeMillis);
     }
 ​
     private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
             long uptimeMillis) {
       // 【关键点6】这里要注意target指向了当前Handler
         msg.target = this;
         
 ​
         if (mAsynchronous) {
             msg.setAsynchronous(true);
         }
         // 【关键点7】调用到了queue#enqueueMessage方法
         return queue.enqueueMessage(msg, uptimeMillis);
     }

最终会走到handler中的`enqueueMessage方法,然后走到quene#enqueneMessage(msg, uptimeMillis),从这个命名来看,就是将message放入了消息队列,那么我们来看看,是如何放入队列的吧。

     boolean enqueueMessage(Message msg, long when) {
         ……
         // 【关键点8】对queue对象上锁
         synchronized (this) {
             ……
             msg.markInUse();
             msg.when = when; // msg的when时刻赋值
             Message p = mMessages;
             boolean needWake;
             if (p == null || when == 0 || when < p.when) {
                 // New head, wake up the event queue if blocked.
                 // 翻译:新的头结点,如果queue阻塞,则wakeup唤醒
                 msg.next = p;
                 mMessages = msg;
                 needWake = mBlocked;
             } else {
                 // Inserted within the middle of the queue.  Usually we don't have to wake
                 // up the event queue unless there is a barrier at the head of the queue
                 // and the message is the earliest asynchronous message in the queue.
                // 翻译:将消息插入到消息队列中,通常我不需要进行唤醒操作,除非........
                 needWake = mBlocked && p.target == null && msg.isAsynchronous();
                 Message prev;
                 for (;;) { // for循环,break结束时when < p.when,说明按照when进行排序插入,或者尾节点
                     prev = p;
                     p = p.next;
                     if (p == null || when < p.when) {
                         break;   // 【关键点9】 找到插入位置,条件尾部或者when从小到大的位置
                     }
                     if (needWake && p.isAsynchronous()) {
                         needWake = false;
                     }
                 }
               // 执行插入
                 msg.next = p; // invariant: p == prev.next
                 prev.next = msg;
             }
 ​
             // We can assume mPtr != 0 because mQuitting is false.
             if (needWake) {
                 nativeWake(mPtr); // native方法,唤醒
             }
         }
         return true;
     }

这里面将msg放到消息队列中,可以看到这个队列是一个简单的单链表结构,按照msg的when进行的排序,并且进行了synchronized加锁,确保添加数据的线程安全。之所以采用链表的数据结构,原因是链表方便插入。

初看源码的时候,应该忽略掉wakeup这些处理,关注msg是如何加入队列即可。

到这里,我们了解了message是如何加入消息队列MesssageQueue。但是消息什么时候执行,即重新Handler#handleMessage方法,以及post(Runnable)中的Runnable什么时候才执行。

消息出队执行

我们看完了消息入队,接下来看消息出队的位置。自然在MessageQueue中查找,很容易就定位到next方法,调用它的的方法是Loope#oopOnce,再往上找就是Looper#loop(注意我是Android12)。这个方法很熟悉吧。

     public static void loop() {
         final Looper me = myLooper();
         if (me == null) {
             throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
         }
         ……
         // 【关键点10】死循环,只有loopOnce方法返回false时退出
         for (;;) {
             if (!loopOnce(me, ident, thresholdOverride)) {
                 return;
             }
         }
     }
 ​
     
     private static boolean loopOnce(final Looper me,
             final long ident, final int thresholdOverride) {
         Message msg = me.mQueue.next(); // might block 【注意注释:可能阻塞】
         if (msg == null) {
             // No message indicates that the message queue is quitting.
             return false;
         }
        ……
         try {
           ……
             // 【关键点11】还记得这个target是什么吗?
             msg.target.dispatchMessage(msg);
            ……
         } catch (Exception exception) {
             ……
         } finally {
            ……
         }
         ……
         return true;
     }

loopOnce方法中主要就是调用me.mQueue.next()获取一个消息msg,注意这里可能阻塞。【关键点11】这里调用了msg.target.dispatchMessage(msg)可以回头看看【关键点6】赋值的地方。所以这里就将消息分发给了对应的Handler去处理了。待会儿再看Handler#dispatchMessage,我们接着看next方法是怎么取消息的。

     Message next() {
         // Return here if the message loop has already quit and been disposed.
         // This can happen if the application tries to restart a looper after quit
         // which is not supported.
         final long ptr = mPtr;
         if (ptr == 0) {
             return null;
         }
 ​
         ……
         int nextPollTimeoutMillis = 0;
         // 【关键点12】继续死循环
         for (;;) { 
             ……
             nativePollOnce(ptr, nextPollTimeoutMillis);
 ​
           // 加锁
             synchronized (this) {
                 // Try to retrieve the next message.  Return if found.
                 final long now = SystemClock.uptimeMillis(); // 当前时间
                 Message prevMsg = null;
                 Message msg = mMessages; // msg 指向链表头结点
                 if (msg != null && msg.target == null) { // 这个if可以先忽略
                     // Stalled by a barrier.  Find the next asynchronous message in the queue.
                     do {
                         prevMsg = msg;
                         msg = msg.next;
                     } while (msg != null && !msg.isAsynchronous());
                 }
                 if (msg != null) {
                   // 【关键点13】如果当前时间小于头结点的when,更新nextPollTimeoutMillis,并在对应时间就绪后poll通知
                     if (now < msg.when) { 
                         // Next message is not ready.  Set a timeout to wake up when it is ready.
                         nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                     } else {
                         // Got a message. 开始获取消息
                         mBlocked = false;
                         if (prevMsg != null) {
                             prevMsg.next = msg.next;
                         } else {
                             mMessages = msg.next; // 更新头结点 
                         }
                         msg.next = null; // 断链处理,等待返回
                         ……
                         return msg;
                     }
                 } else {
                     // No more messages.
                     nextPollTimeoutMillis = -1;
                 }
 ​
                 // Process the quit message now that all pending messages have been handled.
                 if (mQuitting) {
                     dispose();
                     return null; 
                 }
 ​
                 ……
             }
 ​
             ……
             // 下面是IdleHandler的处理,还不是很了解
         }
     }

next方法中也是一个死循环,不断的尝试获取当前消息队列已经到时间的消息,如果没有满足的消息,就会一直循环,这就是为什么会next会阻塞的原因

看完了next方法,获取到了msg,回到刚才的msg.target.dispatchMessage(msg),接着看Handler是如何处理消息的。

     public void dispatchMessage(@NonNull Message msg) {
         if (msg.callback != null) {
           // 【关键点14】如果消息有CallBack则直接,优先调用callback
             handleCallback(msg);
         } else {
             // 【关键点15】如果Handler存在mCallback,优先处理Handler的Callback
             if (mCallback != null) {
                 if (mCallback.handleMessage(msg)) {
                     return;
                 }
             }
             // 【关键点16】此方法会被重写,用户用于处理具体的消息
             handleMessage(msg);
         }
     }
 ​
     private static void handleCallback(Message message) {
         message.callback.run();
     }

dispatchMessage方法中优先处理Messagecallback,再回头看看Handler#post方法应该知道callback是个啥了吧。

【关键点15】如果Handler设置了mCallback, 则优先判断mCallback#handleMessage的返回值,这个机制可以让我们做一些勾子,监听Handler上的一些消息。

【关键点16】应该不用多说了,这个方法一般在创建Handler时被重写,用于接收消息。


到这里,我们就简单的把Handler的消息机制看完了,我们结合开篇的几个问题,做一个总结吧。

总结

  1. 一个线程只能有一个Looper,如何确保?

    1. ThreadLocal
  2. Handler#post(Runnable)会将Runnable封装到一个Message中。

  3. MessageQueue采用单链表的实现方式,并且在存取消息时都会进行加锁。

  4. Looper#loop采用死循环的方式,会阻塞线程。那么为什么主线程不会被阻塞?

    1. 因为Android是事件驱动的,很多的系统事件(点击事件、屏幕刷新等)都是通过Handler处理的,因此主线程的消息队列,会一直有消息的。
  5. Handler是如何实现线程切换的?

    1. LooperMessageQueue和线程绑定的,也就是说这个消息队列中的所有消息,最后分给对应的Handler都是在创建Looper的线程。所以无论Handler在什么线程发送消息,最后都回到创建Looper的线程中执行。(有点饶,可以自己好好捋一下,可以看一下第一节的使用)
  6. Thread和Looper、MessageQueue是一对一的关系,Looper、MessageQueue对于Handler和是一对多的关系。这里要注意,一个具体的Handler实例,肯定只关联一个Looper和queue的哟

上面就是整个Handler的消息机制

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

最后针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

需要资料的朋友可以扫描下方二维码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值