Android Handler分析 (二) MessageQueue详解

在上一篇博客《Android Handler分析(一) Handler和Message详解》中我们说到了Android中Handler机制的两个重要类Message和Handler,在这篇博客中,我们继续来看一下另外的一个重要类,MessageQueue(消息队列)类。

MessageQueue源码分析:

1. MessageQueue中的一些成员变量

private final boolean mQuitAllowed;  // MessageQueue是否可以退出,如果是主线程的就不能退出
private long mPtr; // used by native code,native方法中使用的,表示native代码中的MessageQueue地址
Message mMessages; // Message是一个链表结构,mMessages表示链表的第一个元素,也就是消息队列的队首
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();// 用来保存IdleHandler对象的集合
private IdleHandler[] mPendingIdleHandlers;// 用来保存IdleHandler对象的数组,在后面会将集合中的对象复制到数组中
private boolean mQuitting; // 当前消息队列是否处于退出状态
private boolean mBlocked;  // 表示在next()方法中是否被阻塞在超时时间非0的native pollOnce()方法上。
private int mNextBarrierToken; // 表示下一个Barrier(障碍物,屏障;界线)的标记(令牌)

2. 看一下IdleHandler接口

/**
 * 这个接口是当next()方法没有获取到或者在等待可以执行的消息时,就可以执行这个接口的回调
 */
public static interface IdleHandler {
    /**
     * 回调方法,返回boolean类型的值,如果返回true表示不移除,返回false表示执行完之后就从集合中移除该对象
     */
    boolean queueIdle();
}

// 增加一个IdleHandler接口实现类对象到mIdleHandlers集合中
public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

// 从mIdleHandlers集合中移除这个IdleHandler对象
public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

下面是系统ActivityThread.java中的一个使用IdleHandler的例子

final GcIdler mGcIdler = new GcIdler();
// 创建内部类实现 IdleHandler 接口
final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        // 调用关于gc的方法,即当Handler没有消息可以执行时调用一下doGcIfNeeded()方法进行垃圾回收
        doGcIfNeeded();
        // 返回false,表示执行完之后就移除
        return false;
    }
}
void scheduleGcIdler() {
    if (!mGcIdlerScheduled) {
        mGcIdlerScheduled = true;
        // 调用MessageQueue的addIdleHandler()方法将mGcIdler添加到集合
        Looper.myQueue().addIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}
void unscheduleGcIdler() {
    if (mGcIdlerScheduled) {
        mGcIdlerScheduled = false;
        // 从集合中移除mGcIdler对象
        Looper.myQueue().removeIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}

在ActivityThread.java类中还有一些使用了这个接口,大家有兴趣可以去查看一下

3. MessageQueue对象的创建

// 构造方法,默认修饰符,开发者不能创建,是在创建Looper时系统创建的,并和Looper绑定在一起
// 参数quitAllowed 表示该消息队列是否可以退出,主线程的不可以退出,其他线程的可以退出
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

4. 判断消息队列中是否有指定消息

boolean hasMessages(Handler h, int what, Object object) {
    // 如果Handler为null,直接返回false
    if (h == null) {
        return false;
    }
    // 同步操作
    synchronized (this) {
        Message p = mMessages;
        // 遍历消息队列,获取每一个消息
        while (p != null) {
            // 将获取到的消息与参数进行比较
            if (p.target == h && p.what == what && (object == null || p.obj == object)) {
                // 找到了,返回true
                return true;
            }
            // 链表,移动到下一个
            p = p.next;
        }
        // 没有找到,返回false
        return false;
    }
}

// 判断方式一样,源码就不贴出来了
boolean hasMessages(Handler h, Runnable r, Object object) {}

5. MessageQueue中的enqueueMessage()方法(非常重要)

在上一篇博客《Android Handler分析(一) Handler和Message详解》中,我们使用Handler发送一个消息,我们就看到了Handler中并没有很多操作,最终都是调用MessageQueue中的enqueueMessage()方法并将Message对象作为参数传递过来。上一篇博客中我们并没有说到这个过程,现在,我们就一起来看看这个方法的庐山正面目吧。

boolean enqueueMessage(Message msg, long when) {
    // target表示Handler对象,在上一篇博客中已经强调过了。
    // 如果消息没有绑定Handler对象,直接抛出一个异常
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    // 判断消息是否在使用,如果在使用,抛出一个异常
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    // 进入同步代码块
    synchronized (this) {
        // 判断队列是否已经退出
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            // 已经退出,释放消息,返回false。消息加入队列失败
            msg.recycle();
            return false;
        }
        // 指定消息正在使用
        msg.markInUse();
        // Message的when设置为参数的when
        msg.when = when;
        // 定义一个临时变量保存队列的头(队首的消息)
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // 如果队列为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) {
                    // 当满足 p = null (到了最后一个)或者 when < p.when 时(根据执行时间找到合适了位置了)跳出循环
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            // 将Message插入到队列中
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        // 如果需要唤醒,调用native方法,让轮询器继续取消息
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    // 返回true,Message加入队列成功
    return true;
}

在我们调用Handler中的发送消息方法时,Handler会调用MessageQueue中的enqueueMessage()方法来将消息加入到消息队列中,然后让轮询器获取消息。MessageQueue是一个链表,如果对链表不了解的,可以查看《数据结构——链表(一)》《数据结构——链表(二)》

下面我们就来看一下MessageQueue中另一个非常重要的方法,next() 方法,它返回下一个需要处理的Message,在 Looper.loop() 中方法调用

6. MessageQueue中的next()方法(非常重要)

next() 方法的主要作用就是从消息队列中取出下一个需要执行的方法

Message next() {
    // 定义变量保存native代码中的消息队列地址值
    final long ptr = mPtr;
    // 如果地址值为0,表示队列不存在,返回null
    if (ptr == 0) {
        return null;
    }
    // 保存IdleHandler对象的集合的大小,初始化时设置为 -1
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    // 执行下一个消息需要等待的时间
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            // 如果nextPollTimeoutMillis != 0的话,调用Binder.flushPendingCommands()
            Binder.flushPendingCommands();
        }
        // 进入native层,有可能会阻塞next()方法执行,
        // 等待nextPollTimeoutMillis的时间后开始执行
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // 记录当前时间
            final long now = SystemClock.uptimeMillis();
            // 初始化变量prevMsg为null,msg为mMessges(第一个消息)
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // msg.target == null表示此消息为消息屏障(通过MessageQueue#postSyncBarrier()方法发送来的)
                // 如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息)
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                // 找到了可执行的消息
                if (now < msg.when) {
                    // 找到了,但是Message还没到执行的时间,就设置一个等待时间
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 消息到了执行的时间,设置不在阻塞
                    mBlocked = false;
                    // 操作链表
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    // 设置msg的下一个为null
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    // 设置msg正在使用
                    msg.markInUse();
                    // 返回找到的Message(需要现在处理的Message)
                    return msg;
                }
            } else {
                // 没有找到可执行的消息,设置 nextPollTimeoutMillis 为 -1,会一直阻塞,直到被唤醒
                nextPollTimeoutMillis = -1;
            }

            /************** 走到这里表示没有找到可以返回的消息 ***************/

            // 如果队列需要退出,调用方法销毁队列,并返回null
            if (mQuitting) {
                dispose();
                return null;
            }

            /************** 开始处理IdleHandler,用于空闲的时候处理不紧急事件 ***************/

            // 只有在第一次循环的时候才会走这步,因为 pendingIdleHandlerCount 的初始值是 -1
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // 集合中没有IdleHandler对象(没有需要空闲时处理的逻辑),继续等待,并跳出此次循环
                mBlocked = true;
                continue;
            }
            // 没有跳出循环,表示集合中有元素,
            // 如果保存IdleHandler对象的数组为null,就创建一个
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            // 将集合中的IdleHandler对象复制到数组中
            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 {
                // 执行IdleHandler的回调
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                // 如果keep为false,表示该IdleHandler可以从集合中移出,那么就移除集合中的IdleHandler对象
                // 否则不移除,空闲时间再次可能调用
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // 重置值
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

注释在代码中写得比较详细,在这里就不在啰嗦了。

7. MessageQueue中的移除方法

我们可以调用Handler中的移除消息方法,其主要逻辑也是在MessageQueue中。

void removeMessages(Handler h, int what, Object object) {
    // Handler为null,直接返回
    if (h == null) {
        return;
    }

    synchronized (this) {
        Message p = mMessages;

        // Remove all messages at front.移除前面所有消息
        while (p != null && p.target == h && p.what == what
               && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            // 释放消息
            p.recycleUnchecked();
            p = n;
        }

        // Remove all messages after front.移除后面所有消息
        while (p != null) {
            Message n = p.next;
            if (n != null) {
                if (n.target == h && n.what == what
                    && (object == null || n.obj == object)) {
                    Message nn = n.next;
                    n.recycleUnchecked();
                    p.next = nn;
                    continue;
                }
            }
            p = n;
        }
    }
}

下面的也是MessageQueue中的移除消息方法,其主要逻辑和上面方法基本一样,只是参数不同

void removeMessages(Handler h, Runnable r, Object object)
void removeCallbacksAndMessages(Handler h, Object object)
private void removeAllMessagesLocked() // 移除所有的消息,清空消息队列,private修饰
private void removeAllFutureMessagesLocked()// 移除所有的未处理消息,当消息队列的头Message.when大于当前时间,就调用removeAllMessagesLocked()方法。

8. 退出方法

// 有一个参数,表示是否安全退出,true表示安全退出
void quit(boolean safe) {
    // 判断当前MessageQueue是否可以退出,如果是主线程就不能退出并抛出一个异常
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    // 进入同步块
    synchronized (this) {
        // 该MessageQueue已经退出,直接返回
        if (mQuitting) {
            return;
        }
        // 将 mQuitting 设置为true,表示MessageQueue处于退出状态
        mQuitting = true;
        // 判断是否需要安全退出,然后调用不同的移除方法
        if (safe) {
            removeAllFutureMessagesLocked(); // 只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理
        } else {
            removeAllMessagesLocked(); // 把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过sendMessageDelayed或通过postDelayed等方法发送的需要延迟执行的消息)还是非延迟消息
        }

        // 调用native方法,并把native层当前的MessageQueue地址作为参数传递
        nativeWake(mPtr);
    }
}

9. 其他方法

增加和移除需要在空闲时间处理的事件

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

// 移除
public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

发送一个同步阻碍消息

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();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

该消息用于阻碍同步Message消息执行,优先执行异步的Message。特点是变量 target 为 null,在 next() 方法中有用到。

在Android系统中的绘制过程中会使用这个方法:

ViewRootImpl 类中,会通过这个方式来保证主线程可以优先来执行接下来的绘制工作

@UnsupportedAppUsage
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
} 

MessageQueue类主要就分析这么多了,Handler机制的另外一个重要类Looper和Handler相关的一些知识在下一篇博客《Android Handler分析 (三) Looper详解和Handler其他知识》 中介绍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容