为什么View.post可以获取宽高
先说结论:利用View mAttachInfo关联的Handler往主线程发送任务,任务是在绘制任务之后执行,所以自然就能获取到View的宽高。
源码分析基于android-28
View.post发送任务
java
复制代码
//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添加任务
java
复制代码
//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是发起点
csharp
复制代码
//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类型;
执行队列中的任务
ini
复制代码
//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发起的,
scss
复制代码
//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不失为一种与主线程通讯的方式。
以上分析有不对的地方,请指出,互相学习,谢谢哦!
作者:杨小妞566
链接:https://juejin.cn/post/7090742217484009508
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。