1、引言

Android的消息机制,就是Handler、Looper、Message、MessageQueue之间的运作机制。本文假设大家都已经有所了解,并不打算介绍它们之间千丝万缕的联系,不了解的同学可以参考 这篇之前的博文。

这里面有个很细节的问题,估计很多人没有注意到,那就是 Message 的 target == null的情况,这有什么特殊含义吗?与 target 不为 null 的区别在哪里呢?这篇文章的目的就是要揭开 Message 之 target 的面纱。

2、target 为何物

首先,在 Message 类中找到 target 的定义:

Handler target;

没错, target 即为 Handler 对象。

让我们再看看 target 是哪里出现和使用的?

在 Android 消息机制中,当消息入队的时候(如调用Handler#sendMessage() ),最终都会走到 Handler#enqueueMessage(),如下:

// Handler.java private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {        msg.target = this;        if (mAsynchronous) {            msg.setAsynchronous(true);        }        return queue.enqueueMessage(msg, uptimeMillis);    }

看到没,当我们发送一个消息的时候,msg.target 就会被赋值为this, 而 this 即为我们的 Handler对象,它不为 null,因此,通过这种方式传进来的消息的 target 肯定也就不为 null。这种消息为同步消息,也就是我们通常通过sendMessagexxx()来发送的消息。相对地,应该还有一种异步消息吧?没错,的确还有一种很容易被忽略的异步消息,因为我们很少使用。那么,如何发送异步消息呢?

简单说两种。一种是直接设置消息为异步的:

Message msg = mMyHandler.obtainMessage();msg.setAsynchronous(true);mMyHandler.sendMessage(msg);

还有一个 需要用到 Handler 的一个构造方法,不过该方法已被标记为@Hide了:

   /**     *     * @hide     */    public Handler(boolean async) {        this(null, async);    }

使用如下:

Handler mMyHandler = new Handler(true);Message msg = mHandler.obtainMessage();mMyHandler.sendMessage(msg);

参数 asynctrue 即为异步消息。

但是,通过上面两种方式来发送的消息还不是异步消息,因为他们最终还是会进入到 enqueueMessage()给 target 赋值 ,导致 target 不为 null。这跟最初说的普通的同步消息没什么差别。那么什么情况下为target == null 呢?

咱们今天的主角,同步屏障 (Sync Barrier) 就要登场了。

内存屏障是什么

没错,发送异步消息的关键就是要消息开启同步屏障。屏障的意思即为阻碍,同步屏障就是阻碍同步消息,只让异步消息通过。如何开启同步屏障呢?这样:

MessageQueue#postSyncBarrier()

我们看源码里有什么黑科技:

   /**     *     * @hide     */    public int postSyncBarrier() {        return postSyncBarrier(SystemClock.uptimeMillis());    }    private int postSyncBarrier(long when) {        // Enqueue a new sync barrier token.        // We don't need to wake the queue because the purpose of a barrier is to stall it.        synchronized (this) {            final int token = mNextBarrierToken++;            final Message msg = Message.obtain();            msg.markInUse();            //这里赋值的时候没有给 target赋值,因此target=null            msg.when = when;想            msg.arg1 = token;            Message prev = null;            Message p = mMessages;                       if (when != 0) {                while (p != null && p.when <= when) {                 //如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步消息里有时间小于T,则prev也不为null                    prev = p;                    p = p.next;                }            }            /根据prev是不是为null,将 msg 按照时间顺序插入到 消息队列(链表)的合适位置            if (prev != null) { // invariant: p == prev.next                msg.next = p;                prev.next = msg;            } else {                msg.next = p;                mMessages = msg;            }            return token;        }    }

可以看到,Message 初始化的时候没有给 target 赋值,因此, target == null的 来源就找到了。上面消息插入也做了相应的注释。这样,target == null 的消息就进入了消息队列。

那么,开启同步屏障后,所谓的异步消息又是如何获取到的呢?

如果对消息机制有所了解的话,应该知道消息的获取最终都是在MessageQueue#next()中,详见 此博文,简单起见,将里面的代码拿出来:

Message next()         .....//省略        int pendingIdleHandlerCount = -1; // -1 only during first iteration        // 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。        // 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。        // 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)        //   如果期间有程序唤醒会立即返回。        int nextPollTimeoutMillis = 0;        for (;;) {            if (nextPollTimeoutMillis != 0) {                Binder.flushPendingCommands();            }            nativePollOnce(ptr, nextPollTimeoutMillis);            synchronized (this) {                //获取系统开机到现在的时间                final long now = SystemClock.uptimeMillis();                Message prevMsg = null;                Message msg = mMessages; //当前链表的头结点                //如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息                if (msg != null && msg.target == null) {                // 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) {                    //如果有消息需要处理,先判断时间有没有到,如果没到的话设置一下阻塞时间,                    //场景如常用的postDelay                    if (now < msg.when) {                       //计算出离执行时间还有多久赋值给nextPollTimeoutMillis,                       //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                    } else {                        // 获取到消息                        mBlocked = false;                       //链表操作,获取msg并且删除该节点                         if (prevMsg != null)                             prevMsg.next = msg.next;                        } else {                            mMessages = msg.next;                        }                        msg.next = null;                        msg.markInUse();                        //返回拿到的消息                        return msg;                    }                } else {                    //没有消息,nextPollTimeoutMillis复位                    nextPollTimeoutMillis = -1;                }                .....//省略    }

从上面可以看出,当消息队列开启同步屏障的时候(即标识为msg.target == null),消息机制会通过循环遍历,优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。

下面用示意图简单说明:

如上图所示,在消息队列中有同步消息和异步消息以及一道墙----同步屏障。有了内存屏障的存在,msg_2这个异步消息可以被处理,而后面的 msg_3等同步消息不会被处理。那么什么时候这些同步消息可以被处理呢?那就需要移除这个内存屏障,调用removeSyncBarrier()即可。

举个栗子。开演唱会的时候,观众们都在等候在体育馆门口排队依次检票入场(相当于消息队列中的普通消息),这个时候有一票工作人员来了(相当于异步消息,优先级高于观众),如果他们出示工作证(不出示工作证,就相当于普通观众入场,也还是需要排队),保安立马拦住(出示工作证就拦住就相当于开启了同步屏障)进场的观众,先让工作人员进去(只处理异步消息,而过滤掉同步消息)。等工作人员全部进去了,保安不再阻拦观众(即移除内存屏障),这样观众又可以进场了。只要保安不解除拦截,那么后面的观众就永远不可能进场。

内存屏障使用场景

似乎在应用开发中,很少用到内存屏障。那么,同步屏障有什么哪些使用场景呢?

比如,在View更新时,draw、requestLayout、invalidate等很多地方都调用了ViewRootImpl#scheduleTraversals()

    void scheduleTraversals() {        if (!mTraversalScheduled) {            mTraversalScheduled = true;            //开启内存屏障            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();            //发送异步消息            mChoreographer.postCallback(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);            if (!mUnbufferedInputDispatch) {                scheduleConsumeBatchedInput();            }            notifyRendererOfFramePending();            pokeDrawLockIfNeeded();        }    }

postCallback()最终走到ChoreographerpostCallbackDelayedInternal()

这里就开启了内存屏障,并发送异步消息,这样系统就会优先处理这些异步消息。

最后,移除内存屏障的时候会调用了ViewRootImpl#unscheduleTraversals()

    void unscheduleTraversals() {        if (mTraversalScheduled) {            mTraversalScheduled = false;            //移除内存屏障            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);            mChoreographer.removeCallbacks(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);        }    }

总结

当我们调用mHandler.getLooper().getQueue().postSyncBarrier(); 时,target 即为 null ,即开启了同步屏障。当消息队列 MessageQueue处理消息时,如果开启了内存屏障,会过滤同步消息而优先处理其中的异步消息。

更多相关文章

  1. Android(安卓)异步加载——AsyncTask详谈
  2. parse push 消息推送学习笔记(Android消息推送解决方案 备选)
  3. 5分钟完全理解android handler
  4. Android之基于xmpp openfire smack开发之Android消息推送技术原
  5. 浅谈Android四大组件之BroadcastReceiver
  6. 浅谈:Android(安卓)TextView的append方法与滚动条同时使用
  7. Android(安卓)intent消息通知机制
  8. Android(安卓)之ActivityThead、ActivityManagerService 与activ
  9. 检查Android本地代码的内存泄漏(Detecting memory leak in Androi

随机推荐

  1. android 主题元素映射方式
  2. Android自学笔记(番外篇):全面搭建Linux环境
  3. Android之Bean属性通知类
  4. Android任务栏的图标显示
  5. Android 自定义Drawable 实现圆角矩形和
  6. 提示:Not targeting the latest versions
  7. android Drawable转Bitmap| Bitmap转byte
  8. 【原创】Android多个xml文件的使用
  9. Android: 判断网络连接状态及连接类型
  10. [置顶] Android GridView