详解 Handler 消息处理机制(附自整理超全 Q&A)
android 为什么要用消息处理机制
如果有多个线程更新 ui,并且没有加锁处理,会导致界面更新的错乱,而如果每个更新操作都进行加锁处理,那么必然会造成性能的下降。所以在 android 开发中,为了使 ui 操作是线程安全的,规定只许主线程即 ui 线程可以更新 ui 组件。但实际开发中,常常会遇到多个线程并发操作 ui 组件的需求,于是 android 提供了一套消息传递与处理机制来解决这个问题。也就是在非主线程需要更新 ui 时,通过向主线程发送消息通知,让主线程更新 ui。
当然消息处理机制不仅仅可以用来解决 ui 线程安全问题,它同时也是一种线程之间沟通的方式。
looper
looper 即消息循环器,是消息处理机制的核心,它可以将一个普通线程转换为一个 looper 线程。所谓的 looper 线程就是一个不断循环的线程,线程不断循环的从 messagequeue 中获取 message,交给相应的 handler 处理任务。在 looper 类的注释介绍中,我们可以得知通过两个静态方法 looper.prepare()
和 looper.loop()
就可以将线程升级成 looper 线程:
class looperthread extends thread { public handler mhandler; public void run() { // 将当前线程初始化为looper线程 looper.prepare(); // 注意:一定要在两个方法之间创建绑定当前looper的handler对象, // 否则一旦线程开始进入死循环就没法再创建handler处理message了 mhandler = new handler() { public void handlemessage(message msg) { // 处理收到的消息 } }; // 开始循环处理消息队列 looper.loop(); } }
looper.prepare()
该方法在线程中将 looper 对象定义为 threadlocal 对象,使得 looper 对象成为该线程的私有对象,一个线程最多仅有一个 looper。并在这个 looper 对象中维护一个消息队列 messagequeue 和持有当前线程的引用,因此 messagequeue 也是线程私有。
public final class looper { // 每个线程仅包含一个的looper对象,定义为一个线程本地存储(tls)对象 static final threadlocal<looper> sthreadlocal = new threadlocal<looper>(); // 每个looper维护一个唯一的消息队列 final messagequeue mqueue; // 持有当前线程引用 final thread mthread; private looper(boolean quitallowed) { mqueue = new messagequeue(quitallowed); mthread = thread.currentthread(); } public static void prepare() { prepare(true); } // 该方法会在调用线程的tls中创建looper对象,为线程私有 private static void prepare(boolean quitallowed) { if (sthreadlocal.get() != null) { // 试图在有looper的线程中再次创建looper对象将抛出异常 throw new runtimeexception( "only one looper may be created per thread"); } sthreadlocal.set(new looper(quitallowed)); } }
looper.loop()
该方法启动线程的循环模式,从 looper 的 messagequeue 中不断的提取 message,再交由 handler 处理任务,最后回收 message 供以后复用。
public static void loop() { // 得到当前线程的looper对象 final looper me = mylooper(); if (me == null) { // 线程没有调用looper.prepare(),所以没有looper对象 throw new runtimeexception( "no looper; looper.prepare() wasn't called on this thread."); } // 得到当前消息队列 final messagequeue queue = me.mqueue; ... // 开始循环 for (;;) { // 从消息队列中获取下一个message,该方法可以被阻塞 message msg = queue.next(); ... // 将message推送到message中的target处理 // 此处的target就是发送该message的handler对象 msg.target.dispatchmessage(msg); ... // 回收message,这样就可以通过message.obtain()复用 msg.recycleunchecked(); } }
handler
handler 可以称之为消息处理者。looper 线程不断的从消息队列中获取消息,而向消息队列中推送消息的正是 handler 这个类。
handler 在工作线程通过 sendmessage()
向 messagequeue 中推送 message,而主线程 looper 循环得到 message 后,即可得到发出该 message 的 handler 对象(handler 发送消息时将自身引用赋值给 message.target
),再通过 handler 对象的 dispatchmessage()
和 handlemessage()
方法处理相应的任务。这样我们就可以通过 handler 同时完成了异步线程的消息发送与消息处理两个功能。
handler 默认构造方法会关联当前线程的 looper 对象,一个线程只能有一个 looper 对象,但可以有多个 handler 关联这个 looper 对象。handler 也提供一些构造方法关联自定义的 looper 对象。
public class handler { final looper mlooper; final messagequeue mqueue; final callback mcallback; public handler() { this(null, false); } public handler(boolean async) { this(null, async); } public handler(callback callback) { this(callback, false); } public handler(looper looper) { this(looper, null, false); } public handler(looper looper, callback callback) { this(looper, callback, false); } /** * @hide */ public handler(callback callback, boolean async) { if (find_potential_leaks) { // 如果打开了find_potential_leaks开关 // 会判断是否是非静态匿名内部类、非静态成员内部类或非静态局部内部类 // 如果是,可能发生内存泄漏,发出警告 final class<? extends handler> klass = getclass(); if ((klass.isanonymousclass() || klass.ismemberclass() || klass.islocalclass()) && (klass.getmodifiers() & modifier.static) == 0) { log.w(tag, "the following handler class should be static or leaks might occur: " + klass.getcanonicalname()); } } // 关联到当前线程的looper mlooper = looper.mylooper(); if (mlooper == null) { // 当前线程未调用looper.prepare(),不是looper线程 throw new runtimeexception( "can't create handler inside thread " + thread.currentthread() + " that has not called looper.prepare()"); } // 持有当前looper中消息队列的引用 mqueue = mlooper.mqueue; mcallback = callback; // 设置是否是异步消息 masynchronous = async; } /** * @hide */ public handler(looper looper, callback callback, boolean async) { // 指定了looper对象,handler处理消息就会在该looper对应的线程中执行 mlooper = looper; mqueue = looper.mqueue; mcallback = callback; masynchronous = async; } }
sendmessage()
handler 的功能之一就是向消息队列发送消息,其有多个方法可以实现这个功能,如 post()
、postattime()
、postdelayed()
、sendmessage()
、sendmessageattime()
、sendmessagedelayed()
等等。post 一类的方法发送的是 runnable 对象,send 一类的方法发送的是 message 对象,但实际上 runnable 对象最后都会封装成 message 对象。
public final boolean post(runnable r) { return sendmessagedelayed(getpostmessage(r), 0); } public final boolean postattime(runnable r, long uptimemillis) { return sendmessageattime(getpostmessage(r), uptimemillis); } public final boolean postdelayed(runnable r, long delaymillis) { return sendmessagedelayed(getpostmessage(r), delaymillis); } // 将runnable封装成message对象 private static message getpostmessage(runnable r) { message m = message.obtain(); // 将runnable设为message的callback变量 m.callback = r; return m; }
再看 sendmessage 类方法的源码:
public final boolean sendmessage(message msg) { return sendmessagedelayed(msg, 0); } public final boolean sendmessagedelayed(message msg, long delaymillis) { if (delaymillis < 0) { delaymillis = 0; } return sendmessageattime(msg, systemclock.uptimemillis() + delaymillis); } public boolean sendmessageattime(message msg, long uptimemillis) { messagequeue queue = mqueue; if (queue == null) { runtimeexception e = new runtimeexception( this + " sendmessageattime() called with no mqueue"); log.w("looper", e.getmessage(), e); return false; } return enqueuemessage(queue, msg, uptimemillis); } private boolean enqueuemessage(messagequeue queue, message msg, long uptimemillis) { // 核心代码,sendmessage一路调用到此,让message持有当前handler的引用 // 当消息被looper线程轮询到时,可以通过target回调handler的handlemessage方法 msg.target = this; if (masynchronous) { msg.setasynchronous(true); } // 将message加入messagequeue中,并根据消息延迟排序 return queue.enqueuemessage(msg, uptimemillis); }
dispatchmessage()
、handlemessage()
handler 的另外一个主要功能就是处理消息。处理消息的核心代码即是 looper.loop()
中的msg.target.dispatchmessage(msg)
以及 handler 子类重写实现的 handlemessage()
回调方法。源码如下:
public interface callback { public boolean handlemessage(message msg); } // 处理消息,该方法由looper的loop方法调用 public void dispatchmessage(message msg) { if (msg.callback != null) { // 处理runnable类的消息 handlecallback(msg); } else { if (mcallback != null) { // 这种方法允许让activity等来实现handler.callback接口 // 避免了自己定义handler重写handlemessage方法 if (mcallback.handlemessage(msg)) { return; } } // 根据handler子类实现的回调方法处理消息 handlemessage(msg); } } // 处理runnbale类消息的方法 private static void handlecallback(message message) { // 直接调用message中封装的runnable的run方法 message.callback.run(); } // 由handler子类实现的回调方法 public void handlemessage(message msg) { }
通过上面对 handler 核心源码的分析,我们可以看到其在异步线程消息处理机制中的重要作用。任何持有 handler 引用的其他线程都可以发送消息,这些消息都发送到 handler 内部关联的 messagequeue 中,而 handler 是在 looper 线程中创建的(必须在 looper.prepare()
调用之后,looper.loop()
调用之前创建),所以 handler 是在其关联的 looper 线程中处理取到的消息。
正是由于 handler 这种机制解决了 android 在非主线程(ui 线程)更新 ui 的问题。由于 android 主线程也是一个 looper 线程,在主线程创建的 handler 默认将关联到主线程的 messagequeue。我们可以在 activity 中创建 handler,并将其引用传递给工作线程,在工作线程执行完任务后,通过 handler 发送消息通知 activity 更新 ui。
message
在消息处理机制中,message 扮演的角色就是消息本身,它封装了任务携带的额外信息和处理该任务的 handler 引用。
message 虽然有 public 构造方法,但是还是建议使用 message.obtain()
方法从一个全局的消息池中得到空的 message 对象,这样可以有效的节省系统资源。handler 有一套 obtain 方法,其本质是其实是调用 message 的一套 obtain 方法,最终都是调用 message.obtain()
。
另外可以利用 message.what
来区分消息类型,以处理不同的任务。在需要携带简单的 int 额外信息时,可以优先使用 message.arg1
和 message.arg2
,这比用 bundle 封装信息更省资源。
messagequeue
messagequeue.enqueuemessage()
这个方法是所有消息发送方法最终调用的终点,也就是说无论怎么发送消息,都会直接插入到对应的消息队列中去。并且在插入后还会根据一些判断,来决定是否唤醒阻塞的队列。
boolean enqueuemessage(message msg, long when) { ... synchronized (this) { // 如果发送消息所在的线程已经终止,则回收消息,返回消息插入失败的结果 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; if (p == null || when == 0 || when < p.when) { // 队列里无消息,或插入消息的执行时间为0(强制插入队头),或插入消息的执行 // 时间先于队头消息,这三种情况下插入消息为新队头,如果队列被阻塞则将其唤醒 msg.next = p; mmessages = msg; needwake = mblocked; } else { // 根据执行时间将消息插入到队列中间。通常我们不必唤醒事件队列,除非 // 队列头部有消息屏障阻塞队列,并且插入的消息是队列中第一个异步消息 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; prev.next = msg; } // we can assume mptr != 0 because mquitting is false. if (needwake) { // native方法唤醒等待的线程 nativewake(mptr); } } return true; }
messagequeue.next()
该方法可以从消息队列中取出一个需处理的消息,在没有消息或者消息还未到时时,该方法会阻塞线程,等待消息通过 messagequeue.enqueuemessage()
方法入队后唤醒线程。
message next() { ... int pendingidlehandlercount = -1; // -1 only during first iteration int nextpolltimeoutmillis = 0; for (;;) { if (nextpolltimeoutmillis != 0) { binder.flushpendingcommands(); } // 通过native层的messagequeue阻塞nextpolltimeoutmillis毫秒时间 nativepollonce(ptr, nextpolltimeoutmillis); synchronized (this) { // 尝试检索下一个消息,如果找到则返回该消息 final long now = systemclock.uptimemillis(); message prevmsg = null; message msg = mmessages; if (msg != null && msg.target == null) { // 被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; } ... // 如果第一次遇到空闲状态,则获取要运行的idlehandler数量 // 仅当队列为空或者队列中的第一条消息(可能是同步屏障) // 还没到执行时间时,才会执行idlehandler if (pendingidlehandlercount < 0 && (mmessages == null || now < mmessages.when)) { pendingidlehandlercount = midlehandlers.size(); } if (pendingidlehandlercount <= 0) { // 如果没有idlehandler需要执行,那么就阻塞等待下一个消息到来 mblocked = true; continue; } if (mpendingidlehandlers == null) { mpendingidlehandlers = new idlehandler[math.max(pendingidlehandlercount, 4)]; } mpendingidlehandlers = midlehandlers.toarray(mpendingidlehandlers); } // 运行idlehandler // 执行一次next方法只有第一次轮询能执行这里的操作 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 { // 执行idlehandler,返回是否保留idlehandler keep = idler.queueidle(); } catch (throwable t) { log.wtf(tag, "idlehandler threw exception", t); } if (!keep) { synchronized (this) { // 如果不需要保留,则移除这个idlehandler midlehandlers.remove(idler); } } } // 设置为0保证在再次执行next方法之前不会再执行idlehandler pendingidlehandlercount = 0; // 当调用一个idlehandler执行后,无需等待直接再次检索一次消息队列 nextpolltimeoutmillis = 0; } }
上面源码里面的方法 nativepollonce(ptr, nextpolltimeoutmillis)
是一个 native 方法,实际作用就是通过 native 层的 messagequeue 阻塞 nextpolltimeoutmillis 毫秒的时间。
- 如果 nextpolltimeoutmillis = -1,一直阻塞不会超时。
- 如果 nextpolltimeoutmillis = 0,不会阻塞,立即返回。
- 如果 nextpolltimeoutmillis > 0,最长阻塞 nextpolltimeoutmillis 毫秒(超时),如果期间有程序唤醒会立即返回。
q&a
1. threadlocal 是什么?
threadlocal 可以包装一个对象,使其成为线程私有的局部变量,通过 threadlocal 的 get 和 set 方法来访问这个线程局部变量,而不受到其他线程的影响。threadlocal 实现了线程之间的数据隔离,同时提升同一线程中该变量访问的便利性。
2. post
和 sendmessage
两类发送消息的方法有什么区别?
post
一类的方法发送的是 runnable 对象,但是其最后还是会被封装成 message 对象,将 runnable 对象赋值给 message 对象中的 callback 变量,然后交由sendmessageattime()
方法发送出去。在处理消息时,会在dispatchmessage()
方法里首先被handlecallback(msg)
方法执行,实际上就是执行 message 对象里面的 runnable 对象的run
方法。而
sendmessage
一类的方法发送的直接是 message 对象,处理消息时,在dispatchmessage
里优先级会低于handlecallback(msg)
方法,是通过自己重写的handlemessage(msg)
方法执行。
3. 为什么要通过 message.obtain()
方法获取 message 对象?
obtain
方法可以从全局消息池中得到一个空的 message 对象,这样可以有效节省系统资源。同时,通过各种 obtain 重载方法还可以得到一些 message 的拷贝,或对 message 对象进行一些初始化。
4. handler 实现发送延迟消息的原理是什么?
我们常用
postdelayed()
与sendmessagedelayed()
来发送延迟消息,其实最终都是将延迟时间转为确定时间,然后通过sendmessageattime()
->enqueuemessage
->queue.enqueuemessage
这一系列方法将消息插入到 messagequeue 中。所以并不是先延迟再发送消息,而是直接发送消息,再借由 messagequeue 的设计来实现消息的延迟处理。消息延迟处理的原理涉及 messagequeue 的两个静态方法
messagequeue.next()
和messagequeue.enqueuemessage()
。通过 native 方法阻塞线程一定时间,等到消息的执行时间到后再取出消息执行。
5. 为什么主线程不会因为 looper.loop()
里的死循环卡死?
主线程确实是通过
looper.loop()
进入了循环状态,因为这样主线程才不会像我们一般创建的线程一样,当可执行代码执行完后,线程生命周期就终止了。而对于 cpu 来说无论是进程还是线程都是一段可执行代码,cpu 采用 cfs 调度算法,保证每个执行任务都尽可能公平的享有 cpu 时间片段。所以只要创建了其他的新线程处理事务,主线程的循环就不会导致系统卡死。这里有两个问题:何时创建了其他的新线程运转?主线程的循环会不会消耗大量的 cpu 资源?
何时创建了其他的新线程运转?
实际上运行在主线程的 activitythread 的main()
方法中就创建了新的 binder 线程(即 applicationthread,用于接受系统服务 ams 发来的事件):public static void main(string[] args) { ... looper.preparemainlooper(); ... activitythread thread = new activitythread(); // 这一句创建了一个binder线程 // 这个线程可以通过 activitythread 里的 handler 将消息发送给主线程 thread.attach(false, startseq); ... looper.loop(); throw new runtimeexception("main thread loop unexpectedly exited"); }一些 activity 生命周期的消息就是通过这个机制在循环外执行起来的。比如一条暂停 activity 的消息,首先是处于系统 system_server 进程的 activitymanagerservice(ams)线程调用 applicationthreadproxy(atp)线程,接着 atp 线程通过 binder 机制将消息传输到 app 进程中的 applicationthread(at)线程,最后 at 线程通过 handler 消息机制,将消息发送到了主线程的消息队列中,主线程通过
looper.loop()
遍历得到该消息后,将消息分发给了 activitythread 对应的 handler 中处理,最后调用到了 activity 的onpause()
方法,方法处理完后,继续主线程继续循环下去。
主线程的循环会不会消耗大量的 cpu 资源?
这里就涉及到 linux pipe/epoll 机制。在主线程的 messagequeue 没有消息时,便阻塞在messagequeue.next()
中的nativepollonce()
方法里,此时主线程会释放 cpu 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种 io 多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,即读写是阻塞的。所以主线程大多数时候都是处于休眠状态,并不会消耗大量 cpu 资源。
6. 同步屏障 syncbarrier 是什么?有什么作用?
message 分为两类:同步消息、异步消息。在一般情况下,两类消息处理起来没有什么不同。只有在设置了同步屏障后才会有差异。同步屏障从代码层面上看是一个 message 对象,但是其 target 属性为 null,用以区分普通消息。在
messagequeue.next()
中如果当前消息是一个同步屏障,则跳过后面所有的同步消息,找到第一个异步消息来处理。我们可以通过 messagequeue 对象的
postsyncbarrier()
发送一个同步屏障,通过removesyncbarrier(token)
移除同步屏障android 应用框架中为了更快的响应 ui 刷新事件在
viewrootimpl.scheduletraversals()
中使用了同步屏障void scheduletraversals() { if (!mtraversalscheduled) { mtraversalscheduled = true; // 设置同步障碍,确保mtraversalrunnable优先被执行 mtraversalbarrier = mhandler.getlooper().getqueue().postsyncbarrier(); // 内部通过handler发送了一个异步消息 mchoreographer.postcallback( choreographer.callback_traversal, mtraversalrunnable, null); if (!munbufferedinputdispatch) { scheduleconsumebatchedinput(); } notifyrendererofframepending(); pokedrawlockifneeded(); } }上面源码中的任务 mtraversalrunnable 调用了
performtraversals()
执行measure()
、layout()
、draw()
等方法。为了让 mtraversalrunnable 尽快被执行,在发消息之前调用 messagequeue 对象的postsyncbarrier()
设置了一个同步屏障。
7. idlehandler 是什么?有什么作用?
/** * 发现线程阻塞等待更多消息时,回调的接口 */ public static interface idlehandler { /** * 当消息队列没有消息并等待更多消息时调用。 * 返回true以保持idlehandler处于有效状态,返回false则将其删除。 * 如果队列中仍有待处理的消息,但都未到执行时间时,也会调用此方法。 */ boolean queueidle(); }可以看出 idlehandler 只有当消息队列没有消息时或者是队列中的消息还未到执行时间时才会执行,idlehandler 保存在 mpendingidlehandler 队列中。
queueidle()
方法如果返回 false,那么这个 idlehandler 只会执行一次,执行完后会从队列中删除,如果返回 true,那么执行完后不会被删除,只要执行messagequeue.next()
时消息队列中没有可执行的消息,即为空闲状态,那么 idlehandler 队列中的 idlehandler 还会继续被执行。比如我们想实现一个 android 绘制完成的回调方法,android 本身提供的 activity 框架和 fragment 框架并没有提供绘制完成的回调,如果我们自己实现一个框架,就可以使用 idlehandler 来实现一个 onrenderfinished 这种回调了,可以有效的优化启动时间等需求。
8. handlerthread 是什么?
handlerthread 继承自 thread ,所以本质上,它是一个 thread。它与普通的 thread 的差别在于其建立了一个线程的同时创建了一个含有消息队列的 looper,并对外提供这个 looper 对象供 handler 关联,让我们可以在该线程中分发和处理消息。当我们想让一个线程和主线程一样具备消息循环机制时,就可以使用这个类。
9. 能否在子线程更新 ui ?为什么 oncreate()
中的子线程更新 ui 有时可以成功?
因为 android 的 ui 访问是没有加锁的,这样多线程操作 ui 是不安全的,会导致界面错乱,所以 android 系统限制了只能在主线程访问 ui,其他线程直接操作 ui 会抛出异常。
如果在
oncreate()
中新建一个线程立刻执行操作 ui,结果却可以正常运行,但如果延迟一段时间执行仍旧抛出异常。这是因为检测子线程的方法checkthread()
是在 viewrootimpl 中被调用,而 viewrootimpl 在 activitythread 执行了handleresumeactivity()
时被添加,也就是对应的是onresume()
。所以在oncreate()
时根本不会执行checkthread()
方法做判断。
10. 为什么非静态类的 handler 导致内存泄漏?如何解决?
首先,非静态的内部类、匿名内部类、局部内部类都会隐式的持有其外部类的引用。也就是说在 activity 中创建的 handler 会因此持有 activity 的引用。
当我们在主线程使用 handler 的时候,handler 会默认绑定这个线程的 looper 对象,并关联其 messagequeue,handler 发出的所有消息都会加入到这个 messagequeue 中。looper 对象的生命周期贯穿整个主线程的生命周期,所以当 looper 对象中的 messagequeue 里还有未处理完的 message 时,因为每个 message 都持有 handler 的引用,所以 handler 无法被回收,自然其持有引用的外部类 activity 也无法回收,造成泄漏。
解决的办法:
使用静态内部类 + 弱引用的方式
静态类不会持有外部类的的引用,当需要引用外部类相关操作时,可以通过弱引用来获取到外部类。当一个对象只有弱引用时,是可以被垃圾回收掉的。private handler shandler = new testhandler(this); static class testhandler extends handler { private weakreference<activity> reference; testhandler(activity activity) { reference = new weakreference<>(activity); } @override public void handlemessage(message msg) { super.handlemessage(msg); activity activity = reference.get(); if (activity != null && !activity.isfinishing()) { switch (msg.what) { // 处理消息 } } } }
在外部类对象被销毁时,将消息队列清空
@override protected void ondestroy() { handler.removecallbacksandmessages(null); super.ondestroy(); }
参考资料
android 异步消息处理机制 让你深入理解 looper、handler、message 三者关系
android 异步消息处理机制完全解析,带你从源码的角度彻底理解
android 消息处理机制(looper、handler、messagequeue、message)