一、概述
Android的Handler
消息机制涉及Android系统多个方面,例如Activity
和Service
的生命周期调用,开发中展示从网络下载的数据,线程通信等。通过阅读源码解决下面问题。
问题:
- 消息处理的优先级?
- 消息是怎么存储的?
- 一个线程中
Looper
,Handler
,MessageQueue
数量的对应关系? - 主线程
Looper.loop()
无限循环为什么不会ANR
? - 如何避免使用
Handler
造成的内存泄露? -
IdleHandler
是什么?有什么用? - 消息(
Message
)能没有target(Handler)
吗?
二、引出主题
模拟使用Handler
显示下载的数据。
//step1,创建Handler
var handler = object : Handler() {
override fun dispatchMessage(msg: Message) {
println(msg.obj)
}
}
//模拟线程下载数据
Thread{
Thread.sleep(1000)
var msg=Message.obtain()
msg.obj="download data"
//step2,发送消息
handler.sendMessage(msg)
}.start()
三、Handler
1.发送消息
使用无参构造创建的Handler
会调用2个参数的构造方法。
public Handler(@Nullable Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
在这里会检查Looper
是否初始化完成,但我们前面使用时并没有初始化这个Looper
,为什么没有抛出这个RuntimeException
?其实在App启动时系统已经帮我们初始化好了这个主线程的Looper
。像每个Java程序都有个main
方法的入口,App也有个main
方法入口。在这个方法中,会初始化UI线程Looper
,并开始无限循环,从这可以侧面看出整个应用的运转基本依赖于这套消息机制。
ActivityThread.java
public static void main(String[] args) {
...代码省略
//初始化Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//开启循环
Looper.loop();
//执行到这就是出大问题了
throw new RuntimeException("Main thread loop unexpectedly exited");
}
sendMessage()
会一直调用到enqueueMessage()
,然后指定msg
的target
为当前Handler
,在调用MessageQueue
的enqueueMessage()
2.处理消息
在Looper
的loop
方法中会不断取出消息,然后找到msg
的target
处理消息,然后回调到Handler
的dispatchMessage
方法,通过源码可以看出,消息的处理优先级为:
-
msg
的callback
优先级最高 - 然后是在创建
Handler
时指定的CallBack
- 最后才是
Handler
的handleMessage
方法
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
四、Looper
1.prepare方法
prepare方法会初始化Looper,保存在ThreadLocal中。ThreadLocal是一个与线程相关的数据保存类,保证了每个线程的这份数据独立性。所以通过sThreadLocal取出的Looper都是当前线程的独一份的Looper,得到的MessageQueue也是独一份。
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));
}
2.loop方法
loop
方法无限循环从MessageQueue
中取出msg
,交给Handler
处理,然后将处理的msg
放回消息池。如果消息队列为空了则结束此循环。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
}
msg.recycleUnchecked();
}
}
五、MessageQueue
1.添加消息enqueueMessage()
MessageQueue意为消息队列,就是消息池。通过下面的enqueueMessage方法可以看出MessageQueue的实现方式是单链表。单链表在这的优势在于插入删除快,不用申请一块大的连续内存空间(相比数组)。
boolean enqueueMessage(Message msg, long when) {
//这种方式添加的msg必须有target
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
//被标记退出循环,回收msg
msg.recycle();
return false;
}
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
2.next方法
Message next() {
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();
}
//nataive方法,阻塞方法。
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) {
//最近的消息还没到触发时间,算出需要等好久,去nativePollOnce方法里等着
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 {//取出消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;//从链表断开
msg.markInUse();
return msg;
}
} else {
// No more messages.
//没有消息去nativePollOnce一直等。
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {//标记退出循环
dispose();
return null;
}
//第一次空闲,得到IdleHandlers的数量
//队列为空或者第一条消息将来要处理时,才运行空闲处理。
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
//没有idle handlers需要执行,循环去等着
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 运行idle handlers.
// 只有第一次循环才会到这执行
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 {//执行idler
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {//false执行一边就删除,true执行多次
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// 重置为0,我们就不会在再执行idle handler
pendingIdleHandlerCount = 0;
// 当我们执行了idle handler,可能就有新消息来了,就不等了,去看看。
nextPollTimeoutMillis = 0;
}
nativePollOnce
方法是一个native
方法,没有消息处理时,程序会在这阻塞。nextPollTimeoutMillis
表示阻塞的时间,-1表示一直会阻塞,0表示不阻塞,大于0表示下条消息等的时间。当没有消息时,会进入nativePollOnce
方法里,线程会释放CPU的资源,进入休眠状态。
如果没有消息处理,在进入等待前,会认为这个线程处于空闲状态,会把添加了的IdleHandler
执行了。
六、Message
Message
比较重要的是obtain()
和recycleUnchecked()
,创建Message
最好使用obtain()
而不是直接new
。obtain()
方法会从消息池中拿出一个已经创建好的Message
,可以减少对象的创建。recycleUnchecked()
则是将已经使用过的消息重新放回消息池,消息池最大为50。
private static final int MAX_POOL_SIZE = 50;
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
七、其它
1.IdleHandler
从上面分析MessageQueue
的next()
方法可以看出,当暂时没消息处理进入休眠前,它会查看是否有其它杂事要干,可以通过Looper.myQueue().addIdleHandler(MyIdleHandler())
添加执行内容。所以IdleHandler
是Handler
提供的一种当消息队列空闲时,执行任务的机制。由于执行时机不稳定,可以用来处理一些不重要的杂事。例如ActivityThread
中的GcIdler
用来执行GC
任务。注意返回false
和true
的区别,false
执行一次,true
执行多次。
2.postSyncBarrier()
postSyncBarrier()
的作用是设置一个同步屏障。设置一个msg
为屏障,它的标志是没有target
,把这个msg
按时间when
插入链表中。在next()
方法中,如果发现了同步屏障,则在链表中寻找第一个异步消息返回。这个机制就实现了,只要设置一个没有target
的同步屏障msg
,则消息机制就转成优先处理异步消息,同步消息就会阻塞到等同步屏障移除后才能执行。而postSyncBarrier()
被标记为hide
,普通开发者不能调用。它的作用在与处理一些优先级比较高的任务。比如绘制UI,在ViewRootImpl
的scheduleTraversals()
方法设置了同步屏障,保证了UI绘制优先执行。
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
Message next() {
...
synchronized (this) {
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());
}
...
}
ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//设置同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//发送异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
八、总结
- 消息处理的优先级?
- 消息是怎么存储的?
- 一个线程中
Looper
,Handler
,MessageQueue
数量的对应关系? - 主线程
Looper.loop()
无限循环为什么不会ANR
? - 如何避免使用
Handler
造成的内存泄露? -
IdleHandler
是什么?有什么用? - 消息(
Message
)能没有target(Handler)
吗?
综上,问题基本能够解答,还差个内存泄露问题。造成这个问题的原因是,平时开发中创建Handler
大多是在Activity
中用非静态内部类或者匿名内部类的方式,Handler
持有Activity
的引用,当Activity
被销毁时,如果还有消息msg
没处理,而msg
持有Handler
引用,导致Activity
不能被回收,造成内存泄露。
解决办法是2种(打破这个引用链即可):
- 静态内部类+弱引用
- 当
Activity
销毁时调用handler.removeCallbacksAndMessages(null)
清空没有处理完的消息。
本文参考:
Android消息机制1-Handler(Java层)
面试官:“看你简历上写熟悉 Handler 机制,那聊聊 IdleHandler 吧?
Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
揭秘 Android 消息机制之同步屏障:target==null ?
Android 多线程:你的 Handler 内存泄露 了吗?
网友评论