你要悄悄拔尖,然后惊艳所有人-----致每一位坚持到最后的读者!
我们都知道在主线程中不能做耗时操作,如网络请求,数据库读写等。主线程的耗时操作会导致屏幕无响应,造成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




