View.post如何获取宽高
先说结论:利用View mAttachInfo关联的Handler往主线程发送任务,任务是在绘制任务之后执行,所以自然就能获取到View的宽高。
View.post发送任务
//View类中
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);//1
}
getRunQueue().post(action);//2
return true;
}
- 注释1:当mAttachInfo不为空,则获取Handler发送任务,mAttachInfo是View添加到Window的标志,不为空则表示添加到Window了,但是不一定绘制完成,Handler是添加View的线程,默认是主线程的;
- 注释2:当mAttachInfo为空,往其消息队列添加任务,等待被执行,注意是等待,getRunQueue()获取是HandlerActionQueue类型对象;
HandlerActionQueue.post添加任务
//View类中
public void post(Runnable action) {
postDelayed(action, 0);//1
}
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);//2
mCount++;
}
}
- 注释1:post会走到postDelayed,只不过delayMillis为0;
- 注释2:将post的任务(获取宽高),封装成HandlerAction添加到mActions数组当中;
数组任务什么时候被执行
View.dispatchAttachedToWindow是发起点
//View类中
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info; //1
......
// Transfer all pending runnables
if (mRunQueue != null) {//2
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;//3
}
......
}
- 注释1:看到这里了吗,这里会给mAttachInfo赋值;
- 注释2:mRunQueue跟getRunQueue()对应,当其不为空,则表示有任务需要被执行,将mAttachInfo的Handler作为参数传入,,mRunQueue是HandlerActionQueue类型;
执行队列中的任务
//HandlerActionQueuel类中
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);//1
}
mActions = null;
mCount = 0;
}
}
- 注释1:这里没什么特别的,就是往mAttachInfo的Handler发送任务;
到这里发现,无论是mAttachInfo是否为空,最终都是往mAttachInfo的Handler发送任务;
只是发送任务,也不能保证它绘制任务之后吧?
这里涉及到View的绘制流程,我们知道绘制流程是从ViewRootImpl.performTraversals发起的,
//ViewRootImpl类中
private void performTraversals() {
final View host = mView;//1
......
host.dispatchAttachedToWindow(mAttachInfo, 0);//2
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
......
performMeasure();
......
performLayout();
......
performDraw();
......
}
- 注释1:mView就是DecorView,这里表示从顶层View开始dispatchAttachedToWindow,
- 注释2:mAttachInfo是在创建ViewRootImpl的时候实例化的,其维护的Handler对应主线程,有兴趣可以看到ViewRootImpl的构造函数;Android是消息驱动的,获取View宽高Runnable需要在主线程执行,绘制任务(performTraversals)也是主线程执行,而前者是在后者中发送的,所以执行顺序就很显然了,执行完绘制Runnable,再执行获取宽高Runnable,所以就能正常宽高。
延伸
除了获取宽高场景,View.post不失为一种与主线程通讯的方式。
以上分析有不对的地方,请指出,互相学习,谢谢哦!
写在最后
在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。
如果你觉得自己学习效率低,缺乏正确的指导,可以扫码,加入我们资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
加入我们吧!里面有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。