沃梦达 / IT编程 / 移动开发 / 正文

掌握Android Handler消息机制核心代码

该文主要是分析Handler消息机制的关键源码,文章会从对handler有一些基本的认识开始介绍,内容详细,感兴趣的小伙伴可以参考下

这端代码很简单,Message内部维持了一个单线链表,使用sPool作为头部,用来存储Message实体。可以发现,每次调用者需要一个新的消息的时候,都会先从链表的头部去取,有消息就直接返回。没有消息才会创建一个新的消息。

那么这个链表是在何时插入消息的呢?接下来看Message的回收:


public static final Object sPoolSync = new Object();
private static final int MAX_POOL_SIZE = 50;

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

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

该方法在每次消息从MessageQueue 队列取出分发时都会被调用,就是在上文提到的Loop.loop()方法里。 代码也很简单,首先将Message的成员变量还原到初始状态,然后采用头插法将回收后的消息插入到链表之中(限制了最大容量为50)。而且插入和取出的操作,都是使用的同一把锁,保证了安全性。

注意插入和取出都是对链表的头部操作,这里和消息队列里就不太一样了。虽然都是使用单向链表,回收时使用头插和头取,先进后出,是个栈。而在MessageQueue里是个队列,遵循先进先出的原则,而且插入的时候是根据消息的状态确定位置,并没有固定的插入节点。

这是一个典型的享元模式,最大的特点就是复用对象,避免重复创建导致的内存浪费。这也是为什么android官方推荐使用这种方式创建消息的原因:就是为了提高效率减少性能开销。

4、 IdleHandler

IdleHandler 的定义很简单,就是一个定义在MessageQueue里的接口:


  public static interface IdleHandler {
        boolean queueIdle();
    }

根据官方的解释,在 Looper循环的过程中,每当消息队列出现空闲:没有消息或者没到任何消息的执行时间需要滞后执行的时候,queueIdle 方法就会被执行,而其返回的布尔值标识IdleHandler 是永久的还是一次性的:

  • ture:永久的,一旦空闲,就会执行
  • false:一次性的,只有第一次空闲时会执行

它的使用方法如下:


Looper.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                return true;
            }
        });

看一下addIdleHandler 的实现


private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

 public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }

代码很简单,就是一个List来保存接口的实现。那么它是怎么实现在出现空闲时调用呢?

还记得在上文MessageQueuenext方法中省略的代码吗?


Message next() {
        
        //...省略不相关代码

        //步骤一
        int pendingIdleHandlerCount = -1; // -1 只存在第一次迭代中
        for (;;) {
                //...省略不相关代码

                //步骤二
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }

                //步骤三
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                //步骤四
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

        
            //步骤五
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;

                //步骤六
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                //步骤七
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            //步骤八
            pendingIdleHandlerCount = 0;
        }
    }

接下来分步骤分析一下代码:

第一步:在取消息的循环开始前创建局部变量pendingIdleHandlerCount用来记录IdleHandler的数量,只在循环开始时为-1;
第二步:当没有取到Message消息(没有消息或者没有可立即执行的消息,也没有进去阻塞状态)或者消息需要延后执行,为pendingIdleHandlerCount 赋值记录IdleHandler的数量;
第三步:判断IdleHandler的数量,如果没有IdleHandler,则直接结束当前循环,并标记循环可进入挂起状态。
第四步:判断是否是第一次,初始化IdleHandler 的List
第五步:开始遍历所有的IdleHandler
第六步:依次执行IdleHandler queueIdle方法
第七部:根据各IdleHandler queueIdle的返回值判断IdleHandler 是永久的还是一次性的,将非永久的从数组里移除;
第八步:修改IdleHandler 的数量信息pendingIdleHandlerCount ,避免IdleHandler 重复执行。
这就是IdleHandler 的核心原理,它只在消息队列为空时,或者消息队列的头部消息为延时消息时才会被触发。当消息队列头部为延时消息时,它只会触发一次哦。在前文中取消息的小节中我们讲过:延时消息在结束当前循环后进入下一路循环会触发阻塞。

5、Handler在Framework层的应用

不知道你有没有想过为什么Android在主线程里直接帮你调用了Looper.prepare() Looper.loop()方法,难道只是为了你使用方便吗?这岂不是有点杀鸡用牛刀的感觉?

事实上远没有这么简单,如果你看一下framework的源码你就会发现,整个android app的运转都是基于Handler进行的。四大组件的运行,它们生命周期也是基于Handler事件模型进行的,以及点击事件等。这一切均是由Android系统框架层产生相应的message再交由一个Handler进行处理的。这个Handler就是ActivityThread内部类H,贴一段它的代码截图

可以看到,四大组件的生命周期甚至内存不足,都有handler在参与。

这也解释了为什么在主线程执行耗时任务会导致UI卡顿或者ANR:因为所有主线程也就是UI线程的逻辑代码都是在组件的生命周期里执行的,而生命周期又受到Handler的事件体系的控制,当在任意生命周期做中执行耗时操作,这会导致消息队列MessageQueue中后面的消息无法得到及时的处理,就会造成延迟,直至视觉上的卡顿,严重的则会进一步触发ANR的发生。

到此这篇关于掌握Android Handler消息机制核心代码的文章就介绍到这了,更多相关Android Handler消息机制核心代码内容请搜索编程学习网以前的文章希望大家以后多多支持编程学习网!

本文标题为:掌握Android Handler消息机制核心代码