zoukankan      html  css  js  c++  java
  • 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);
          }
      }
      
  • 相关阅读:
    Python3.5 学习三
    心灵鸡汤20180727
    Python3.5 学习二
    spring心得4--setter注入集合(set、list、map、properties等多种集合,配有案例解析)@基本装(引用)
    drop user和drop user cascade的区别(转)
    数据库的导入 导出
    OracleDBConsole服务无法启动原因
    create XML
    C#里面Console.Write与Console.WriteLine有什么区别????
    将字符串 按照规定编码方式编码
  • 原文地址:https://www.cnblogs.com/yongfengnice/p/14987398.html
Copyright © 2011-2022 走看看