探究为何:在onCreate中通过View.post能获取宽高

笔者解惑原文,并对原文有所借鉴之处,特此声明,读者可一并阅读原文:链接

惯例,导语:
最怕一生碌碌无为,还聊以自慰平淡是真。

平淡无为.png

在之前的文章《Android解决在onCreate中获取View的width、Height为0的方法》提到过,可以通过View.post方式:

view.post(new Runnable() {
        @Override
        public void run() {
            view.getHeight(); //height可用
        }
    });

之后有同学问到:

question.png

本着知其然知其所以然的学习态度,觉得还是有必要把为什么通过View.post方式就能获取到View的width/height的原理捯饬捯饬。

首先,观察View.post方法的实现:

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

主要是根据attachInfo是否被初始化决定执行方式,那么attachInfo在Activity的onCreate()执行时到底是不是null呢?关于attachInfo的初始化,我们可以在View源码中找到,其只有在dispatchAttachedToWindow()方法才被赋值,而dispatchAttachedToWindow()方法的调用是来自于ViewGroup,继续向上层去找,我们就不得不追溯到ViewRootImpl的perFormTraversals()方法了,熟悉view流程的都知道,view的三大流程就是通过这个称为“执行遍历”的方法来完成的。但是这个方法有整整800行代码,就只取主要流程的代码了:

private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
        if (mFirst) {
            ···
            host.dispatchAttachedToWindow(mAttachInfo, 0);
        } 
        ···
        //先于performMeasure被执行了
        getRunQueue().executeActions(attachInfo.mHandler);
        ...
        performMeasure();
        ...
        performLayout();
        ...
        performDraw();
 }

在这里,我们明确了attachInfo的初始化,在onCreate中执行View.post的时候,attachInfo还是null。回到post的代码,确认执行的是 ViewRootImpl.getRunQueue().post(action) 的逻辑:

static final class RunQueue {
        void post(Runnable action) {
            postDelayed(action, 0);//没有延时
        }

        void postDelayed(Runnable action, long delayMillis) {
            HandlerAction handlerAction = new HandlerAction();
            handlerAction.action = action;
            handlerAction.delay = delayMillis;

            synchronized (mActions) {
                mActions.add(handlerAction);
            }
        }
    }

RunQueue只是将需要执行的runnable消息暂时做一个存储,并且此消息没有延时。在前面ViewRootImpl.performTraversals()方法中我有注释:

//先于performMeasure被执行了
        getRunQueue().executeActions(attachInfo.mHandler);
        ...
        performMeasure();
        ...
        performLayout();
        ...
        performDraw();

getRunQueue().executeActions()竟然先于performMeasure()执行了,这还了得吗?如果是这样的话,我们通过View.post()方式获取的应该是还没有测量过的宽高呀!

好吧,我们还要看一下RunQueue.executeActions()的实现:

    void executeActions(Handler handler) {
            synchronized (mActions) {
                final ArrayList<HandlerAction> actions = mActions;
                final int count = actions.size();

                for (int i = 0; i < count; i++) {
                    final HandlerAction handlerAction = actions.get(i);
                    handler.postDelayed(handlerAction.action, handlerAction.delay);
                }

                actions.clear();
            }
        }

这里面其实也是调用Handler去post我们的Runnable,而ViewRootImpl的Handler就是主线程的Handler,因此在performTraversals()被执行的Runnable其实是被主线程的Handler的post到执行队列里面了。这里说明下,Android的运行其实是一个消息驱动模式,不了解消息机制的也可以看我的另一篇《Android源码 从runOnUiThread聊聊消息机制》
根据消息机制原理,我们需要等待主线程的Handler执行完当前的任务,才会去执行我们View.post的那个Runnable。
那么当前正在执行了什么任务呢?答案是TraversalRunnable,具体我们也要看ViewRootImpl的源码,里面有TraversalRunnable的定义:

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

void doTraversal() {
        if (mTraversalScheduled) {
            ···
            performTraversals();
            ···
        }
    }

关于TraversalRunnable的调度时机,不再此篇范围了。
到这里,我能回答开篇有同学提到的问题了吧:

View.post(runnable)方法的代码会在view的draw方法之前调用么?

如果按照我们刚分析的performTraversals()方法的执行流程:

getRunQueue().executeActions(attachInfo.mHandler);
        ...
        performMeasure();
        ...
        performLayout();
        ...
        performDraw();

那么答案是明确的:View.post(runnable)方法的代码会在view的draw方法之前调用。

但,这是真的吗?不是!

OMG! 为毛?我曾也天真的以为。

我还是去做了实验,结果:

textview.png

注意到了没?measure被执行了三次,layout被执行了两次,中间穿插了post的Runnable的执行结果,然后在第二次的layout之后才会去执行draw流程!

通过上面的分析,可以明确的是:第一次layout和第二次layout应该是两个不同的任务。因为在这中间已经有了View.post的Runnable的执行结果,所以有了结论是:一共有三个任务,第一次performTraversals、我们的Runnable、第二次performTraversals。

那么为什么会执行两次performTraversals呢?还是要回到performTraversal()方法中,取出与performDraw相关的代码:

           ......
    if (!cancelDraw && !newSurface) {
        if (!skipDraw || mReportNextDraw) {
            ......
            performDraw();
        }
    } else {
        if (viewVisibility == View.VISIBLE) {
            
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).endChangingAnimations();
            }
            mPendingTransitions.clear();
        }
    }
    ......

可以看出,当newSurface为真时,performTraversals函数并不会调用performDraw函数,而是调用scheduleTraversals函数,从而再次调用一次performTraversals函数,从而再次进行一次测量,布局和绘制过程。

到这里终于有了明确答案了:

View.post(runnable)方法的代码不会在view的draw方法之前调用。

但是Android系统设计时,为什么要将整个初始化过程设计成这样?为什么当Surface为新的时候,要推迟绘制,重新进行一轮初始化?

希望有经验的同学解惑啊,欢迎讨论。

together.jpeg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容