一切从Android的Handler讲起(四):Looper消息获取

一切从Android的Handler讲起(四):Looper消息获取

  上一篇肥柴分析了Looper的线程唯一性原理,这一篇章,肥柴将从Looper的源码入手,对Looper获取分发消息原理的进一步解析,来解答以下三个问题:

  1、Looper如何不断的获取Message并处理?

  2、在UI线程里,系统预先为我们创建了一个Looper,那么UI线程里的Looper这个死循环岂不是占用了所有CPU资源,那为何不会出现ANR?

  3、当MessageQueue在检查到没有Message时进入了休眠,那如果此时,用户此时来一个按钮点击事件,点击事件包装成的Message被Handler扔进来时,MessageQueue又如何知道,从而立即做出反应?

带走

  以上三个问题具备层次关系,因此我们一步步来解答。

一、Looper循环获取Message并处理

  在Handler的使用中,我们一直强调需要调用Looper.loop()来启用消息循环监听处理,那么肥柴就从这部分源码入手,来回答第一个问题:Looper如何不断的获取Message并处理?

    /** Looper.class */

    @SuppressWarnings("AndroidFrameworkBinderIdentity")
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed" + " before this one completed.");
        }

        me.mInLoop = true;

        // other code ...

        /** 注释1 循环获取Message */
        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

    @SuppressWarnings("AndroidFrameworkBinderIdentity")
    private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {
        /** 注释2 通过MessageQueue的next方法获取Message */
        Message msg = me.mQueue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return false;
        }

        // other code ...

        try {
            /** 注释3 获取到Message,分发并处理 */
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        
        // other code ...

        return true;
    }

  肥柴省略了大部分代码,只留下关键性源码,从中我们得知:loop()方法通过一个for (;;)死循环(注释1)不断的从MessageQueue中循环获取消息并处理,而具体的消息获取机制则在MessageQueue的next()中,如果取出的Message为null,则跳出循环;否则通过Message的target将消息传递给对应的Handler处理(注释3)

  肥柴有了如下结论:

  1、Looper通过for (;;)死循环不断的从MessageQueue中循环获取消息并处理

  2、停止死循环的方法是使得Looper取得的消息为null,即调用quit()/quitSafely()方法

二、UI线程不阻塞

  在学习Handler使用的时候,我们经常会提到,主线程Looper的获取直接通过Looper.getMainLooper(),并不需要调用prepare()和loop()方法创建启用,原因在于:Android的主线程为ActivityThread,主线程的入口方法为main,在main方法中会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()开启主线程的消息循环。

    /** ActivityThread.class */
    public static void main(String[] args) {
        // other code

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();
        // other code
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

原来如此

  这时候就回到了我们的第二个问题:UI线程里的Looper这个死循环岂不是占用了所有CPU资源,那为何不会出现ANR?

  要回答这个问题,首先我们需要了解MessageQueue获取消息的源码。

    /** MessageQueue.class */
     @UnsupportedAppUsage
    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();
            }

			/** 注释4 Message获取 */
            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;
        }
    }

  这里的源码相对来说比较长,大部分的操作是在计算消息延迟时间以及IdelHandler相关,这部分我们暂时不看,我们只关注注释4的地方,nativePollOnce()方法。

  在MessageQueue中,nativePollOnce()方法检查到单链表里没有任何Message存在,就会使得线程阻塞在此处,loop()方法无法继续下面的处理逻辑,因此也没有可执行的代码片段,对于CPU来讲此线程将会进入休眠,将时间片分配给其他线程;如果检查到单链表队首有Message可以立即返回,则交给loop()执行接下来处理此Message的逻辑,处理完逻辑后。for(;;)又会紧接着循环到开头的queue.next(),如此周而复始…

  因此对于第二个问题:UI线程里的Looper这个死循环岂不是占用了所有CPU资源,那为何不会出现ANR?肥柴对以上的总结回答便是:在MessageQueue无消息情况下,nativePollOnce()方法阻塞线程,对于CPU来讲此线程将会进入休眠,将时间片分配给其他线程,因此并不会造成ANR

想到了!

三、MessageQueue响应消息机制

  进一步的,当MessageQueue在检查到没有Message时进入了休眠,那如果此时,用户此时来一个按钮点击事件,点击事件包装成的Message被Handler扔进来时,MessageQueue又如何知道,从而立即做出反应?

  这里就涉及到了nativePollOnce()方法的机制原理。

  实际上,从nativePollOnce的方法名就可以知道,这是一个JNI的底层方法,而这个方法便是使用了Linux的epoll机制。

  epoll机制提供了Linux平台上最高效的I/O复用机制,它能够在一个地方等待多个文件句柄的I/O事件。简而言之就是Android使用pipe管道创建两个fd文件描述符,nativePollOnce方法中正是承载着这个管道的读操作,根据fd知道是管道读端有可读事件。

  当有Message来临时,比如触摸事件,即会调用Message.enqueueMessage(),添加完Message后,调用了native层的nativeWake方法,就会向管道写端写一个“W”字符,这样就能触发管道读端从epoll_wait函数返回,从而达到唤醒线程的目的,从nativePollOnce()处继续执行下去

懵

四、肥柴总结

  至此,我们来总结回答开篇提到的三个问题,来结束这漫长的一篇:

  1、Looper通过for (;;)死循环不断的从MessageQueue中循环获取消息并处理

  2、在MessageQueue无消息情况下,nativePollOnce()方法阻塞线程,对于CPU来讲此线程将会进入休眠,将时间片分配给其他线程,因此并不会造成ANR

  3、nativePollOnce()方法原理是利用Linux的epoll机制,以达到MessageQueue休眠情况下及时相应消息的到来和处理;

 - - - - - Looper消息获取篇完 - - - - -

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值