Handler 流程源码解析
用了三四年的 Handler,一直就没有真正理解过 Handler 到底是个什么东西,从开始认识它,就一直以为记住 Handler 是用来发送和处理消息的;Message 是消息,Handler 发送和处理的对象;Looper 是用来管理消息队列的;MessageQueue 是消息队列,仅此而已。
我曾经天真的以为我记住这些就能用好了,但是当我遇到了一个让我郁闷的一个 BUG,具体错误信息我忘记了,反正就是找不到 looper 之类的错误,当时我就懵逼了,不知道如何解决,没办法只能求助度娘。
在主席(任玉刚)的《开发艺术探索》中,关于 Handler 的解析还是分模块解析的,先解释 MessageQueue 的工作原理,然后是 Looper 的工作原理,最后是 Handler 的工作原理,瞬间人生三大拷问有木有,或许这样更适合他吧,但是我还是比较喜欢的是整体的理解,这也正是我为什么写这篇文章的原因,来让我们对 Handler 有一个整体的认识。
说一嘴不相关的,Handler 的使用我们经常这么写:
var mHandler = object : Handler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
Log.i("message", "我是一个handler")
}
}
fun sendMessage() {
mHandler.sendMessage(null)
}
但是这样会导致一个问题,那就是可能会内存泄漏,那么我么如何规避这个问题呢,我比较推荐使用弱引用来处理:
fun sendMessage() {
Handler.sendMessage(null)
}
val Handler = MyHandler()
class MyHandler : Handler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
Log.i("message", "我是一个handler")
}
}
1. 关于创建
好了,我们现在进入正题,我们先从 Handler 的创建来一步一步的分析,我们来看看它的构造:
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());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
我们只需要关注这几行
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
从这里我们能看出,在我们创建 Handler 的时候,会通过 myLooper 方法获取当前线程保存的 Looper 的实例,为什么这么说呢,而在新建 Looper 的时候,系统又为我们做了什么呢,我们来看下源码就明白了:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
调用了 sThreadLocal.get(),那么既然出现了 get 方法,那么一定会有 set 方法了,但是这个方法又在哪里执行的呢,我们来看下在创建 Handler 的时候出现的一个关于 Looper 的报错信息:throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); 很显然,如果 Looper 是空的,是不能执行 Looper.prepare 方法的,我们来看下这个方法是干嘛的:
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));
}
哦~原来这个 set 方法是在这里,是新建了一个 Looper,然后和一个叫 ThreadLocal 的线程绑定了,通过异常我们知道,在一个线程中,只能有一个 Looper。这时候你是不是会问了,我先执行的是 myLooper 方法,也就是 ThreadLocal 的 get 方法,然后才执行的 prepare 方法,也就是 set 方法,那么 get 获取的肯定是空的啊,那我创建的 Handler 中不照样是一个空的 Looper,为什么没有抛出异常呢,其实呢,这个通过源码也是可以找到答案的,我们来看下它的 get 和 set 方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
原来是进行了一次判断,如果不存在这个线程,那么会创建一个,如果存在,则进行其他的处理。
好,我们回到 Looper.prepare 的方法中,在执行 set 方法的时候,传入了一个 Looper 实例,我们来看下在 Looper 的构造中做了哪些处理:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
哇!竟然和 MessageQueue 绑定了,这时候,我们或许明白了一部分了,我们来总结下:
(1)创建 Handler 的时候,我们通过 myLooper 方法获取当前线程保存的实例,然后通过 mQueue = mLooper.mQueue 获取 Looper 相关的 MessageQueue 队列。
(2)在创建 Looper 的时候,我们创建了一个 MessageQueue 的队列,然后知道了通过 set 和 get 方法来处理线程和 Looper 的关系。
2. 关于插入数据
我们继续往下走,来看下 sendMessage 为我们做了哪些事情:
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);
}
跟随源码,我们找到这个方法 sendMessageAtTime,然后获取到了 MessageQueue 的实例,调用了 enqueueMessage 方法,那么 enqueueMessage 到底是干什么的呢,我们继续往下看:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
到这里,我们发现竟然多出了一个属性 meg.target 被赋值了,那么这个属性到底有啥用呢?我们继续往下看,这里调用了 MessageQueue 的 enqueueMessage 方法,源码如下:
boolean enqueueMessage(Message msg, long when) {
//消息的所属target不为空
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//如果该消息已经设置为inUse标签则不可再次加入,道理就是同一个Message不可用两次
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;
}
//设置消息的inUse标签
msg.markInUse();
//赋值执行时间
msg.when = when;
//获取下一个将要执行的Message
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 {
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;
}
原来,这个方法是把 Handler 发送的消息插入到 MessageQueue 队列中,这里我就不过多解释了,我们继续往下看。
3.关于取数据
关于怎么从 MessageQueue 中读取数据,然后把数据给 Handler,我们需要先来看另外一个方法,既然说了 Looper 是用来管理队列的,那么他就不可能创建一个 MessageQueue 队列就一遍歇着去了,哪能那么简单,我们来看下 Looper 的 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.");
}
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 (;;) {
Message msg = queue.next(); // might block
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.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);
}
msg.recycleUnchecked();
}
}
这时候我们通过 myLooper 方法得到一个 sThreadLocal 中保存的一个 Looper 对象,然后得到 Looper 中存在的 MessageQueue 队列,之后调用 MessageQueue 的 next 方法开启我们读取消息的旅程。
Message next() {
//获取native的MessageQueue的实例引用
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//额外处理消息的个数
int pendingIdleHandlerCount = -1;
//阻塞时间:-1是一直阻塞不超时;0是不会阻塞,立即返回;大于0则nextPollTimeoutMillis是最长阻塞时间,期间有线程唤醒立即返回
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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) {
//如果当前时间还没到Message的执行时间
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;
}
//初始化mPendingIdleHandlers
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);
}
}
}
//归零
pendingIdleHandlerCount = 0;
//阻塞时长归零
nextPollTimeoutMillis = 0;
}
}
具体的也就不说了,注释已经说明,根据上面源码我们发现如果 msg.target 为空,那么就循环找出第一个 Message,那么它什么时候是空的呢?
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
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;
}
}
这个方法直接在MessageQueue中插入了一个Message,并且未设置target,它的作用是插入一个消息屏障,这个屏障之后的所有同步消息都不会被执行,即使时间已经到了也不会执行。
可以通过public void removeSyncBarrier(int token)
来移除这个屏障,参数是post方法的返回值。
这些方法是隐藏的或者是私有的,具体应用场景可以查看ViewRootImpl中的void scheduleTraversals()
方法,它在绘图之前会插入一个消息屏障,绘制之后移除。
回到之前的next方法,如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息),可以通过setAsynchronous(boolean async)设置为异步消息。
继续往下,如果有消息需要处理,先判断时间有没有到,如果没到的话设置一下阻塞时间nextPollTimeoutMillis,进入下次循环的时候会调用nativePollOnce(ptr, nextPollTimeoutMillis);
阻塞;
否则把消息返回给调用者,并且设置mBlocked = false代表目前没有阻塞。
如果阻塞了有两种方式唤醒,一种是超时了,一种是被主动唤醒了。根据生产消费模式,生产者有产品的时候一般情况下会唤醒消费者。那么MessageQueue入队列的时候应该会去唤醒,下面看一下MessageQueue入队列的方法,截取了主要逻辑:
(以上内容来自:https://blog.csdn.net/kisty_yao/article/details/71191175 本来想自己总结一部分,但是感觉有用的比较多,就直接复制了。)
我们发现不管是插入数据或者是读取数据,都有一个属性 msg.target 一直伴随我们所有,从最初 Handler 执行 enqueueMessage 到 MessageQueue 执行 next 方法,它一直伴随我们左右,那么它到底是什么呢?其实它就是一个 Handler,Looper的loop方法会取出每个 msg 然后交给 msg.arget.dispatchMessage(msg) 去处理消息,而 msg.arget.dispatchMessage 正是调用了我们重写的 handleMessage 方法,这时候我们就把消息又重新交给 Handler 来处理了。
上一篇: 源码分析:Handler发送延时消息
下一篇: 源码解析 Handler机制
推荐阅读
-
spring5 源码深度解析----- 事务的回滚和提交(100%理解事务)
-
Spring5源码解析5-ConfigurationClassPostProcessor (上)
-
spring5 源码深度解析----- AOP代理的生成
-
Spring5源码解析4-refresh方法之invokeBeanFactoryPostProcessors
-
vue如何实现observer和watcher源码解析
-
Vue监听数组变化源码解析
-
Laravel框架源码解析之模型Model原理与用法解析
-
groupcache源码解析-概览
-
源码解析Spring 数据库异常抽理知识点总结
-
.NET Core源码解析配置文件及依赖注入