Handler的主要作用是收发消息和切线程
功能一:收发消息
简单流程介绍
希望你看完这篇文章后也可以把流程自己讲出来,并且每个环节还可以讲出很多细节
他的消息机制离不开Looper、MessageQueue
其中 Looper 每个线程只能持有一个,主要负责循环查看 MessageQueue 里面是否有 msg 需要处理,并将需要处理的消息取出,交给 Handler
MessageQueue 是负责存放消息的,数据结构是一个单链表,这样就可以方便地插入或删除 msg
具体流程一般是:
Handler 发送一条msg => 本质是向MessageQueue里插入一条msg,插入时候的依据是msg.when => SystemClock.uptimeMillis() + delayMillis
这条msg被MessageQueue.next()返回并交给Handler去处理
next()会在有同步屏障(msg.target==null)的时候遍历查找并返回最早的异步消息,并在移除屏障后,从头取出并返回消息Handler.dispatchMessage(msg)会优先处理msg.callback,如果msg.callback为空,就处理Handler.mCallback,然后处理是msg本身
msg.callback是在调用Handler.post(Runnable)时,里面的Runnable(runOnUIThread,view.post(Runnable)也用的是Handler.post(Runnable),Runnable是一样的)这是在不新增Handler的情况下,另一种调用Handler的方式(如下)
class MyHandlerCallBack: Handler.Callback { override fun handleMessage(msg: Message?): Boolean { TODO("Not yet implemented") } }
可以看到他也有handleMessage这个方法
Looper是个死循环
(1)死循环的目的
目的就是让主线程一直卡在这个死循环里面
因为Looper的作用就是在这个死循环里面取出消息,然后交给Handler处理
Android的生命周期,你了解的onCreate,onStop,onStart...... 等等都是由Handler来处理的,都是在这个死循环里面运行的
所以什么Looper死循环卡死主线程怎么办???
必须给我卡住!!!不卡住的话,消息就没法整了!!!
看下Android启动的时候的源码
Activitythread.java >> main()
public static void main(String[] args) { ... Looper.prepareMainLooper(); ... if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
想想写java的时候,main最后一行执行完了,不就彻底玩完了嘛!!!
(2)死循环里干了啥
其实想都不用想,一直在看MessageQueue里面有没有消息呗,太简单了!调用的就是MessageQueue.next()
看下源码 MessageQueue.java >> 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); if (observer != null) { observer.messageDispatched(token, msg); } dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } catch (Exception exception) { if (observer != null) { observer.dispatchingThrewException(token, msg, exception); } throw exception; } finally { ThreadLocalWorkSource.restore(origWorkSource); if (traceTag != 0) { Trace.traceEnd(traceTag); } } ... }
很简单,next()返回Message,msg.target.dispatchMessage() 处理Message
但是队列里没消息就会返回null,这是错误的!!!具体往下看
MessageQueue是个单链表
1.插队
Handler发消息的时候,目的就是对msg经过一系列操作,最终也只是调用enqueueMessage插入队列而已
看下源码 Handler>>enqueueMessage()
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); }
return直接调用Message的插入队列方法
2.出队
出队就是next()方法,之前已经见过了
(1)时间顺序
Message是按时间排序的,也就是msg.when => SystemClock.uptimeMillis() + delayMillis
msg.when是Message期望被处理的时间
SystemClock.uptimeMillis()是开机到现在的时间,delayMills是延迟时间,这个在sendMessageDelayed方法里直接可以直接传参
next()就是按照时间顺序处理MessageQueue里面的消息的
但是next()里有个概念叫 同步屏障
(2)同步屏障
同步屏障,就是说,平时MessageQueue都是处理同步消息,也就是按顺序来,一个个出队
同步屏障就是阻挡同步消息的意思
就是msg.target == null 的时候,MessageQueue就会去找msg.isAsynchronous()返回true的msg
isAsynchronous,没错 ! 这是异步消息,就是优先级很高,需要立刻执行的消息,比如:更新View
(3)阻塞
值得注意的是,讲Looper的时候,源码next()后面官方给我们注释了 // might block可能阻塞,也就是说可能这个next()也许会执行好久
next()会阻塞?,什么时候阻塞?
now < msg.when也就是时间还没到,期望时间大于现在的时间
(4)退出
另外看第一行,只有ptr == 0,才会返回null
所以上面才说next()不会因为没消息而返回null,原来返回null的时候在这呢!
看下源码,MessageQueue.java >> next()
@UnsupportedAppUsage 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(); } nativePollOnce(ptr, nextPollTimeoutMillis); 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()); } if (msg != null) { 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 { ... 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; } ... } ... } }
代码简略了还是有点多,别着急,慢慢看
那pre什么时候就是0了呢?
答:quit()了之后
看下源码,Looper.java
public void quit() { mQueue.quit(false); } public void quitSafely() { mQueue.quit(true); }
可以看到只是一个传参不同而已,下面看看这个参数是干嘛的
看下源码,MessageQueue.java >> quit()
void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr); } }
可以看到,safe == true,就移除未来的Message
safe == false,就移除所有的Message
mQuiting变成了true,记住他我们一会儿会用到
而改变ptr的地方在这里
在next()里面
里面有个dispose,找不到可以ctrl+F找一下
这里只有在mQuiting == true的时候,才会调用
这就是改mPtr的地方,然后下次next()的时候就会返回null了
Handler流程
(1)post过来的msg
我们已经知道了在Looper的死循环里面,会将next()返回的msg交给Handler,调用dispatchMessage()
dispatchMessage()里面会先判断msg是不是被post过来的,因为post要执行的逻辑在msg.callback里面,callback是一个Runnable,这可能不是很好理解
你可以想想runOnUIThread(Runnable),这里的Runnable就是上面的callback,
他们都是调用了Handler.post(Runnable)
至于为啥起个名叫callback,我也纳闷儿
(2)send过来的msg
这些msg是会的逻辑是你重写的handleMessage那里的逻辑
如果实现了Handler.Callback这个Interface,就会处理mCallback的handleMessage
而不是Handler自己的handleMessage
这是一个优先级策略,没什么好奇怪的
我们看下源码 => Handler.java >> dispatchMessage()
public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
这就是Handler的消息机制了
接下来我们讲讲Handler的另一个功能,切线程
功能二:切线程
Handler切线程使用的是ThreadLocal
(1)ThreadLocal
ThreadLocal是线程里面的一个数据储存类,用法类似map,key就是thread
但是他没有提供,根据key来找ThreadLocal的Values的方法,所以暴露的api就只能让你去get当前线程的ThreadLocal的Values对象而已,就是key——你自己没法作为参数传进去,只能是currentThread
如果你没用过ThreadLocal,我给你举个例子
fun main() { val booleanThreadLocal = ThreadLocal<Boolean>() booleanThreadLocal.set(true) println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}") thread(name = "thread#001") { booleanThreadLocal.set(false) println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}") } thread(name = "thread#002") { println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}") } }
结果是这样的:你可以自己运行看看
in Thread[main] booleanThreadLocal value = true in Thread[thread#001] booleanThreadLocal value = false in Thread[thread#002] booleanThreadLocal value = null
(2)切线程的细节
话说回来,Handler怎么通过ThreadLocal切线程的呢?
答案是:Looper是放在ThreadLocal里的
回顾片头的流程,Handler将消息插入MessageQueue,然后Looper取出来,再还给Handler,这种设计不止是为了让msg可以按顺序处理,还可以让外部接口只有Handler
最关键的是,Looper跟Handler的触发关系只有Looper触发Handler,Handler不会触发Looper
因此Handler把消息放在MessageQueue之后,就在等着Looper来给自己派发任务(msg)
举个例子:
线程A调用主线程的Handler发一个消息
Handler将这个消息插入MessageQueue,此时其实还在线程A里
只有Looper在next()调用msg.target.dispatchMessage()时,就变成了主线程了
仅仅是因为Looper在 主线程 而已
相关推荐
0评论