Android API 31 Handler机制源码解读(三)

上文中,Android API 31 Handler机制源码解读(二)已经把Message成功enqueue到了MessageQueue当中,enqueue之后,当然是希望能够被处理,进入队列之后,是怎么进入处理流程的呢,在第一篇Android API 31 Handler机制源码解读(一)中的示例代码中有这样一段,

        thread = new Thread(() -> {
            Looper.prepare();
            threadHandler = new Handler(Looper.myLooper()) {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    Log.i(TAG, "threadHandler handleMessage thread id: " + Thread.currentThread().getId());
                }
            };
            Looper.loop();
        });

其中Looper.prepare()就是为当前线程创建一个Looper对象,咱在上一文当中有举过例,Looper就好比机场出口那里的大转盘,不停的旋转,把每位旅客的行李分发出去。这段code最后有一个Looper.loop()操作,就是启动Looper,让大转盘转起来。这个Looper.loop()是个阻塞的方法,所以要放在最后。咱看下loop()方法,这个方法里重点看下2处,一头一尾,

 在最开始的位置,做了个判断,如果没有先创建(prepare),抛一个异常。主要是在末尾这里,

        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }

 这里是个无限循环,loopOnce方法里咱重点看3处,分别对应取Message,分发处理Message,回收Message,先看取Message,在loopOnce方法的最开头,就这样一行:

Message msg = me.mQueue.next(); // might block

    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                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) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                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);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            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);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

首先对ptr指针进行判空,我们先不考虑quit的情形。看下面,这个Binder.flushPendingCommands();是干嘛的?点进去看一下,

    /**
     * Flush any Binder commands pending in the current thread to the kernel
     * driver.  This can be
     * useful to call before performing an operation that may block for a long
     * time, to ensure that any pending object references have been released
     * in order to prevent the process from holding on to objects longer than
     * it needs to.
     */
    public static final native void flushPendingCommands();

从注释描述,这个是用来刷新一些状态的,释放一些对象,为下面即将产生的长时间阻塞(block)做准备,那说明接下来会有阻塞的操作,

Line 17,nativePollOnce(ptr, nextPollTimeoutMillis);这里会产生阻塞,在上一文中末尾(enqueueMessage方法的最后),当时忽略了一个操作:nativeWake(mPtr);

nativeWake和nativePollOnce是一套组合拳。这套组合拳使用的内功是Linux poll(epoll)心法,哈哈,关于这套IO机制,有很多资料可以查看,这里举个栗子理解一下,

 还是以机场出口取行李的转盘为例,行李转盘我看到的是一直转不停的,这样其实也是有问题的,比如疫情期间,航班限流,旅客稀少,也没多少行李,转盘一直转不停是不是有点浪费?那有什么办法可以解决这个浪费呢,当然可以。在上文的例子中,提到会有个标签贴在行李上,写上旅客的名字,类比到Message,这个标签上还有另一个信息,就是旅客期望什么时候来取这件行李。那我们把转盘改造一下,让转盘可以识别行李上的时间,比如有件下午3点要被取走的行李(行李都是按照被取走的时间排好序的,早的排前面),到达下午3点,转盘就旋转,把这件行李转出去。好了,干这个事情的就是他了nativePollOnce(ptr, nextPollTimeoutMillis)。所以咱前面提到的“Looper.loop()操作,就是启动Looper,让大转盘转起来”,其实不是转起来,应该说给转盘通上电,更为合适。

Line 19,queue是大家共同使用的,对queue进行同步。

Line 24,天呐,这里为什么要判断"msg.target == null",真的有人行李不写名字的吗,还真有,VIP旅客,哈哈,关于Message同步屏障,咱后面分析,从developer api call进来的情形,是不会出现"msg.target == null"的。咱跳过这个if。

Line 31至48,这段就比较好理解了。下一个Message的时间还没到,就再计个时,时间到了就取出来,然后从队列里移除,并返回。接下来下面的是quit和idle的一些逻辑,咱暂且略过。可以通过注释了解一二。到这里我们姑且认为,已经成功吭哧吭哧地取到一个Message,哈哈。

我们回到loopOnce方法里,直接看Line 201处的分发,可以看到这里是根据名字(msg.target)来进行分发的。

 这里就进入到了Handler的dispatchMessage方法:

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        //post(runnable)的时候会对msg.callback进行赋值,msg.callback就是runnable
        if (msg.callback != null) {
            handleCallback(msg);//进去就是调用了run方法
        } else {
            if (mCallback != null) {//传入callback创建的Handler走这里
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //subclass创建的Handler走这里,与上面没啥本质区别,都是调到
            //用户自定义实现的handleMessage方法
            handleMessage(msg);
        }
    }

用户handleMessage之后,在loopOnce方法的最后,进行了Message的回收msg.recycleUnchecked();

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    @UnsupportedAppUsage
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        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++;
            }
        }
    }

把各种状态清掉,然后放到Message Pool里面,需要用的时候,可以通过obtain方法去拿,避免Message对象的反复创建。

时间仓促,水平有限,欢迎老铁同行们批评指正,讨论交流,敬请关注后续。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值