如果文章有问题,请及时指出
android 29 源码
dispatchAttachedToWindow()每个view只会调用一次。
一 第一次绘制时
1 ActivityThread.handleResumeActivity()
在ActivityThread.handleResumeActivity()方法中会把DecorView添加到window中。
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
.........
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 把DecorView添加到window中
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
}
2 WindowManagerImpl.addView()
3 WindowManagerGlobal.addView()
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
.......
root = new ViewRootImpl(view.getContext(), display);
// 会调用DecorView.requestLayout()-》ViewRootImpl.requestLayout()
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 这里也会调用ViewRootImpl.requestLayout()
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
4 ViewRootImpl.requestLayout()
5 ViewRootImpl.scheduleTraversals()
6 ViewRootImpl.doTraversal();
7 ViewRootImpl.performTraversals();
这里会调用dispatchAttachedToWindow,DecorView对子View都调用dispatchAttachedToWindow(),不管是否可见。
private void performTraversals() {
.......
// 第一次调用
if (mFirst) {
// 调用DecorView的dispatchAttachedToWindow()
host.dispatchAttachedToWindow(mAttachInfo, 0);
}
performMeasure();
performLayout(); // 2590行
mFirst = false; // 2718行
performDraw(); // 2755行
}
8 View.dispatchAttachedToWindow()
二 第一次绘制后,添加View也会调用
1 ViewGroup.AddView()
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
// 先调用requestLayout
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
2 ViewGroup.addViewInner
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
......
// FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW只有在onAttachedToWindow中添加子View,时才会设置。
// 一般不会有。即使在onAttachedToWindow中addView,
// 也会先执行requestLayout(),再执行dispatchAttachedToWindow
if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
boolean lastKeepOn = ai.mKeepScreenOn;
ai.mKeepScreenOn = false;
// 这里调用dispatchAttachedToWindow
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
if (ai.mKeepScreenOn) {
needGlobalAttributesUpdate(true);
}
ai.mKeepScreenOn = lastKeepOn;
}
}
3 View.dispatchAttachedToWindow()
三 为什么onCreate/onStart/onResume中直接获取View的宽高是不行的,但View.post()就能
首先onCreate/onStart/onResume中不行,是因为这是View还没测量布局。Activity.onResume时,才会请求requestLayout,等待垂直同步信号后,才开始测试布局绘制的。所以在onCreate/onStart/onResume不能获得正确宽高。那么View.post()为什么行?
1 View.post()源码
如果mAttachInfo不等于空,就使用mAttachInfo中handler.post(),这就是往主线程post消息。
如果mAttachInfo为空,就把任务存在一个队列中,每个View都有一个这样的任务队列(记得以前是ViewRootImpl才有的)。
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
/**
* Returns the queue of runnable for this view.
*
* @return the queue of runnables for this view
*/
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
2 dispatchAttachedToWindow中会把任务队列post到主线程中,置空任务队列。
mAttachInfo是在dispatchAttachedToWindow()中赋值的。
View.java
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
// 对mAttachInfo赋值
mAttachInfo = info;
.......
// Transfer all pending runnables.
// 会把任务队列中任务使用Handler.post(),重新丢到主线程中。
// 这里的Handler其实是final ViewRootHandler mHandler = new ViewRootHandler();
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
}
void dispatchDetachedFromWindow() {
mAttachInfo = null;
}
dispatchAttachedToWindow()方法调用后,View.post()和我们自己定义的Handler.post()没有啥区别。主要是在dispatchAttachedToWindow()方法调用前。
这里主要分析dispatchAttachedToWindow()执行前,View.post()的情况。(其实如果之前后能获得,之后肯定没问题)
分2种情况:
1 第一次绘制前,View就已经add到DecorView
在Activity启动时,在onCreate/onStart/onResume中使用View.post()。在第一次绘制时,会调用View.dispatchAttachedToWindow(),把View中任务队列通过ViewRootHandler,放到主线的消息队列中,这样会在第一次绘制之后执行。所以此时能获得View的宽高。
2 第一次绘制后,View才add到DecorView
addView时,会先requestLayout(),再dispatchAttachedToWindow()。View.requestLayout()最后都会到ViewRootImpl.requestLayout()。ViewRootImpl.requestLayout()往主线程的消息队列中发送同步屏蔽消息,之后的所有同步消息禁止执行,只能执行异步消息(在Choreographer中)。dispatchAttachedToWindow()中往主线发送的消息都不能执行,只有等同步垂直信号来了,开始绘制时,才把同步屏蔽消息删除。但这时已经开始测量布局绘制了,主线程消息队列中任务只能之后执行了。所以这种情况也能获得View的宽高。