Handler核心理解

Handler可能是面试Android被问的最多的把?

大部分都知道Handler、MessageQueue、message、Loop的关系,因为要面试嘛。没办法。那今天看下Handler这一块的东西核心在哪里?

Handler可以创建Message、也可以new一个Message,然后丢到Handler中去。那他是如何丢到列队中去的呢?

1、如何把Message放进列队的呢?

        Handler创建的时候

        mLooper = Looper.myLooper(); // 这里从Looper中拿一个MyLooper,这个就是属于这个Handler的Looper了
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue; // 拿出一个列队来。
        mCallback = callback;
        mAsynchronous = async;

当然,Handler也可以从外面给Looper,比如Looper.getMainLooper(),直接用UI现成的消息列队。尽量不要这么干。

那,有了列队了,Handler中的消息就可以放进MessageQueue中了。

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis); // 就是这里了,把Message丢到MessageQueue中去。
    }

好,我们来看下MessageQueue

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) { // 这个和markInUse,看看代码就知道了。markInUse是标记,isInUse是判断,和下面的msg.markInUse();结合起来看就明白了
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            if (mQuitting) { // 如果正在结束,那没必要往里面塞消息了。
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse(); // 标记这个Message正在操作
            msg.when = when; // 赋值Message的延时时间
            Message p = mMessages; // Message是链表。数据结构,这个我不解释了。
            boolean needWake; // 是否需要唤醒线程,在延时等情况下,会阻塞等待具体看下Next,等下也会讲到
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked. 《-这个解释的很清楚了。
                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 (;;) {
                    prev = p;
                    p = p.next;
                    // 上面两行的意思不用解释了吧。
                    // 这里。找到一个时间比当前Message的延时时间大的,就退出
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next // 找到比他时间大的,就把这个放在时间较大的节点前面
                // 这样,一个先进先出的列队就出来了。
                // 举个例子说明下,以时间作准
                // 0  0  0  0  10000 我要插入一个1000的数据
                // 插入1000时间数据时:
                // 0  0  0  0  10000
                //              ↑ 一直遍历,发现10000比他大,那就把1000放在10000的节点前面,原理就是这样的了。
                // 最后变成 0  0  0  0  1000  10000的链表
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

总结:以上注释就解释了,消息的存放机制了,最终是按照延时时间排序了的。这个机制也解释了,为什么列队中有延时时间长的Message,也不会阻塞到其他的消息分发了。因为每次拿到的,都是延时时间最小的。

看看Looper中的loop()

    public static void loop() {
        final Looper me = myLooper();
        ...

        for (;;) {
            Message msg = queue.next(); // might block // 这里的next,会告诉你会被阻塞。
            if (msg == null) { 循环还是有可能退出的。第一眼以为没有数据,就退出了。那还了得。看看MessageQueue.next后才明白。
                // No message indicates that the message queue is quitting.
                return;
            }

        ...
    }

什么时候阻塞呢?

在看下MessageQueue中的next()

                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    // 上面的描述已经很清楚了,已经没有了,就会等待,并且阻塞住loop
                    mBlocked = true;
                    continue;
                }

2、Handler是如何利用Message切换线程的?

        子线程,可以切换到主线程,利用的就是Handler,这其实就是Android中的线程切换原理,但Handler是如何做到的线程切换呢?

        这个就全在Looper上了。

        sendMessage(...),把消息放在列队中。

    自己扒源码,最终是到这里的。    

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis); // 消息被丢到了这个列队中,那这个列队
        // 是哪里的呢?对,就是Looper中的,这个looper就是Handler持有的,如果给的是    
        // Looper.getMainLooper()。那消息就会被丢到UI线程中的消息列队中执行。就是这样的。
    }

        至于Looper是怎么定位到每个线程的,看看ThreadLocal。这个是个里面有着Map(自己实现的,散列算法),key就是当前线程,所以每次取值的时候,都会取到自己的那个Looper。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值