你要悄悄拔尖,然后惊艳所有人-----致每一位坚持到最后的读者!

我们都知道在主线程中不能做耗时操作,如网络请求,数据库读写等。主线程的耗时操作会导致屏幕无响应,造成ANR( Not )。但是为什么中可以一直循环处理消息而不会阻塞主线程? 下面让我们一起看看。

在的main()方法中:

 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");

从上面系统源码中可以看出主线程也是创建了一个。然后调用.loop()方法。.loop()是一个死循环。不断地处理消息。代码如下:

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;
        //省略部分代码
        me.mSlowDeliveryDetected = false;
        for (;;) { //死循环
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

为什么主线程中存在死循环却不会造成ANR呢?我们接着往下看。


private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        Message msg = me.mQueue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return false;
        }
       //省略部分代码
        return true;
    }

在()中调用的next()方法,不断地从消息队列中取出消息。我们来看一下next()方法。

  @UnsupportedAppUsage
    Message next() {
        //如果消息循环已经退出并被处理,则返回此处。
        //如果应用程序在退出后尝试重新启动不受支持的 Looper,就会发生这种情况。
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        int pendingIdleHandlerCount = -1; // -1 第一次进来的时候赋值
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            /**
               * 关键点。
               *参数1:ptr:natvie
               *当第一次进入的时候,nextPollTimeoutMillis = 0,不需要等待。
                * nativePollOnce 函数被调用时,整个消息队列进入睡眠。
              **/
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                // 尝试检索下一条消息。找到就返回。
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // 被屏障挡住了。查找队列中的下一条异步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        //下一条消息未准备好。设置超时以在准备好时唤醒。.
                        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.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 没有多余消息
                    nextPollTimeoutMillis = -1;
                }
                // 现在处理退出消息,所有挂起的消息都已处理完毕
                if (mQuitting) {
                    dispose();
                    return null;
                }
              //如果第一次空闲,则获取要运行的空闲程序的数量。
              //空闲句柄仅在队列为空或队列中的第一条消息(可能是障碍)将在未来处理时运行
                   if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // 没有空余的线程执行,循环等待
                    mBlocked = true;
                    continue;
                }
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
            // 返回空闲线程.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; 
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            //将空闲线程的数量设置为0
            pendingIdleHandlerCount = 0;
            //下一次等待的时间
            nextPollTimeoutMillis = 0;
        }
    }

从next()方法中可以看出当第一次进入时,s =0,当代码执行到(ptr, s); 睡眠时间为0,当前消息队列不会进入休眠状态,如果当前时间小于消息执行的时间,则将时间差记录给s,如果要处理的消息时间到了,则会自动进入唤醒状态,可以理解为线程中的sleep.

if (now < msg.when) {
         //下一条消息未准备好。设置超时以在准备好时唤醒。.
         nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
         } 

从这里我们就知道了,如果当消息队列中没有要处理的消息时,s = -1,当前消息队列也会进入休眠.可以理解为wait,等待被唤醒.

那么消息队列是如何被唤醒的呢? 我们接着往下看...

当我们往消息插入一条消息时:执行的方法,可以将要处理的消息插入消息队列中.

 boolean enqueueMessage(Message msg, long when) {
           //省略部分代码
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 如果是新部头,如果阻塞则唤醒事件队列。.
                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;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            // 如果needWake为ture,则需要唤醒消息队列
            if (needWake) {
                nativeWake(mPtr); //唤醒方法
            }
        }
        return true;
    }

从代码第7行可以看出,当要一条新的处理的消息被插入消息队列头部时,则需要唤醒消息队列然后将赋值为true.当代码执行到33行时进入判断唤醒消息队列处理消息.

到这里我们基本上可以理解为什么主线程的消息队列不会阻塞线程.当没有消息要处理时,消息队列进入休眠状态,当有消息事件要处理时被唤醒.主要的涉及的方法,,类似多线程中的生产者与消费者.如果大家对今天的内容还有什么不懂得,欢迎留言哦!

源码分析之源码分析《一》

源码分析之源码分析《二》

源码分析之源码分析《三》

源码分析之同步屏障-《四》

未经允许不得转载! 作者:admin,转载或复制请以超链接形式并注明出处墨迹游戏网

原文地址:《为什么Handler消息队列可以一直循环而不会阻塞主线程?》发布于:2024-11-10