Android开发Handler是如何确保UI刷新优先执行的源码解读

Android开发Handler是如何确保UI刷新优先执行的源码解读

  1. 问题分析

    问题1:很久前被问到,requestLayout会立刻触发绘制界面吗?答案是不会立即的,需要等待下一个VSync信号到来(VSync信号即硬件垂直同步信号,比如1秒60帧即1000ms/60约为16.7ms一次)。

    当然VSync信号到来也不一定就会触发绘制呀,起码dirty了也就是有变化了才绘制吧,那就bool值标记一下不就好了。。。比如ViewRootImpl里的mTraversalScheduled变量,Choreographer里的mFrameScheduled变量都是标记作用。。。

    其实嘛本质是requestLayout方法(调用checkThread()检查是否为主线程)会调用scheduleTraversals()方法,发送一条message消息(特殊的message消息即屏障消息)给handler,接着使用mChoreographer.postCallback()发送一条异步消息给handler,具体实现在Choreographer.postCallbackDelayedInternal()方法里,怎么好像就一条屏障消息和一条异步消息就搞定了?具体下面会分析。

    问题2:那我们给handler发送很多消息的话,不是会堵塞UI刷新消息的执行,然后导致UI不能及时刷新吗?答案是不会的,原因就是刷新UI的时候给handler发送的是一条屏障消息和一条异步消息,具体原因下面分析。

  2. Handler、Looper、MessageQueue、Message四个之间的关联关系

    //1.Looper的prepare静态方法里实例化了一个Looper对象,并将其放入sThreadLocal实现线程私有
    //并且做了判断,也就是说每个线程最多可以有一个Looper,每个线程的Looper是私有的,互不干扰。
    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));
    }
    //1.Looper通过构造函数持有MessageQueue对象,而MessageQueue持有消息循环队列头指针Message mMessages
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
    //2.Hanndler通过构造函数持有Looper以及Looper的MessageQueue,也说明Looper持有MessageQueue
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    	mLooper = looper;
    	mQueue = looper.mQueue;
    	mCallback = callback;
    	mAsynchronous = async;
    }
    
    handler.sendMessage(msg);msg.target = this;//除了屏障消息之外,同步消息和异步消息,有target并且target是Handler
    //target的作用(msg的target持有的是Handler对象)
    public void dispatchMessage(@NonNull Message msg) {
    	if (msg.callback != null) {//优先回调msg自动的callback
    		handleCallback(msg);
    	} else {
    		if (mCallback != null) {//再尝试回调handler带的callback
    			if (mCallback.handleMessage(msg)) {
    				return;
    			}
    		}
    		handleMessage(msg);//最后才调用这个空实现,一般我们new一个handler重写这个方法处理发送消息的结果
    	}
    }
    
  3. Handler从发送消息到处理消息的大致过程

    //1. 第一阶段,就是通过handler发送消息到循环队列
    public final boolean sendMessage(@NonNull Message msg) {}
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {}
    private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
        msg.target = this;//这个target的值this指向的是Handler,用于后面队列循环到这个消息时回到Handler处理
        ...
    }
    //2. 第二阶段,Looper.loop();之后,循环队列开始工作,系统早已调用主线程loop()方法了,所以主线程队列一直在工作或者等待
    public static void loop() {
         for (;;) {
             Message msg = queue.next();//调用队列的next方法获取下一个符号条件的消息
             ...
          	try {
    			msg.target.dispatchMessage(msg);//回到handler的dispatchMessage方法,和上面说的enqueueMessage对应
    		} catch (Exception exception) {
    		}
         }
        msg.recycleUnchecked();//回收消息
    }
    //2.  第二阶段,MessageQueue.next()方法取出下一个符号条件的消息
    Message next() {
        int nextPollTimeoutMillis = 0;//等待多久时间,第一次等待时间是0,-1表示无限等待直到被nativeWakt唤醒
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);//等待时间,第一次等待时间是0表示立刻返回,即查询一次
    		synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {//本文重点:如果msg的target为空,表示这个msg是屏障消息
                do {//如果是屏障消息,遍历找到下一个异步消息为止。别忘记UI刷新发送的就是异步消息哦,可能查找的就是UI刷新消息呢
                    prevMsg = msg;
                    msg = msg.next;
                 } while (msg != null && !msg.isAsynchronous());//循环找到下一个异步消息为止
             }
            if (msg != null) {
                if (now < msg.when) {//时间还没到,计算下次等待时长
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                 } else {// Got a message.将消息从链表取出,并标记为已经使用,然后跳出循环返回到上一层Looper.loop();
                    mBlocked = false;
                    if (prevMsg != null) {
                         prevMsg.next = msg.next;
                     } else {
                         mMessages = msg.next;
                     }
                     msg.next = null;
                     msg.markInUse();
                     return msg;
                  }
              } else {// No more messages.没有消息,-1无限等待直到被唤醒
                  nextPollTimeoutMillis = -1;
              }
            }
    		...省略掉IdleHandler的处理,就是没有消息可处理时,查询是否添加了IdleHandler,就有处理IdleHandler回调
        }
    }
    
  4. 到处已经分析完标题的疑问,可能还有人有疑问,总结一下

    1.handler消息分为三类:

    ​ 同步消息:就是我们平时使用的postMessage,sendMessage,Message的isAsynchronous()返回false,Message的target不能为空。

    ​ 异步消息:同步消息通过Message的setAsynchronous(boolean async)方法可以变为异步消息,target同样不能为空。

    ​ 屏障消息:同步消息的target为空时变成屏障消息。但是平时不能使用target为空的消息,因为使用完后需要对应的移除屏障消息,所以只能系统使用。

    2.屏障消息的作用

    ​ 屏障的作用自然是屏蔽遮挡作用,就是一个标记作用,当MessageQueue.next()方法取到的是屏障消息时,就是while循环只取异步消息,也就是遇到屏障消息时过滤不考虑同步消息,只处理异步消息,这样屏障消息就起到标记作用,提升异步消息处理优先级的作用。

    3.UI刷新是异步消息,配合屏障消息的使用,异步消息优先处理,所以我们平时发送的普通消息是同步消息不会影响到UI刷新

    mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();	//插入屏障消息
    mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//然后再发送异步消息
    

    4.屏障消息是target为空的消息,屏障消息使用完需要手动移除,没有暴露给开发者移除api,所以只能系统使用

    void unscheduleTraversals() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);//移除屏障消息
            mChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }
    
原文地址:https://www.cnblogs.com/yongfengnice/p/14987398.html