Moosphan / Android-Daily-Interview

:pushpin:每工作日更新一道 Android 面试题,小聚成河,大聚成江,共勉之~

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2019-08-21:View.post()为什么可以获取到宽高信息?

Moosphan opened this issue · comments

我们可能会在项目中经常这么做:

 view.post(new Runnable() {
            @Override
            public void run() {
                 Logger.e("当前 view 的高度为:${view.getHeight()}");
            }
        });

这么做是可以获取到 view 的宽高信息的,但是大家有想过为什么吗?

不清楚哈哈哈哈 不过貌似 这样子做 会把任务推到最后 直到当前view绘制完成后 才能正常获取到宽高 当然 还有其他三种方法 可以正确获取到

因为view没绘制的时候post是添加到view内部的一个队列中,view创建好之后(啥viewattachwindows方法分发的时候会一起调用,而这个方法在view绘制完成之后执行,所以能拿到)

其实我们调用的post方法执行流程有两种,一种是直接使用Handler去执行,第二是将我们的runabale存储到队列中。

这个问题讲清楚有点难度呀。 内部执行是通过handler 去执行的, View.post 如果在onCreate方法中调用,这时会把Runnable保存到一个缓存数组中,等到View的 dispatchAttachedToWindow 方法被调用时,去通过handler执行Runnable方法, dispatchAttachedToWindow就是View加载到Window时被调用的。https://www.cnblogs.com/dasusu/p/8047172.html

这样写一般是在 Activity 的 onResume 方法中,因为 onResume 执行在 View 初始化之前,如果在 onResume 中直接获取 View 宽高是获取不到的。使用 view.post 就能获取到,因为 view.post 是向 主 Handler 的 MessageQueu 中插入一条带执行消息,但是因为系统在 ViewRoot 中初始化 View 时也是利用 Handler 机制,平且为了优先执行 View 的初始化设置了同步屏障,导致 view.post 插入的消息会在 View 初始化之后执行,那么肯定就能获取到 View 的宽高啦!

commented

View.post方法调用时,如果在 View 还没开始绘制时( Activity 的 onResume方法还没回调之前 或者onResume方法执行了,但是 ViewRootImpl 的 performTraversals 还没开始执行)就会用一个初始长度为 4 的数组缓存起来(Runnable 数量大于4时会进行扩容),ViewRootImpl 在初始化时创建了一个 View.AttchInfo 对象并绑定 ViewRootImpl 的 Handler ,该 Handler 也用于发送 View 绘制相关的 msg ;

等到 ViewRootImpl执行 performTraversals方法时(此时 Activity 已经回调了onResume),会配置 View 的 AttchInfo 对象并且通过 View 的 dispatchAttachedToWindow 方法传入到 View 里面完成绑定,在该方法中会取出 View 缓存的 Runnable 并用 View.AttchInfo 的 Handler 来进行 post 方法,这样子就会加入到 MessageQueue 里面进行排队,等到这些缓存 Runnable 执行时,主线程里面 View的绘制流程也就结束了,所以这时候 Looper 取出这些缓存的Runnable 执行时就可以拿到 View 的宽高

那么,什么情况下View.post 方法不会执行呢?如果 Activity 因为某些原因没有执行到 onResume 的话,无法顺利调用 ViewRootImpl 的 performTraversals 的话,View.post 方法就不会执行
========================(更新分割线)==============================
刚朋友提醒,如果 View 是 new 出来的,并且没有通过 addView 等方法依赖到 DecorView 上面,它的 post 方法也是不会执行的,因为它没有机会和 ViewRootImpl 进行互动了

一个view的大小的最终确定是在onlayout中,但是当我们打开Activity,执行onCreate方法的时候,view可能还没执行到onlayout方法,这也是为什么我们在onCreate的方法中,通过getWidth获取的宽度为0的原因,但是在View.post方法中,实现了一个Runnable,即添加了一个队列任务,在执行完setContentView方法之后,队列任务里面会多一条询问布局是否完成的任务,我们添加的这一个任务就是加在这个任务之后,所以通过view.post这个方法可以获取到view的宽和高

大概几点, 1.. Window 2.. viewGroup.addView 或者 activity.setContentView 引起 View 树变化;
就会调用 requestLayout, 当收到 vSync 信号之后, 会在 ViewRootImpl 调用 performTraversals , 进而会 measure 所有的view节点, 之后, 会回调 myView.post{ 放进去的 runnable};
往 decorView 或者 window 上动态添加 view的时候, 有时候需要算 各个 view 的宽高, 用 myView.post 这种形式最好的, 免去 手动measure消耗的时间, 而且这个数值, 一定是最终呈现的, 不会错;

1、LAUNCH_ACTIVITY,SystemServer进程发起,在这个消息里边将view.post中提交的runnable对象缓存起来,当执行到dispatchAttachedToWindow方法时,将缓存中的runnable插入消息队列,然后执行performMeasure、performLayout、performDraw方法
2、此处就是post的Runnable消息了,当上面的消息处理结束后,就轮到我了,由于上述已经对视图已经测量过了,所以这里能拿到宽高

最根本原因还是View#post这种方式提交的runnable的执行是在view树绘制完成之后才真正执行的。

commented

个人理解:

  1. mAttachInfo 是ViewRootImpl performTraversals 时绑定给DectorView的 此前会开启消息屏障(只接受异步消息),绘制结束后移除屏障!
  2. attachInfo.mHandler.post(action); 发送的是普通消息。只有消息屏障被移除后才可以执行(移除时机:View渲染完成,此时已完成了绘制)。
  3. 如上:借助消息屏障确保 post 消息是在View渲染完成后执行