美文网首页Android
handler源码解析二

handler源码解析二

作者: crossroads | 来源:发表于2020-09-15 15:27 被阅读0次

前言

上一篇handler源码解析一我们跟踪了handler的初始化和发送消息机制,这一篇我们跟踪处理消息机制。
我们知道Looper.prepare()来给线程创建一个消息队列,Looper.loop()来取出消息队列中的消息。

一、 小知识点

native void nativePollOnce(long ptr, int timeoutMillis)
timeoutMillis :0 ,代表不阻塞立即返回,负数代表一直阻塞直到添加新消息为止,正数代表最多等待多少时间。

二、Looper.loop()

我们简化一下loop方法的代码,这里我们只分析正常读取消息情况,也就是取出然后处理,

 final Looper me = myLooper(); // 或得当前线程的looper
 final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); // 可能阻塞
           if (msg == null) { // 没有消息表示消息队列正在退出
                return; //for循环中return相当于break
            }
           msg.target.dispatchMessage(msg); // 处理消息
       }

我们先进入取出方法queue.next,

  int pendingIdleHandlerCount = -1; // -1 only during first iteration
 int nextPollTimeoutMillis = 0;
for (;;) { //一个死循环
    nativePollOnce(ptr, nextPollTimeoutMillis);  
    synchronized (this) {
          final long now = SystemClock.uptimeMillis();
                 Message msg = mMessages;
             if (msg != null) {
                    if (now < msg.when) {
                    //  当第一个节点的消息时间还没到,设置阻塞的等待时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                    // 无需等待 ,返回头节点
                        mBlocked = false;
                        mMessages = msg.next;
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                   } else {
                       // No more messages.
                       nextPollTimeoutMillis = -1; // 没有消息,等待唤醒
                   }
           }
     }
     msg.recycleUnchecked();  //回收message,并加入回收链表中
}

那什么时候被唤醒呢?还记得messagequeue的enqueueMessage()么?里面调用了 nativeWake()方法,这个就是唤醒啦。

下面,我们来看dispatchMessage(), msg.target是当前handler,所以我们进入handler.dispatchMessage方法

public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

这个很简单,就不讲啦。

Looper的quit和quitSafely方法

非安全退出是清空所有消息队列中的消息,安全退出是只清空晚于当前时间的延迟消息。源码就不贴啦。

看完源码,面试一下吧!

  1. 一个线程有几个handler?一个线程有几个Looper和MessageQueue?如何保证?
    多个handler,一个线程只有一个Looper和MessageQueue。Looper.prepare只能执行一次,否则报错,messageQueue是looper的一个变量,looper只有一个,messageQueue也只有一个。

  2. 什么是内存泄漏?Handler内存泄漏原因?如何避免?
    内存泄漏:当一个对象本该被回收时, 另一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了
    原因:匿名内部类持有外部类,也就是activity,当调用postDelayed时,activity还没在时间到达之前被销毁了,会导致activity被持有引用而无法回收
    解决方案: 1>. 使用静态内部类与弱引用,将handler设置为静态的,弱引用activity。2>. 在Activity的ondestrory中调用handler的removeCallbacksAndMessages(), 移除所有的消息和回调就意味着这个Handler被打回原型,也就可以回收了。

  3. 为何主线程可以直接new Handler()?子线程怎么new Handler?
    主线程在APP启动过程中就调用了Looper.prepare和Looper.loop方法,而子线程需要手动调用这两个方法。

  4. 子线程中维护的Looper,消息队列中无消息的时候处理方案是什么?有何作用?
    没有消息的时候,会调用nativepollonce阻塞,直至下一个消息到达,作用是释放CPU资源进入休眠状态,直到下个消息到达。

  5. 不同线程的多个handler往MessageQueue中添加数据,如何保证线程安全
    通过synchronized messageQueue对象来保证了线程的安全性

  6. 使用Message如何创建它?
    Message.obtain()方法,使用了享元设计模式。如果message被remove调,它会成为回收链表的一个节点,当需要新的消息的时候,会从这个队列中读取,如果该链表为空,则创建新的message。

  7. Looper死循环为何不会导致应用卡死?
    ANR出现的情况:1.在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸)2.BroadcastReceiver在10秒内没有执行完毕3.service是20。在这些场景会发出ANR警告,但looper循环就是为了取消息去处理的,如果死循环退出了,也就不能处理各类事件了。

  8. postMessageDelayed怎么延迟时间的?和postDelayed有没有什么区别?
    postMessageDelayed会将 system.uptimeMillis+delay 赋值给message的when成员变量,messageQueue会根据message的when 插入到messageQueue中,和postDelayed没有区别,最终都是调用的postmessageAtTime方法。

  9. Toast在子线程中如何使用
    Toast需要借助Looper的,因为Toast也有new Handler哦,所以要像子线程中初始化handler一样,需要在前后加上Looper.prepare(),在之后加上Looper.Loop()。

  10. 两个子线程怎么使用handler进行交互?
    这个可以参考HandlerThread哦

相关文章

网友评论

    本文标题:handler源码解析二

    本文链接:https://www.haomeiwen.com/subject/pxqfyktx.html