2023年6月28日发(作者:)
androidhandler⾯试,Android⾯试⾥常见的Handler相关问题⼀、Handler、MessageQueue、Looper 的关系模型职责Handler: 负责向MQ⾥⼊队消息(sendMessage)、删除消息(removeMessage)、处理消息(handleMessage)MessageQueue: 负责投递消息(enqueueMessage),取消息(next)Looper: 负责轮询MQ,将取出的消息分发给对应的 handler 处理(loop)Message: 单链表结构(next),绑定⽬标Handler(target),记录发送时间(when),发送内容(obj)调⽤关系Handler: 需要指定 Looper,Looper 中会有对应的 MQMessageQueue: 对应⼀个待处理的 Message 链表Looper: 对应⼀个 MQMessage: 对应处理⾃⼰的 HandlerHandler 消息队列机制的使⽤过程如下```class HandlerThread extends Thread {private Handler mHandler;public void run() {e();mHandler = new Handler(er());();}public Handler getThreadHandler() {return mHandler;}}class ActivityThread {public static void main() {//...HandlerThread ht = new HandlerThread();();eadHandler().post(()-> {//do xxx});}}```关系确⽴时机Handler Looperclass Handler {public Handler(Looper looper, Callback callback, boolean async) {mLooper = looper;mQueue = ;mCallback = callback;mAsynchronous = async;}}初始化 Handler 的时候,会指定 Looper,同时会将 Looper 中的 MessageQueue 也绑定到Handler上Handler Messageclass Handler {private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { = this;if (mAsynchronous) {nchronous(true);}return eMessage(msg, uptimeMillis);}}在发送消息的时候,Handler 会和 Message 发⽣绑定,=this,可以理解为谁发的消息就要谁去处理Looper Threadclass Looper {private static void prepare(boolean quitAllowed) {if (() != null) {throw new RuntimeException("Only one Looper may be created per thread");}(new Looper(quitAllowed));}}(new Looper(quitAllowed)),借助 ThreadLocal 将 Looper 和 Thread 进⾏绑定,所以⼀个 Thread 只会对应⼀个Looper,是 ThreadLocal 决定的,想知道ThreadLocal原理的同学可以⾃⼰查查相关资料。⼆、有关消息队列的⼀些问题如何保障消息队列中的消息的时间顺序?延时消息如何实现的?消息的分发过程?消息屏障是什么?解决这些问题的关键在于MQ⼊队和出队消息的逻辑⾸先,解决前两个问题,如何保障消息队列中的消息的时间顺序?延时消息如何实现的?这需要分析enqueueMessage()boolean enqueueMessage(Message msg, long when)这个⽅法是在Handler#sendMessageAtTime()中调⽤的,when 是Millis() + delayMillis的结果,即系统正常运⾏时间(不算阻塞、休眠)+消息延时时长去除特殊情况,和加锁逻辑后的代码如下boolean enqueueMessage(Message msg, long when) { = when;Message p = mMessages;//之前的消息链if (p == null || when == 0 || when < ) {// New head, wake up the event queue if = p; //新 msg 作为链表头mMessages = msg; //更新消息链needWake = mBlocked;} else {Message prev;for (;;) {prev = p;p = ;if (p == null || when < ) { //⾛到链表尾部或找到正确时间线位置break;}} = p; // invariant: p == = msg; //将msg插⼊到消息链}}具体插⼊逻辑简化为,向队列头部插⼊消息,⽐如调⽤了handler#sendMessageAtFrontOfQueue,按时间线插⼊消息,如下图所⽰,假设系统当前运⾏时间定格在+500的状态,开始向队列插⼊消息的状况特殊说明: 在+4100插⼊了⼀个延时500的消息,在⼊队的时候需要调整链表位置,所以+4100的消息会插⼊到+4200的后⾯,保证了消息的时间顺序,同时巧妙的实现了延时消息。然后,分析⼀下取出正常消息(同步消息)的过程,精简代码如下Message next() {int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {endingCommands();}nativePollOnce(ptr, nextPollTimeoutMillis); //阻塞 nextPollTimeoutMillis 时间synchronized (this) {// Try to retrieve the next message. Return if long now = Millis();Message prevMsg = null;Message msg = mMessages;if (msg != null) {if (now < ) { //没到发消息的时间,队列需要阻塞// Next message is not ready. Set a timeout to wake up when it is llTimeoutMillis = (int) ( - now, _VALUE); //此时会给 nextPollTimeoutMillis 赋值,在循环开始时会阻塞} else {// Got a ed = false;if (prevMsg != null) { = ;} else { //调整消息链表mMessages = ;} = null; //断开后⾯的消息链if (DEBUG) Log.v(TAG, "Returning message: " + msg);Use(); //标记为消息已使⽤return msg;// 返回得到的消息}} else {// No more llTimeoutMillis = -1;}if (pendingIdleHandlerCount <= 0) {// No idle handlers to run. Loop and wait some ed = true;continue;}}}正常从消息队列取消息的过程可以理解为如下过程假设系统时间为+4300,此时取出next为链表中的最后⼀个消息,when=+4500,此时因为还未到执⾏消息的时候,就会给nextPollTimeoutMillis赋值,然后会检查pendingIdleHandlerCount,此时⼀定没有IdleHandler要处理任务,进⼊下⼀次循环,进⼊阻塞状态第三个问题,消息的分发过程?在上⾯刚刚分析了取消息的过程 MessageQueue#next(),现在看看是谁调⽤了 next 并且得到了当前的 Message 对象,这个问题答案在Looper#loop()中,简化逻辑后的代码如下for (;;) {Message msg = (); // might blockif (msg == null) {// No message indicates that the message queue is ;}chMessage(msg);}}逻辑很简单,就是死循环调⽤next(),如果得到msg,就调⽤ chMessage 即回到 Handler#dispatchMessagepublic void dispatchMessage(Message msg) {if (ck != null) {handleCallback(msg);} else {if (mCallback != null) {if (Message(msg)) {return;}}handleMessage(msg);}}逻辑也很简单,就是指定了⼀个回调的优先级,先看 msg 是否指定了 Callback,如果没有就查看是否给 Handler 设置了 Callback,如果也没有就执⾏ Handler#handleMessage,这个⽅法需要⾃⼰重写第四,消息屏障是什么?简单来说,消息屏障就是个标志,标志开启时会优先处理异步消息这个问题也需要在消息的⼊队出队时寻找答案,由于应⽤开发的时候使⽤场景⽐较少,所以放到最后解答,现在看⼀下有关消息屏障的源码Message next() {Message prevMsg = null;Message msg = mMessages;if (msg != null && == null) {do {// Stalled by a barrier. Find the next asynchronous message in the g = msg;msg = ;} while (msg != null && !chronous());}//后⾯⾛上⾯分析过的流程,但此时的msg已经是⼀个异步消息了}判断有⽆消息屏障的条件是 == null,在有消息屏障下,会不断循环链表找到异步消息,条件为!chronous(),现在再来看看如何设置消息屏障MessageQueue#postSyncBarrierpublic int postSyncBarrier() {return postSyncBarrier(Millis());}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 onized (this) {final int token = mNextBarrierToken++;final Message msg = ();Use(); = when;1 = token;//此处没设置target,所以target是nullMessage prev = null; //找到合适的位置插⼊屏障消息Message p = mMessages;if (when != 0) {while (p != null && <= when) {prev = p;p = ;}}if (prev != null) { // invariant: p == = p; = msg;} else { = p;mMessages = msg;}return token;}}这个⽅法就是在指定时间处插⼊⼀个屏障消息,如果是0,就是在消息队列最头部插⼊,然后消息队列在取消息的时候发现有消息屏障就会向后遍历直到找到异步消息(chronous()),将消息分发处理。个⼈理解:消息屏障的作⽤其实并不是名字定义的同步异步的意思,⽽是给消息定了优先级,给异步消息开了后门(只要有屏障消息在,之后读取的消息都是异步消息)。三、⼩结消息队列中的消息按时间顺序排序和延时消息的实现,都是依靠系统时间的推移实现的如果下⼀个消息的时间超过系统当前时间,则会阻塞屏障消息的判断条件是 == null,正常使⽤Handler发的消息是⽆法将 target 置为 null 的,需要⼿动调⽤postSyncBarrier补充:IdleHandler的作⽤是什么?IdleHandler 可以⽤来提升提升性能,主要⽤在我们希望能够在当前线程消息队列空闲时做些事情(譬如UI线程在显⽰完成后,如果线程空闲我们就可以提前准备其他内容)的情况下,不过最好不要做耗时操作。
发布者:admin,转转请注明出处:http://www.yc00.com/news/1687955398a60608.html
评论列表(0条)