从源码分析Handler机制
Posted 陳英傑
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从源码分析Handler机制相关的知识,希望对你有一定的参考价值。
一、发送消息
当发送一个消息,在handler里面最后会执行方法:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis)
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous)
msg.setAsynchronous(true);
return queue.enqueueMessage(msg, uptimeMillis);
然后在MessageQueue里面走方法:
boolean enqueueMessage(Message msg, long when)
if (msg.target == null)
throw new IllegalArgumentException("Message must have a target.");
synchronized (this)
if (msg.isInUse())
throw new IllegalStateException(msg + " This message is already in use.");
if (mQuitting)
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 这个when=SystemClock.uptimeMillis() + delayMillis
// SystemClock.uptimeMillis()表示系统开机到现在的时间,这个在下面消息排序有用
// if表示新消息可以作为头一个,可以唤醒事件队列了
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 // 在此之前有消息,需要把新消息插入队列
// 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;
/*
MessageQueue是按时间先后构成的优先级队列
这是一个无限循环,其实就是对MessageQueue的轮询
消息池MessageQueue中存的是一个一个Message
p是message,所以p.next就是取下一个消息,上一条消息指向下一条消息
*/
for (;;)
prev = p;
p = p.next;
// 如果新消息的发生时间早于当前消息,跳出for循环
// 所有MessageQueue是这样的
// m1->m2->m3->m4,假如p是m3,就是把新消息插入m2和m3之前
if (p == null || when < p.when)
break;
if (needWake && p.isAsynchronous())
needWake = false;
// 把新消息插入刚才循环到的消息p前
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;
二、取消息
在MessageQueue中有个next方法,处理消息时取下一个消息。
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;
// 。。。
取出消息给谁用呢?
Looper,looper里有一个loop方法:
public static void loop()
final Looper me = myLooper();
if (me == null)
// 如果当前线程没有初始化Looper会抛出异常
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
// ...
me.mInLoop = true;
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
boolean slowDeliveryDetected = false;
for (;;)
// 这里又是一个死循环,不断从MessageQueue里面取消息,但是如果没有消息时,
// queue.next()方法会block锁住,就是没有任何返回,程序不会往下走了。
Message msg = queue.next(); // might block
if (msg == null)
// No message indicates that the message queue is quitting.
return;
// ...
try
// 取到消息之后,调用dispatchMessage,这个target在上面发送消息时候赋值的
// 它是Handler,所以在handler中dispatchMessage()方法有调用了handleMessage
//这个就是我们使用handler时要重写的方法。
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);
// ...
这个方法是静态的,只要应用一启动它就会跑起来,这也是程序存活的条件,如果这个loop方法死掉了,那程序也就挂掉了,所以主线程的Looper不可以销毁,这一点从源码也可以找到:
// Looper里面有两个quit方法,他们调用了MessageQueue的quit方法
public void quit()
mQueue.quit(false);
public void quitSafely()
mQueue.quit(true);
// MessageQueue的quit方法进来就要检查是否可以退出,从异常信息可以看出,不允许在主线程调用。
void quit(boolean safe)
if (!mQuitAllowed)
throw new IllegalStateException("Main thread not allowed to quit.");
synchronized (this)
if (mQuitting)
return;
mQuitting = true;
if (safe)
removeAllFutureMessagesLocked();
else
removeAllMessagesLocked();
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
这样整个流程就清楚了
发送消息:Handler.sendMessage --> MessageQueue.enqueueMessage --> 进入消息队列
处理消息:Looper.loop --> MessageQueue.next --> Handler.dispatchMessage --> Handler.handleMessage
三、结合对源码的分析来思考几个问题
-
一个线程有几个handler?
答:多个,例如我们可以在多个Activity中创建Handler
-
一个线程有几个looper?如何保证?
答:一个,如何保证的?看源码
private static void prepare(boolean quitAllowed)
if (sThreadLocal.get() != null)
throw new RuntimeException("Only one Looper may be created per thread");
sThreadLocal.set(new Looper(quitAllowed));
创建Looper要走Looper的prepare方法,如果get()到了说明已经创建了,它会抛出异常,说明这个prepare方法只能被调用一次;如果没有创建,就new一个Looper,而且它是ThreadLocal修饰的,作用域只在当前线程。
ThreadLocal结构,
之前是Object[],奇数存key,偶数存value,k1, v1, k2, v2 ...
现在是Entry[]了,Entry extends WeakReference<ThreadLocal<?>>,它的key是持有当前线程的弱引用,value是Object类型。
-
handler内存泄漏原因?为什么其他内部类没有这样的情况?
先看源码:
发消息的方法最终都会走到Handler的这个方法,不管是延迟消息还是即时消息
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis)
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous)
msg.setAsynchronous(true);
return queue.enqueueMessage(msg, uptimeMillis);
这里可以看出把当前Handler作为Message的target了,Handler又持有Activity的引用,所以当我们发送了一个延迟消息,时长不确定,假如消息没处理退出了activity,这时候就有一个引用链导致内存泄漏,MessageQueue->Message->Handler->Activity;一般的内部类,在activity销毁时基本也都销毁了,所以不会导致内存泄漏,当然,如果其他的内部类也有Handler这种机制的也会导致内存泄漏。
解决方案:1、Handler使用弱引用;2、activity的onDestroy中移除所有消息
-
为什么主线程可以new Handler()?子线程创建handler作何准备?
其实在任何线程创建Handler都是以下3步。
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
而且子线程创建Handler处理完消息还要调用Looper.quit,这个下面问题会说明。
为什么主线程没有prepare和loop?看源码,当应用启动,会执行一系列操作,其中包括ActivityThread中的main函数如下,我们主线程的任何代码都执行在节点1和节点2中间,任何,是任何。所以当我们在主线程创建handler,直接new就行了;在子线程创建handler需要3步。
public static void main(String[] args)
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// Install selective syscall interception
androidOs.install();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
// Call per-process mainline module initialization.
initializeMainlineModules();
Process.setArgV0("<pre-initialized>");
// 节点1,重点就在这里,准备主线程的looper
Looper.prepareMainLooper();
// Find the value for @link #PROC_START_SEQ_IDENT if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null)
for (int i = args.length - 1; i >= 0; --i)
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT))
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
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);
// 节点2
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
其实这一点从最上面的loop方法第一行也能看出来,走loop方法首先会获取looper,如果为空会抛出异常,在子线程直接new Handler那就会奔溃了。
- 子线程中维护的Looper,消息队列无消息的时候如何处理的?作用是什么?
这里用一个例子说明,我们在子线程创建一个Handler,写3个log,然后用我们创建的handler发送一个消息,log1和log2打印出来,但是log3是不走的,为什么?因为没有消息时Looper.loop()方法是阻塞的,在上面源码分析中说过。
private void test()
new Thread(new Runnable()
@Override
public void run()
Looper.prepare();
Handler mHandler = new Handler()
@Override
public void handleMessage(@NonNull Message msg)
super.handleMessage(msg);
Log.e("Handler", "log1 handleMessage");
;
Log.e("Handler", "log2 running...");
Looper.loop();
Log.e("Handler", "log3 handler end");
).start();
调用quit方法会进入MessageQueue.quit,它里面会唤醒线程,而且有一个mQuitting = true;
然后在MessageQueue.next中会返回一个空消息对象
Message next()
// Process the quit message now that all pending messages have been handled.
if (mQuitting)
dispose();
return null;
然后在Looper.loop的for循环中就退出来了,继续我们的代码逻辑了
public static void loop()
// ...
for (;;)
Message msg = queue.next(); // might block
if (msg == null)
// No message indicates that the message queue is quitting.
return;
// ...
处理方案:子线程无消息时处理方案就是调用Looper.quitSafely,释放内存,释放线程
-
既然可以存在多个Handler往MessageQueue里发消息(Handler可能在不同线程),它内部是如何确保线程安全的?
答:添加消息和取消息方法中都加锁了,synchronized,所以时间不一定准确
-
如何创建Message对象?
答:Message.obtain,不能用new Message,这种设计成为享元设计模式。因为像Message这种会频繁创建销毁的对象有可能在使用完释放的时候还有其他地方引用导致释放不完全,如果不断new就会出现内存不断增加的情况,也叫内存抖动,如下:
为了避免这种情况出现,采用消息池的方式来解决。来看obtain的源码:
public static Message obtain()
// 这里要考虑线程安全,所以加锁
synchronized (sPoolSync)
if (sPool != null)
// 如果消息池有可用消息,那么把旧的message对象reset了,消息池size自减,并返回message对象。
// sPool是Message类型,它为什么是消息池呢?
// Message里面还有一个next,也是Message对象
// 类比String数组, 消息池的元素类型是Message,每一个元素又持有下一条消息的引用
// 而且这个消息池的大小是50.
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
// 如果没有可用消息,重新new一个。
return new Message();
-
使用Handler的postDelay后消息队列会有什么变化?
答:如果MessageQueue里面没有消息,在MessageQueue的enqueueMessage添加一条消息,然后在next中会计算消息消息触发的时间,如果时间没到 又会休眠,等到了消息触发时间点会唤醒去执行消息;
如果MessageQueue里面有消息,那么会在MessageQueue的enqueueMessage中按时间顺序添加到MessageQueue中,next方法中正常取消息。 -
主线程Looper.loop死循环为什么不会导致应用卡死(ANR)?
答:Looper.loop()这个死循环是应用运行的必要条件,上面说过应用的一切执行逻辑都运行在loop()中,如果它不运行了应用就挂掉了;一个应用好比一辆车,loop相当于发动机启动,有消息、有逻辑执行时相当于汽车挂上档位加油门跑,没有消息时MessageQueue.next无返回是休眠,相当于汽车没有挂档没有加油门,在怠速运行。
那么为什么其他的死循环或者其他的长时间无返回的逻辑会ANR,其实loop的死循环和导致ANR的原因是两个概念。官方给出的产生ANR一般有三种类型:
1:KeyDispatchTimeout(5 seconds) –主要类型
按键或触摸事件在特定时间内无响应
2:BroadcastTimeout(10 seconds)
BroadcastReceiver在特定时间内无法处理完成
3:ServiceTi以上是关于从源码分析Handler机制的主要内容,如果未能解决你的问题,请参考以下文章
从架构师的角度分析Android Handler 源码的正确姿势