面试官问我:View.post为什么能够获取View的宽高
Posted 天才少年_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试官问我:View.post为什么能够获取View的宽高相关的知识,希望对你有一定的参考价值。
记得看文章三部曲,点赞,评论,转发。
微信搜索【程序员小安】关注还在移动开发领域苟活的大龄程序员,“面试系列”文章将在公众号同步发布。
1.前言
最近看到几个技术群里都在吐槽目前面试八股文泛滥,作为技术担当的天才少年_决定要去各个大厂踢馆,用我扎实的八股文功底来教育一下他们。
2.正文
一路披荆斩棘,天才少年终于来到一个大厂的面试会议室。
是心动啊,糟糕眼神躲不掉,对你莫名的心跳,竟然停不了对你的迷恋~~
小伙子,我是今天的面试官,你不要对我抱有幻想,准备好了的话,咱们直接开始面试吧。
什么鬼,我的内心他怎么知道!
在Activity的oncreate方法中如何获取view的宽度和高度?
很简单,直接用view.post方法就可以获取到,代码如下所示:
view.post(new Runnable()
@Override
public void run()
//获取宽度和高度
);
嗯,为什么一定要调用post方法呢?如果直接调用getHeight取到的值是多少呢?
嘿嘿,绕来绕去,不就是view.post源码的执行流程嘛,天才少年岂是浪得虚名,为了熟读framework源码,我抛弃了小琴,那是个风高夜黑的晚上,小琴拉着我的手问我愿不愿意做孩子的父亲,我反手就是一巴掌,怎么能让小孩来打破我的单身生活。思绪飘得有点远,我推了推眼镜,试图掩盖留下的泪水,清了清嗓子,准备开始讲(装)解(逼)。
先看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;
代码量不大,可以很明显的看出来,attchInfo对象起到决定性作用,他是否为空,决定下面流程的运转,我先站在上帝的视角跟你说一下,attchInfo是在ViewRootImpl的构造函数中初始化,ViewRootImpl是在onResume之后才会创建,具体的源码,后面我会单独写一篇文章来讲解。
这边你只需要知道,在onCrate方法里面,attchInfo为空,则会走到ViewRootImpl.getRunQueue().post(action);
RunQueue的源码如下所示:
static final class RunQueue
private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();//post发送的action(Runnable)放入HandlerAction对象中,然后存入mActions集合中
void post(Runnable action)
postDelayed(action, 0);
void postDelayed(Runnable action, long delayMillis)
HandlerAction handlerAction = new HandlerAction();//post发送的action(Runnable)放入HandlerAction对象中
handlerAction.action = action;
handlerAction.delay = delayMillis;
synchronized (mActions)
mActions.add(handlerAction);//存入mActions集合中
void removeCallbacks(Runnable action)
final HandlerAction handlerAction = new HandlerAction();
handlerAction.action = action;
synchronized (mActions)
final ArrayList<HandlerAction> actions = mActions;
while (actions.remove(handlerAction))
// Keep going
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);//循环遍历mActions中的HandlerAction对象,把Runnable发送到该handler的messageQueue中。
actions.clear();
private static class HandlerAction
Runnable action;
long delay;
@Override
public boolean equals(Object o)
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HandlerAction that = (HandlerAction) o;
return !(action != null ? !action.equals(that.action) : that.action != null);
@Override
public int hashCode()
int result = action != null ? action.hashCode() : 0;
result = 31 * result + (int) (delay ^ (delay >>> 32));
return result;
这代码有点多,可以稍微解释一下吗?
别急啊,装逼不需要思考的吗?
核心代码我已经注释过了,相信很好理解:当调用ViewRootImpl.getRunQueue().post(action)时,该action被存储在RunQueue类中的集合
mActions中,等外部调用executeActions方法时,才会真正的执行action的回调。再一次借用上帝视角,这个handler其实就是UI线程的,所以这个时候才是真正的把action放到UI线程对应的MessageQueue中。
那是哪个外部调用executeActions方法的呢?可以讲一讲吗?
肯定可以啊,我来就是面试的,难不成来跟你相亲的。
我们知道View的渲染离不开ViewRootImpl类,页面绘制从requestLayout开始,那我们今天就从requestLayout方法往下讲,如果你想听requestLayout前面的东西,二面咱们细聊,又是一个勾引面试欲望的小技巧。
@Override
public void requestLayout()
checkThread();
mLayoutRequested = true;
scheduleTraversals();
checkThread前面分析过,就是检查更新线程跟新建view的线程是否是用一个,跟今天要讲的不相关,暂时忽略,我们看scheduleTraversals()
void scheduleTraversals()
if (!mTraversalScheduled)
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();//设置同步屏障
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//通过mChoreographer垂直脉冲刷新页面,调用mTraversalRunnable
scheduleConsumeBatchedInput();
可以看到这边设置了同步屏障,同步屏障和异步消息这个咱们后面单独用一篇文章来讲,这边你只需要知道,用同步屏障后,优先执行异步消息,而mChoreographer内部必然是异步消息,见下面的代码:
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);//设置异步消息
mHandler.sendMessageAtTime(msg, dueTime);
继续看mTraversalRunnable的代码,如下所示:
final class TraversalRunnable implements Runnable
@Override
public void run()
doTraversal();
void doTraversal()
if (mTraversalScheduled)
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
if (mProfile)
Debug.startMethodTracing("ViewAncestor");
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
try
performTraversals();
finally
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
if (mProfile)
Debug.stopMethodTracing();
mProfile = false;
继续看performTraversals();
private void performTraversals()
// cache mView since it is used so much below...
final View host = mView;
//省略一些代码
host.dispatchAttachedToWindow(attachInfo, 0);//**注意performMeasure,performLayout,performDraw在这个方法后面哦**
mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets);
host.fitSystemWindows(mFitSystemWindowsInsets);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//测量
performLayout(lp, mWidth, mHeight); // 布局
performDraw();//绘制
可以看到在performTraversals方法中,调用了host.dispatchAttachedToWindow(attachInfo, 0),host其实View,那我们看下View的源码:
void dispatchAttachedToWindow(AttachInfo info, int visibility)
mAttachInfo = info; // 这个时候attachinfo才被赋值
... 省略部分代码
// Transfer all pending runnables.
if (mRunQueue != null)
mRunQueue.executeActions(info.mHandler); // 终于看到他了
//executeActions方法会循环遍历mActions中的HandlerAction对象,把Runnable发送到该handler的messageQueue中。
mRunQueue = null;
到这里基本真想大白了,调用view.post方法并不是实时调用,而是被存储在RunQueue类中的集合
mActions中,等外部调用executeActions方法,而executeActions是在View的dispatchAttachedToWindow方法中被调用。
时序图如下所示:
看你的分析,executeActions方法是在页面刷新方法(performMeasure,performLayout,performDraw)后面,明明页面刷新在调用之后,为什么view.post还能拿到view的宽和高呢?
可以啊,这都能看出来,不过我既然来了,就打算得到你,不是,就是回答你。
还记得我前面讲到同步屏障吗,怕你不记得,代码再贴给你看下
void scheduleTraversals()
if (!mTraversalScheduled)
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();//设置同步屏障
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//通过mChoreographer垂直脉冲刷新页面,调用mTraversalRunnable
scheduleConsumeBatchedInput();
mChoreographer.postCallback其实也是一个Handler消息,并且是异步消息,上面已经讲过了,所以他会优先执行,而通过view.post发送的消息是同步消息,当设置了同步屏障后,会优先执行异步消息,所以必须等performMeasure,performLayout,performDraw流程走完后,view.post发送的消息才能真正的执行,自然也就可以拿到view的宽和高了。
可以了,你对handler,view,ViewRootImpl了解很深刻,今天有点晚了,消息屏障,view的创建流程,mChoreographer发送消息的流程,咱们后面再聊。
微信搜索【程序员小安】“面试系列(java&andriod)”文章将在公众号同步发布。
以上是关于面试官问我:View.post为什么能够获取View的宽高的主要内容,如果未能解决你的问题,请参考以下文章