深入Handler、Looper、MessageQueue

2022-01-25 10:05:26 浏览数 (1)

Handler、Looper、MessageQueue基本了解可以看下这篇文章 Android源码解读-Handler、Loop、MessageQueue

一、MessageQueue如何实现Message添加和延迟获取?

一、如何添加

enqueueMessage

代码语言:javascript复制
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        ......
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        //---------------------------------------1
        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 {
        //----------------------------------------2
            // 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;
        }

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

上面代码,我们可以知道,添加Message方式分两种情况

第一种情况

如果当前待添加的Message的when等于0或小于头Message的when,那就直接吧待添加的Message添加到队列头部。

假如原有队列是这样

现在要添加新Message when=1

看下上面源码,分两种情况。

1.如果mMessages==null

这个情况是第一次添加Message,原本队列为空,则队列内容就是msg。

2.when ==0|| when < p.when

这种情况就如上图,在mMessages不为空的情况下msg的next指向mMessages,也就是msg作为对头,when值小的放在对头,结果如下

由mMessages = msg;可知,mMessages就是对头。

第二种情况

when > p.when

假设队列情况如下

我们现在要添加Message when=6

这个时候我们会遍历队列,一直找到msg的when大于当前队列中次最大when值的Message或者找不到最大when的时候,直接添加到次最大when的Message或者对尾。结果如下

此时的对头mMessages,是指向谁呢,这个取决于next方法。

二、如何延迟获取

代码语言:javascript复制
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;
    }
}

1.每次取Message为 Message msg = mMessages,就是取对头Message。

2.我们从这边文章中得知,Handler中添加Message最终方法是调用 sendMessageAtTime(msg, SystemClock.uptimeMillis() delayMillis);这个方法得知,when值=SystemClock.uptimeMillis() delayMillis。

next最主要的参数 nextPollTimeoutMillis,这个决定了什么时候可以取出Message,nextPollTimeoutMillis=msg.when - now。

调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞,这是一个本地方法,会调用底层C 代码,C 代码最终会通过Linux的epoll监听文件描述符的写入事件来实现延迟。

1.如果nextPollTimeoutMillis=-1,这个时候msg为空,一直阻塞不会超时。

2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。

3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。

二、Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?

我们在ActivityManagerService中发现 调用Process.start接口来创建一个新的进程,新的进程会导入android.app.ActivityThread类,并且执行它的main函数。ActivityThread没有继承Thread,他不是主线程。个人猜测当前进程就是主线程。

1.为什么要阻塞

一个线程,如果执行完成,该线程会消亡。如果当前Looper不阻塞,执行完成,那app直接嗝屁。

2.阻塞后为什么还能跳转页面等操作

这个问题是2个问题。第一个是如何执行页面跳转等操作,第二个是阻塞后为什么还能操作页面。

第一个,我们在ActivityThread main函数发现thread.attach(false, startSeq);行为,该行为就是创建binder线程,使得当前进程和和system_server系统进程通讯。

比如要启动新Activity:

1.应用程序的MainActivity通过Binder进程间通信机制通知ActivityManagerService,它要启动一个新的Activity;

2.ActivityManagerService通过Binder进程间通信机制通知MainActivity进入Paused状态;

3.MainActivity通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态;

4.ActivityManagerService通过Binder进程间通信机制通知MainActivity所在的ActivityThread,现在一切准备就绪,它可以真正执行Activity的启动操作了。

第二个,阻塞后还能操作,跳转页面,还能调用各种声明周期。我们可以看到ActivityThread定义了内部类Handler,他和Looper都是在同一线程即主线程。Handler可以接受来自binder线程的数据,比如收到msg=H.LAUNCH_ACTIVITY,添加到MessageQueue中,Looper监测有数据了,通过Handler,把消息发出去,最终则调用ActivityThread.handleLaunchActivity()方法,通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;

3.源码中哪里执行ANR报错

从源码中我们可以知道

  • 用户的输入在5s内没被App响应
  • BroadcastReceiver的onReceiver()超过10s
  • Service中各生命周期函数执行超过20s

这三个情况会报ANR提示。

0 人点赞