本篇解决两个问题:
- ViewTree中的继承关系如何建立的?
- mAttachInfo是如何分发的?
1:ViewTree的继承关系如何构建的?
在ActivityThread中触发了handleResumeActivity():
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason){
...
ViewManager wm = a.getWindowManager();
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
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);
}
}
...
}
这里的ViewManager是一个接口,我们知道WindowManager继承了该接口,同时WindowManager的实现类WindowManagerImpl则将具体实现代理给了WindowManagerGlobal来实现。因此会调用到WindowManagerGlobal的addView():
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display);
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 {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
ViewRootImpl是WindowManager和View之间的桥梁。而ViewRootImpl实现了ViewParent接口。ViewGroup也实现了ViewParent接口。
ViewRootImpl在setView()中调用了View的assignParent(),将ViewRootImpl传入了DecorView中,ViewRootImpl也就成了DecorView的父亲。而普通view的这个方法,则是在ViewGroup的addView()中调用,这样每个View的parent就都赋值了。
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
因此我们在Activity的onCreate()中调用:
final View viewById = findViewById(R.id.tv);
viewById.post(new Runnable() {
@Override
public void run() {
ViewParent parent = viewById.getParent();
while (parent!=null){
Log.e("parentName:",parent.getClass().getName());
parent = parent.getParent();
}
}
});
打印的结果:
E/parentName:: androidx.constraintlayout.widget.ConstraintLayout
E/parentName:: androidx.appcompat.widget.ContentFrameLayout
E/parentName:: androidx.appcompat.widget.FitWindowsLinearLayout
E/parentName:: android.widget.FrameLayout
E/parentName:: android.widget.LinearLayout
E/parentName:: com.android.internal.policy.DecorView
E/parentName:: android.view.ViewRootImpl
2:mAttachInfo是如何分发的?
在ViewRootImpl的构造方法中就将自己传入到了View的内部类AttachInfo中。
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
那AttachInfo是什么呢?注释中这么说的
/**
* {@hide}
*
* Not available for general use. If you need help, hang up and then dial one of the following
* public APIs:
*
* @see #isAttachedToWindow() for current attach state
* @see #onAttachedToWindow() for subclasses performing work when becoming attached
* @see #onDetachedFromWindow() for subclasses performing work when becoming detached
* @see OnAttachStateChangeListener for other code performing work on attach/detach
* @see #getHandler() for posting messages to this view's UI thread/looper
* @see #getParent() for interacting with the parent chain
* @see #getWindowToken() for the current window token
* @see #getRootView() for the view at the root of the attached hierarchy
* @see #getDisplay() for the Display this view is presented on
* @see #getRootWindowInsets() for the current insets applied to the whole attached window
* @see #hasWindowFocus() for whether the attached window is currently focused
* @see #getWindowVisibility() for checking the visibility of the attached window
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
AttachInfo mAttachInfo;
我们可以看到很多重要的属性,都保存在该类中。那么mAttachInfo是何时赋值的呢?在View中的该方法中进行赋值:
void dispatchAttachedToWindow(AttachInfo info, int visibility){
mAttachInfo = info;
}
该方法由父ViewGroup来调用
void dispatchAttachedToWindow(AttachInfo info, int visibility){
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}
首先将当前ViewGroup的继承父类View中的mAttachInfo进行了赋值,然后对于每一个ChildView中的mAttachInfo进行了赋值。
在ViewRootImpl.setView()中,会调用requestLayout()。
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
scheduleTraversals()开始分发遍历任务,
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
将任务交给了Choreographer来处理。该类从GPU渲染出来的数据的buffer中取出,交给屏幕展示。
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
private void performTraversals() {
...
// 这个host就是DecorView。而所有的mAttachInfo的分发也就是从这里开始的。
host.dispatchAttachedToWindow(mAttachInfo, 0);
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
...
}
我们可以看到注释的这句
host.dispatchAttachedToWindow(mAttachInfo, 0);
将attachInfo分发给了DecorView,由此触发了所有子ViewGroup的分发。其实这些View拿到的mAttachInfo是同一个。