View.post为什么可以拿到View的宽高?

栏目: IOS · Android · 发布时间: 6年前

内容简介:最近因为个人原因,导致要离开杭州了,人生就是一次又一次的意外,你不知道突然会发生什么,你能做到的只有把握好每一次机会在Android中,想要获取View的宽高,常见的基本都是接调用View.getWidth或者View.getMeasuredWidth方法来获取宽高,但是往往在oncreate或者onresume中,得到的值都是0,也就可以理解为此时View的绘制流程并没有执行完,熟悉Activity启动流程的朋友都知道,Activity的生命周期和页面的绘制流程并不是一个串行的状态,没有特定的先后关系,所

最近因为个人原因,导致要离开杭州了,人生就是一次又一次的意外,你不知道突然会发生什么,你能做到的只有把握好每一次机会

原理猜测

在Android中,想要获取View的宽高,常见的基本都是接调用View.getWidth或者View.getMeasuredWidth方法来获取宽高,但是往往在oncreate或者onresume中,得到的值都是0,也就可以理解为此时View的绘制流程并没有执行完,熟悉Activity启动流程的朋友都知道,Activity的生命周期和页面的绘制流程并不是一个串行的状态,没有特定的先后关系,所以也不难理解获取的值是0了

再次回到主题,那为什么View.post(),就可以获取到准确的值呢,不妨猜测一下,首先整体上思考一下,想要实现知道准确的宽高,那就是post的Runnable那肯定是在View整个绘制流程结束之后才执行的,主线程又是基于Looper的消息机制的,如果把Runnable直接作为一个消息插入消息队列,那么很明显不能保证这种效果,熟悉View绘制流程的朋友知道,View的绘制是在ViewRootImp中的,但View的绘制其实也是一个Runnable消息,那么我们可不可以先把post的这个Runnable给缓存起来,等到绘制的Runnable执行完之后,再来通知去执行,这样就能够获取到准确的宽高了。

View##post

本文源码是API是android-28,不同版本可能有些差异,需要读者自行注意

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        getRunQueue().post(action);
        return true;
    }
复制代码

可以看到方法很简答,主要就是一个 attachInfo ,如果不为空就直接使用attachInfo.mHandler去执行这个action,如果为空,把Runnable放入一个类似队列的东西里面

我们再回头想想开头说的话,好像还真是这么实现的,这里的 mAttachInfo 其实可以看做为是否已经绘制好了的一个标志,如果不为空,说明绘制完成,直接handler执行action,如果为空,说明没有绘制完,这时候就把Runnable缓存起来,那么关键点也就来了,这个 mAttachInfo 是什么时候被赋值的,全局搜索下赋值

View##dispatchAttachedToWindow

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ......
    }
复制代码

从这个方法名字,我们也应该能看出来,绑定到window的时候,此时会进行赋值mAttachInfo,也就意味着绘制完毕,当然,我们还不知道dispatchAttachedToWindow这个方法是什么时候调用的,先这么理解着

getRunQueue().post(action)

private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

复制代码
public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }
复制代码

可以看到,实际上是维护了一个HandlerActionQueue类,内部维护了一个数组,长度居然是固定为4(问号脸),然后将这些Runnabel给缓存起来。那疑问就来了,既然是缓存起来,那什么时候执行的,可以看到有个executeActions方法

executeActions(Handler handler)

public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }
            mActions = null;
            mCount = 0;
        }
    }
复制代码
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
		......
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
     ....
    }
复制代码

通过传递进来的handler,然后将内部缓存的Runnable去执行,ctrl一下,看看哪里调用了,咦。又是 dispatchAttachedToWindow 这个方法。

先总结下, View.post(Runnable) 的这些 Runnable 操作,在 View 被 attachedToWindow 之前会先缓存下来,然后在 dispatchAttachedToWindow() 被调用时,就将这些缓存下来的 Runnable 通过 mAttachInfo 的 mHandler 来执行。在这之后再调用 View.post(Runnable) 的话,这些 Runnable 操作就不用再被缓存了,而是直接交由 mAttachInfo 的 mHandler 来执行

所以问题最终也就到这个方法这里了,这个方法是什么时候被调用的,ctrl一下。。居然没有地方调用,那肯定是隐藏类调用了,此时祭出我们的Source Insight,从源码里面找找。

ViewRootImpl##performTraversals

private void performTraversals() {
        .....
        final View host = mView;
        .....
        host.dispatchAttachedToWindow(mAttachInfo, 0)
        ....
    }
	final ViewRootHandler mHandler = new ViewRootHandler();
	public ViewRootImpl(Context context, Display display) {
        ...
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
       ....
    }


复制代码

这个方法是不是很眼熟,View的绘制流程就从这个方法开始的,可以看到的是,mAttachInfo这个对象是在ViewRootImpl初始化的时候就赋值了,并且Handler是直接在主线程中创建的,这个就可以说明了为什么View.post是可以更新UI的,因为最终的Runnable是在主线程的Handler中去执行的,自然是可以更新UI的。

但是大家有可能还有个疑问,那既然View的绘制也是这个方法执行的,dispatchAttachedToWindow也是在这个方法执行的,那怎么能保证一定是在View的绘制流程完成之后才去执行dispatchAttachedToWindow的呢。

答案也很简单,因为android主线程是基于Looper的消息机制的,不断的从绑定Looper的MessageQueue中去获取message去执行,View的绘制操作其实也是一个Runnable对象,所以在执行performTraversals()方法的时候,调用dispatchAttachedToWindow方法,这个时候,所以子View通过View.post(Runnable)缓存的Runnabel是会通过mAttachInfo.mHandler 的 post() 方法将这些 Runnable 封装到 Message 里发送到 MessageQueue 里。mHandler 我们上面也分析过了,绑定的是主线程的 Looper,所以这些 Runnable 其实都是发送到主线程的 MessageQueue 里排队的,所以也就可以保证这些 Runnable 操作也就肯定会在 performMeasure() 操作之后才执行,宽高也就可以获取到了,我们也可以在源码中找到些许痕迹

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
void doTraversal() {
        if (mTraversalScheduled) {
          	.....
            performTraversals();
            ....
        }
    }

复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beginning ASP.NET 4 in C# and Vb

Beginning ASP.NET 4 in C# and Vb

Imar Spaanjaars / Wrox / 2010-3-19 / GBP 29.99

This book is for anyone who wants to learn how to build rich and interactive web sites that run on the Microsoft platform. With the knowledge you gain from this book, you create a great foundation to ......一起来看看 《Beginning ASP.NET 4 in C# and Vb》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

SHA 加密
SHA 加密

SHA 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具