#再论Handler—Handler原理解析
标签(空格分隔): Android架构师之路
Handler,无论你菜鸟还是像我一样的老菜鸟,都无法避免这个亘古不灭的话题,只有了解学会Handler的原理,你才能变成一个出的厅堂下得厨房的优秀的老菜鸟。其实同类优秀的帖子已经很多了,但是为了强化记忆我的理解,还是想再次记一下,我心中的Handler。
要介绍Handler,就必须要介绍与它息息相关甚至“同为一体”的其它几个类:Looper、MessageQueue、Message。所以本篇文章就分为三个部分:
Message是定义一个包含描述和可以发送到Handler的任意数据对象的消息。
我们先看Message的几个关键变量:
target,就是负责发送且分发它的Handler;
callback,是一个Runnable回调;
next,下一个消息池中的它之下的message。
现在有3个问题:
我们先不着急给出答案,接着往下看。
它持有并维护了当前线程的Looper分发的消息列表。消息并不是直接而是通过Handler 关联的Looper入队列的。
MessageQueue看上去是消息队列,然而它实际上只是消息队列的管理者。消息列表是它持有的Message mMessage,是由Message组成的单链表结构。
我们看它管理消息的两个核心方法:
先上代码:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
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;
}
可以看到,在第一行就校验了msg.target是否为null,由此可知,msg必须关联(持有)负责分发它的handler;
最重要的是for循环里入队的这部分,通过逐个和消息列表里消息的发生时间对比,找到与当前入队的message的 -分发时间-之前与它时间最接近的preMsg,将mesage设置到preMsg的next(此处也解答了问题3:message的next是什么),最终得到的是一个按时间先后顺序排列的链表;
此外,入队操作还做了一件事情:当消息入队时候,如果队列为空的或需要立即分发或当前消息分发时间-早于消息队列中最近一个需要分发的消息的时间的(而此时MessageQueue的next()方法的循环是处于阻塞状态的),就调用一个native方法nativeWake()唤醒取出消息的next()(next方法的循环会被nativePollOnce()native方法阻塞)循环。
先上源码
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();
}
//-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-找出下一个需要分发的消息。
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) {
//-3- 如果时机未到,即当前时间还不到消息的处理时间,就计算休眠时间,
//反之,返回这个待处理的消息。并将休眠状态mBlocked置为false
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;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
//-4-只有当消息队列是空的或者现在还不到待处理的消息的时间的时候,idleHanler(空闲handler)才会被执行
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//-5-如果没有空闲Handler待处理(消息队列也没有需要处理的message),那么,标记一下循环被阻塞了。
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
//-5-将idleHandler列表转为数组保存
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
//-6-开始处理IdleHandler的事务。
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 {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
//-7-如果IdleHandler的queueIdle返回false,就不需要保持,这执行完一次任务后直接移除,反之,
//后面每次空闲的时候都执行
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
看到这段代码,愈发觉得谷歌工程是真的是厉害,不知道秃到什么程度才能写出这么精妙的代码,哈哈哈哈哈。由于这段代码我看着太喜欢了,就按照注释逐个解释:
此时,突然有一个问题——循环阻塞在这里了,主线程不就卡住了嘛?为什么没有ANR?
具体原理,可以查看这篇帖子还有这篇帖子等等很多优质讲解。简而言之调用native的nativePoolOnce方法时候,调用了linux低层,使当前线程腾出CPU的使用权,处于休眠等待状态,直到休眠时间结束或消息入队被唤醒。
private IdleHandler[] mPendingIdleHandlers;
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
/**
* Add a new {@link IdleHandler} to this message queue. This may be
* removed automatically for you by returning false from
* {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
* invoked, or explicitly removing it with {@link #removeIdleHandler}.
*
* This method is safe to call from any thread.
*
* @param handler The IdleHandler to be added.
*/
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
/**
* Remove an {@link IdleHandler} from the queue that was previously added
* with {@link #addIdleHandler}. If the given object is not currently
* in the idle list, nothing is done.
*
* This method is safe to call from any thread.
*
* @param handler The IdleHandler to be removed.
*/
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
可以看到,这个列表拥有add和remove的方法,这两个方法会在外部随时被调用,也就意味着,列表是变化的,如果一直添加的话,就会不停的处理,当前循环就会在这里耗时,消息队列的消息就有可能不能被及时处理了。
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
* 当线程即将进入阻塞状态的时候需要处理的更多消息
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
* 当MessageQueue的消息被执行完或下一个消息需要等待时间的时候会被调用。
* 如果仅需要被调用一次,返回false,则被调用一次就会从列表中移除此项,如果需要每次空闲
* 时都需要被调用就返回true,则不会被移除。
*/
boolean queueIdle();
}
IdleHandle是当线程空闲(MessageQueue中暂时不处理消息时)时才需要处理的事物。使用方法 ,使用场景我不再赘述。
好了,next()方法分析完了,再次揪一揪自己浓密健壮的头发感叹——谷歌工程是真的厉害!
Looper短短不到500行的代码,并没有什么特殊之处,它用于为一个线程运行一个消息循环。新的线程默认是没有消息循环的,在线程中调用prepare可以产生一个消息循环,然后调用loop方法可以开始运行循环直到线程终止。
Looper中有以下几个重要的方法:
sThreadLocal 存放了当前线程绑定的Looper,除非先调用了prepare(),否则调用ThreadLocal的get方法时会返回null;
那么我们看看prepare()方法:
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*初始化当前线程的Looper,这给我们一个机会去创建引用这个Looper *的Handler,要真正开始循环,确保调用了loop方法。
*/
public static void prepare() {
prepare(true);
}
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构造函数
*/
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
从代码及其注释可以看出来,它为当前线程初始化一个唯一(多次调用该方法会抛错)的Looper,并保存到sThreadLocal中,同时,会在其构造函数中,初始化一个唯一的MessageQueue。
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) {
//……
throw exception;
} finally {
//……
}
msg.recycleUnchecked();
}
}
这个方法的作用十分简单:
维持一个死循环。在循环中不断的从当前线程的MessageQueue中取出一个待分发的消息。
Message msg = queue.next(); // might block
注意这行代码,表示循环在这里可能会阻塞。当消息队列中没有消息的时候,循环就会阻塞在MessageQueue的next方法中(对应MessageQueue的next())。
然后调用message.target.dispatchMessage()方法执行事件。
这几个构造函数分为两类:一类是形参有Looper的构造函数,一类是没有Looper的构造函数。
public Handler(@Nullable 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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
2.而有Looper的构造函数则关联传入的Looper,而这一点与Handler的线程切换至关重要。
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
另外,我们还需要注意构造函数中的Callback,初始化的时候也可以选择性传入,具体作用,我们后面讲
除了postAtFrontOfQueue方法和sendMessageAtFrontOfQueue方法,其它方法最后都调用了sendMessageAtTime方法。不过殊途同归,最终这三个方法都调用了enqueueMessage方法,最终调用到了当前线程的MessageQueue的enqueMessage方法
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);
}
看到这段代码,我们看到一个熟悉的东西——target,不管是post还是send方式,最终都会在这里将当前发送该message的handler注入到message中,这样,就解答了问题1:message的target从哪里来的。
在此有必要说明一点post方式和send方式一个不同之处——post方式发送的message会被注入我们初始化的Runnable回调,这就解答了我们前面的问题2:message的callback从哪里来的?
Handler,Message,MessageQueue,Looper的相关知识已经介绍完毕,下面开始正文了!
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);
Process.setArgV0("" );
// -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");
}
每每看到这段代码,我都有同样的感触。那是16年的初夏,就像我16岁的雨季,在android成长路上充满了懵懂和无知。当时面试的时候有一位面试官问题,android有没有main方法,我说有的,我看到过,她当时像笑傻子一样。那时候我还对android知之甚少,只知道初次看activity代码的时候,找不到main方法我就郁闷怎么启动呢,百度了一下找到了这个main方法……面试的时候我还和面试官争辩了几句……时至今日,细节我已经忘记,但是我还是想起了她,因为她笑起来可好看,而今天是七夕……
哈哈哈,言归正传,我们接着说这个方法。
在ActivityThread类的main方法中:
Looper调用了它的一个特殊的方法——prepareMainLooper,专门为主线程准备的,用于初始化主线程的Looper;初始化完毕后,调用Looper的loop方法,启动Looper,的loop,开始消息分发。
结合我们前面讲到的,handler没有Looper的构造函数调用的时候,会选择性注入一个callback;同时会默认绑定当前线程的Looper。这也就是为什么我们在主线程可以new Handler()然后直接发送消息的原因。
而在子线程,若像下面这种方式:
new Thread(new Runnable() {
@Override
public void run() {
new Handler().post(() -> {
// TODO: 2020-08-25 todo sth
});
}
}).start();
一运行显然是会失败的,会抛出 “Can’t create handler inside thread ThreadName-x that has not called Looper.prepare()”。我们前面讲Handler构造函数的时候说过,初始化Handler的时候需要关联一个Looper.子线程并没有初始化Looper,怎么办呢?有两种方式:一,我们自己调用Looper的prepare方法初始化一个,然后初始化Handler的时候就会自动关联;二,传入其它线程的Handler,比如主线程的handler。
public void startNewThread() {
//仅用于示例,不要模仿我这种Thread写法
new Thread(new Runnable() {
@Override
public void run() {
//方式一
Looper.prepare();
new Handler(getMainLooper()).post(() -> {
// TODO: 2020-08-25 todo sth
});
Looper.loop();
// Looper.myLooper().quitSafely();
//方式二
new Handler(getMainLooper()).post(() -> {
// TODO: 2020-08-25 todo sth
});
}
}).start();
}
切记!调用完Looper.prepare()方法后记得调用Looper.loop()方法。另外,在子线程任务结束后,调用 Looper.myLooper().quitSafely()退出。
我们通过post方式发送一个消息,紧接着在后续方法中给message注入当前handler,然后通过当前Handler关联的Looper,获取当关联的Looper中的MessageQueue,再然后,调MessageQueue的enqueueMessage方法消息入队。
消息入队之后,我们就该分发消息了。
我们再次定位到Looper的loop()方法中:
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) {
//……
throw exception;
} finally {
//……
}
}
在loop循环中,通过MessageQueue的next方法取出待分发的消息,然后调用
msg.target.dispatchMessage(msg);上面我们已经知道,target就是message被发出入队时被注入的Handler,也就是说,负责发出这个message消息的handler,还负责分发它!我们走进Handler的dispathchMessage方法:
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(@NonNull Message msg) {
}
第1部分:如果msg的calback存在,即通过post方式初始化注入的Runnable存在,那么调用这个Runnable处理消息;
第2部分:如果mCallback存在即初始化Handler的时候初始化了Callback,就调用mCallback的方式处理message。
第3部分:如果前两个回调都不存在,就调用Handler的handleMessage方法处理消息。而我们也看到了,这个方法是一个空的实现,这就需要我么自己去实现它。这也是为什么我们平时使用handler的时候经常会实例化Handler的子类而不是Handler本身的原因。
– 到此为止,Handler的事件分发原理终于讲完了……
我之前看过很多关于Handler的帖子,几乎都是在讲handler事件分发,本质上,事件分发和线程切换原理是一样的,但是,两者侧重点不同!两者都包含了消息机制的整个流程,很多人在讲述的时候,也把整个过程混为一谈;也有人把线程切换错误的认为是由handler完成的,实际上他是一叶障目只看到了冰山一角,根本没有看到重点,线程切换的实质和Handler根本没有半毛钱关系!!
**线程切换并不是由Handler完成的,线程切换的重点是Looper!**非要说和Handler有关系,那么只能说Handler只充当一个送快递的作用。
接下来,我来为你细细分解:
我们回溯从handler、looper的初始化到发出一个message,最后到message分发完成整个流程:
- 创建一个Looper,内部初始化一个MessageQueue,运行looper;
在讲Handler的构造函数时,我们知道,Handler会默认关联当前线程的Looper或传入的Looper,这点至关重要!为什么至关重要呢,因为:传入的不同线程的Looper,发送的消息最终被分发到的线程就不同!Message最终被处理的线程就是负责发送和分发它的Handler初始化的时候关联的Looper所在的线程!
是不是有点郁闷,我们接着往下捋:
handler通过post或者send方式最终都调用了下面两个方法:
public boolean sendMessageAtTime(@NonNull 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);
}
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
//消息队列
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, 0);
}
没错,这就是前面说的入队的方法。但是你有没有一瞬间产生过疑问——mQueue从哪里来的???
往上翻,定位到Handler的两个构造方法,赫然看到一行:
mQueue = mLooper.mQueue;
看到没有,Handler的MessageQueue来自关联的Loooper,也就是我们前面说的,初始化Looper的时候默认初始化的MessageQueue!再回想我上面说的,Looper的prepare()方法为每个线程初始化一个唯一的Looper和MessageQueue!
到此,有没有想到什么?
我们再沿着流程往下,消息入队,然后由Looper的loop取出消息,最后被分发!
可以说,**所谓线程的切换,就是把消息插入不同线程的Looper内的MessageQueue中!**消息分发就是执行在不同线程的Looper的loop()方法中的,自然而然的,消息被处理的时候就已经在不同的线程中了。自此,线程也就切换完成了!而handler,开始是一个负责将消息送入队列的快递员,最后,又只起了一个回调的作用!
到此,Handler线程切换哦不,Looper的线程切换原理已经讲完了,最后总结两点:
- 线程切换和事件分发本质是一样的;
好了,大功告成,这篇帖子到此为止了。描述顺序和逻辑可能有点乱,还请见谅。如有不足,欢迎批评指正,非常感谢:)。