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提示。