一、主要角色
1.1、message:消息
分为同步消息、异步消息、屏障消息。但是异步消息和屏障消息的相关API都是隐藏的,需要通过反射才能使用。
1.2、MessageQueue:消息队列
负责消息的存储与管理,负责管理由 Handler 发送过来的 Message。读取会自动删除消息,单链表维护,插入和删除上有优势。在其 next()方法中会无限循环,不断判断是否有消息,有就返回这条消息并移除。
1.3、Looper:消息循环器
负责关联线程以及消息的分发,在该线程下从 MessageQueue 获取 Message,分发给 Handler,Looper 创建的时候会创建一个 MessageQueue,调用 loop()方法的时候消息循环开始,其中会不断调用 messageQueue 的 next()方法,当有消息就处理,否则阻塞在 messageQueue 的 next()方法中。当 Looper 的 quit()被调用的时候会调用messageQueue 的 quit(),此时 next()会返回 null,然后 loop()方法也就跟着退出。
1.4、Handler:消息处理器
负责发送并处理消息,面向开发者,提供 API, 并隐藏背后实现的细节。
二、基本用法
我们可以使用 Handler 发送并处理与一个线程关联的 Message 和 Runnable 。(注意:Runnable 会被封装进一个 Message,所以它本质上还是一个 Message )。
每个 Handler 都会跟一个线程绑定,并与该线程的 MessageQueue 关联在一起,从而实现消息的管理以及线程间通信。
代码语言:java复制Handler handler = new Handler(){
@Override
public void handleMessage(final Message msg) {
//这里接受并处理消息
}
};
//发送消息
handler.sendMessage(message);
handler.post(runnable);
实例化一个 Handler 重写 handleMessage
方法 ,然后在需要的时候调用它的 send
以及 post
系列方法就可以了,非常简单易用,并且支持延时消息。但是奇怪,我们并没有看到任何 MessageQueue 的身影,也没看到它与线程绑定的逻辑,这是怎么回事?
三、原理解析
3.1、整体流程
相信大部分人对Handler的整体工作流程都有所了解了,但是这里还是先简单回顾一下:
第一步:Handler 通过 sendMessage()等系列发送消息 Message 到消息队列 MessageQueue。
第二步:Looper 通过 loop()不断提取触发条件的 Message,并将 Message 交给对应的 target,也就是handler 来处理。
第三步:对应target 调用自身的 handleMessage()方法来处理 Message。
3.2、Handler 与 Looper 的关联
想要了解Handler是怎么和Looper关联的,就要先看一下Handler的构造方法,Handler的显示构造方法有很多种:
代码语言:java复制 public Handler() {
this(null, false);
}
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);
}
Handler这些显示的构造方法最终都会调到Handler的一个隐藏的构造方法,为什么这个最终的构造方法是隐藏的呢,这就涉及到了异步消息和屏障消息相关API了,这个构造方法里有一个参数是async,表示是否是异步,这个API,默认是隐藏的。代码如下:
代码语言:java复制public Handler(Callback callback, boolean async) {
if (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,如果当前线程没有looper,则抛出一个异常
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//获取当前线程looper对象里的消息队列,也就是上面提到的MessageQueue
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
通过上面代码可以看到,在Handler的构造方法里会回去当前线程的looper,并通过获取到的looper对象回去消息队列。也就是说Handler里面持有当前线程的looper和MessageQueue的引用。这样Handler就和looper关联起来了。如果没有获取到当前线程的looper,就会抛出一个异常。
Handler是通过调用Looper.myLooper()方法来获取当前线程的looper的,我们来看下这个方法的实现:
代码语言:java复制public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
可以看到是通过 sThreadLocal.get() 返回的,那么 sThreadLocal 是何许人也,来看一下:
代码语言:java复制static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
我们发现 sThreadLocal 是Looper类里的一个静态的 ThreadLocal 对象,里面存放的就是looper。(ThreadLocal 不了解的可以自己查找下资料)。那么sThreadLocal 里面的looper是什么时候存进去的?通过查看Looper源码,我们发现了秘密:
代码语言:java复制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));
}
原来是在 prepare()方法里直接 创建了一个Looper,然后存了进去。这个方法大家应该都很熟悉吧。
这就是为什么在子线程里面使用Handler的时候,如果不调用Looper.prepare()
方法会抛出异常的原因了(就是上面Handler构造方法里面的异常)。Looper.prepare() 方法只能调用一次,否则会抛异常。
我们平时在主线程使用Handler的时候并没有调用这个方法,怎么没有抛出异常呢,这个我放在后面说。(见4.2)
到这里就明白了,handler是通过Looper和线程关联起来的。
3.3、Message 的存储与管理
Handler 提供了一些列的方法让我们来发送消息,如 send()系列 post()系列 。
不过不管我们调用什么方法,最终都会走到 MessageQueue.enqueueMessage(Message,long)
方法。
//Handler
sendEmptyMessage(int)
-> sendEmptyMessageDelayed(int,int)
-> sendMessageAtTime(Message,long)
-> enqueueMessage(MessageQueue,Message,long)
-> queue.enqueueMessage(Message, long);
来看下enqueueMessage方法的实现:
代码语言:java复制boolean enqueueMessage(Message msg, long when) {
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) {
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) {
// 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;
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;
}
通过上面代码可以发现,MessageQueue内部是通过一个链表来维护的,链表里的每一个节点就是一个Message,链表是通过Message的when参数来排序的。
3.4、Message 的分发与处理
我们已经知道通过Looper.prepare() 创建Looper,然后在调用Looper.loop(),looper就开始工作了。我们现在就来看一下loop()
的具体工作怎么实现的。
代码语言:java复制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;
// 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();
for (;;) {
// 不断从 MessageQueue 获取 消息
Message msg = queue.next(); // might block
//退出 Looper
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " msg.target " "
msg.callback ": " msg.what);
}
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
//将msg交给他对应的target来处理
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (slowDispatchThresholdMs > 0) {
final long time = end - start;
if (time > slowDispatchThresholdMs) {
Slog.w(TAG, "Dispatch took " time "ms on "
Thread.currentThread().getName() ", h="
msg.target " cb=" msg.callback " msg=" msg.what);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " msg.target " " msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
Long.toHexString(ident) " to 0x"
Long.toHexString(newIdent) " while dispatching to "
msg.target.getClass().getName() " "
msg.callback " what=" msg.what);
}
//回收 message
msg.recycleUnchecked();
}
}
从代码可以看出loop()主要工作如下:
第一步:调用 queue.next() 从 MessageQueue 获取消息,如果msg为null,则表示MessageQueue已经推出了,就退出Looper。
第二步:msg.target.dispatchMessage(msg),将msg讲给对应的target处理。
第三步:msg.recycleUnchecked() 回收msg。
来看看MessageQueue.next()的具体实现:
代码语言:java复制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();
}
//1、如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。
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;
//2、msg.target == null 表示当前消息是屏障消息
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());//3、遍历消息链表找到最近的一条异步消息
}
if (msg != null) {
//4、判断消息有没有到处理时间
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;
}
//。。。省略代码
}
}
}
从上面代码可以看出工作流程如下:
第一步:通过 nativePollOnce(ptr, nextPollTimeoutMillis) 判断是否有新的消息要被处理,如果没有则阻塞。
第二步:判断当前消息是不是屏障消息,如果是屏障消息,则遍历消息链表找到最近的一条异步消息,并处理找到的这个异步消息。如果不是屏障消息,则正常处理该消息。需要注意的是,如果设置了屏障消息,那么MessageQueue就会一直循环的找该屏障消息后面有没有异步消息,如果没有,则会阻塞。而不会在处理同步消息。因为屏障消息没有target,因此屏障消息不会被处理。当你使用了屏障消息后,在不使用的时候一定要手动移除该屏障消息。否则,屏障消息后面的同步消息永远不会被处理。屏障消息前面的消息不受影响。
第三步:判断找到的消息有没有到处理时间,如果已到处理时间,则把该消息从链表中移除,并返回该消息给looper。如果没到处理时间,则阻塞。
Looper在收到 MessageQueue 返回的消息后,会调用 msg.target.dispatchMessage(msg) 来处理消息,target 也就是 Handler
代码语言:java复制public void dispatchMessage(Message msg) {
//如果msg设置了callback 就交给callback处理,这里的callback就是封装成的runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果handler设置了mCallback,则优先交给mCallback处理。这个后面还会具体将。
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//回调到 Handler 的 handleMessage 方法
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
从实现代码可以看出:
如果消息是封装的runnable,则调用runnable的run方法。如果handler设置了mCallback(见4.4),则优先交给mCallback处理,如果mCallback处理返回值为false,则handler可以继续处理。
3.5、小结
Handler 的背后有着 Looper 以及 MessageQueue 的协助,三者通力合作,分工明确。
尝试小结一下它们的职责,如下:
- Looper :负责关联线程以及消息的分发在该线程下**从 MessageQueue 获取 Message,分发给 Handler ;
- MessageQueue :是个队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message ;
- Handler : 负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节。
Handler 发送的消息由 MessageQueue 存储管理,并由 Loopler 负责回调消息到 handleMessage()。
线程的转换由 Looper 完成,handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定。
四、 Handler 的延伸问题
Handler 虽然简单易用,但是要用好它还是需要注意一点,另外 Handler相关 还有些鲜为人知的知识技巧,比如 IdleHandler。
由于 Handler 的特性,它在 Android 里的应用非常广泛,比如: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。
这些我会讲解一些,我没讲到的可以自行搜索相关内容进行了解。
4.1、Handler 引起的内存泄露原因以及最佳解决方案
Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。
这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。
解决该问题的最有效的方法是:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并及时移除所有消息。
示例代码如下:
代码语言:javascript复制private static class SafeHandler extends Handler {
private WeakReference<HandlerActivity> ref;
public SafeHandler(HandlerActivity activity) {
this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
HandlerActivity activity = ref.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
复制代码
并且再在 Activity.onDestroy()
前移除消息,加一层保障:
@Override
protected void onDestroy() {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
复制代码
这样双重保障,就能完全避免内存泄露了。
注意:单纯的在 onDestroy
移除消息并不保险,因为 onDestroy
并不一定执行。
4.2、为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?
前面我们提到了每个Handler 的线程都有一个 Looper ,主线程当然也不例外,但是我们不曾准备过主线程的 Looper 而可以直接使用,这是为何?
在 ActivityThread.main() 方法中有如下代码:
代码语言:javascript复制//android.app.ActivityThread
public static void main(String[] args) {
//...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper.prepareMainLooper(); 代码如下:
代码语言:javascript复制public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
可以看到在 ActivityThread 里调用了 Looper.prepareMainLooper() 方法创建了 主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接使用 Handler 了。
注意:Looper.loop()
是个死循环,后面的代码正常情况不会执行。
4.3、主线程的 Looper 不允许退出
如果你尝试退出 Looper ,你会得到以下错误信息:
代码语言:javascript复制Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
at android.os.MessageQueue.quit(MessageQueue.java:415)
at android.os.Looper.quit(Looper.java:240)
why? 其实原因很简单,主线程不允许退出,退出就意味 APP 要挂。
4.4、Handler 里藏着的 Callback 能干什么?
在 Handler 的构造方法中有几个 要求传入 Callback ,那它是什么,又能做什么呢?
来看看 Handler.dispatchMessage(msg)
方法:
public void dispatchMessage(Message msg) {
//这里的 callback 是 Runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果 callback 处理了该 msg 并且返回 true, 就不会再回调 handleMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到 Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg)
方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。
这个就很有意思了,这有什么作用呢?
我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息!
场景:Hook ActivityThread.mH , 在 ActivityThread 中有个成员变量 mH
,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。
4.5、创建 Message 实例的最佳方式
由于 Handler 极为常用,所以为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗。
方法有二:
- 通过 Message 的静态方法
Message.obtain();
获取; - 通过 Handler 的公有方法
handler.obtainMessage();
。
4.6、子线程里弹 Toast 的正确姿势
当我们尝试在子线程里直接去弹 Toast 的时候,会 crash :
代码语言:javascript复制java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
本质上是因为 Toast 的实现依赖于 Handler,按子线程使用 Handler 的要求修改即可,同理的还有 Dialog。
正确示例代码如下:
代码语言:javascript复制new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Toast.makeText(HandlerActivity.this, "不会崩溃啦!", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}).start();
4.7、妙用 Looper 机制
我们可以利用 Looper 的机制来帮助我们做一些事情:
- 将 Runnable post 到主线程执行;
- 利用 Looper 判断当前线程是否是主线程。
完整示例代码如下:
代码语言:javascript复制public final class MainThread {
private MainThread() {
}
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
public static void run(@NonNull Runnable runnable) {
if (isMainThread()) {
runnable.run();
}else{
HANDLER.post(runnable);
}
}
public static boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
4.8、sendMessageDelayed原理
- sendMessageDelayed 最终是调用的 sendMessageAtTime
- 消息队列的插入是由msg.when顺序排列。如果当前的消息没有到执行时间,其后的也一定不会到,当前的系统时间小于msg.when,那么会计算一个timeout,在到执行时间时wake up。
4.9、只能保证在when之前消息不被处理,不能够保证一定在when时被处理
- 在Loop.loop()中是顺序处理消息,如果前一个消息处理耗时较长,完成之后已经超过了when,消息不可能在when时间点被处理。
- 即使when的时间点没有被处理其他消息所占用,线程也有可能被调度失去cpu时间片。
- 在等待时间点when的过程中有可能入队处理时间更早的消息,会被优先处理,又增加了(1)的可能性。
所以由上述三点可知,Handler提供的指定处理时间的api诸如postDelayed()/postAtTime()/sendMessageDelayed()/sendMessageAtTime(),只能保证在指定时间之前不被执行,不能保证在指定时间点被执行。
4.10、主线程的死循环一直运行是不是特别消耗CPU资源呢?
并不是,这里就涉及到 Linux pipe/epoll 机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next()中的 nativePollOnce方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的epoll 机制,是一种 IO 多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读 或写就绪),则立刻通知相应程序进行读或写操作,本质是同步 I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量 CPU 资 源。
4.11、handler postDelay 这个延迟是怎么实现的?
handler.postDelay 并不是先等待一定的时间再放入到 MessageQueue 中,而是直接进入MessageQueue,以 MessageQueue 的时间顺序排列和唤醒的方式结合实现的。
五、总结
由前文可得出一些知识点,汇总一下,方便记忆。
- Handler 的背后有 Looper、MessageQueue 支撑,Looper 负责消息分发,MessageQueue 负责消息管理;
- 在创建 Handler 之前一定需要先创建 Looper;
- Looper 有退出的功能,但是主线程的 Looper 不允许退出;
- 异步线程的 Looper 需要自己调用
Looper.myLooper().quit();
退出; - Runnable 被封装进了 Message,可以说是一个特殊的 Message;
Handler.handleMessage()
所在的线程是 Looper.loop() 方法被调用的线程,也可以说成 Looper 所在的线程,并不是创建 Handler 的线程;- 使用内部类的方式使用 Handler 可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类;
六、参考链接
Android中为什么主线程不会卡死
https://juejin.cn/post/6844903783139393550