本文研究基于api24
上一篇谈谈Activity的setContentView是怎么加载XML视图的我们了解到了添加的布局文件中的View是如何被添加到Activity的窗口的,今天要解决的是添加到PhoneWindow的View是怎么与View绘制工具ViewRootImpl关联的
上一篇中有介绍到在PhoneWindw的generateLayout方法中最后有确定DecorView的内容和一个装载我们自己添加的布局的ViewGroup,其中的
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)
这个方法最终会走到ViewGroup的addView方法;而且generateLayout方法最后会调用mDecor.finishChanging()这个方法
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();
invalidate(true);
addViewInner(child, index, params, false);
}
void finishChanging() {
mChanging = false;
drawableChanged();
}
private void drawableChanged() {
if (mChanging) {
return;
}
setPadding(mFramePadding.left + mBackgroundPadding.left,
mFramePadding.top + mBackgroundPadding.top,
mFramePadding.right + mBackgroundPadding.right,
mFramePadding.bottom + mBackgroundPadding.bottom);
requestLayout();
invalidate();
}
确定Activity的根布局后,最后回到PhoneWindow的setContentView方法时,几个重载方法里面的调用如下
mLayoutInflater.inflate(layoutResID, mContentParent);
或者mContentParent.addView(view, params);
这几个方法是真正把我们设置的布局内容添加到Activity的窗口里;同时这几个方法最终也会走到上面的addView方法。
很明显可以看到上述贴出的方法里有两句代码
requestLayout();
invalidate(true);
我们平时进行自定义View的开发的时候知道这两个方法的调用会引发View的重新布局和重新绘制,但是这里就这么调用了难道在这里就开始绘制View了?
感觉不对劲啊,Android开发者都知道View的绘制是由ViewParent(ViewRootImpl类是这个类的实现类,ViewParent是一个接口)去完成的,但是我们研究setContentView源码下来暂时没发现View跟这个ViewRootImpl产生关联啊,也就是没看到View拿到View的绘制工具,那问题就来了,那他们两是什么时候关联了呢?
既然正方向不好找,那就从反反向倒推吧,上面贴出的代码都会走requestLayout()方法,它是View类的方法
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
可以看到此时会调用到mParent.requestLayout(),由mParent去确定布局,这个mParent是什么呢,看定义
protected ViewParent mParent;
这个类就是View的绘制工具了,由ViewRootImpl实现,接下来在这个类里找它被初始化的地方,如下
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");
}
}
找了一下发现View类里没有调用这个方法,那看看ViewGroup类,因为ViewGroup是View的子类;可以看到ViewGroup有调用
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
if (mTransition != null) {
// Don't prevent other add transitions from completing, but cancel remove
// transitions to let them complete the process before we add to the container
mTransition.cancel(LayoutTransition.DISAPPEARING);
}
....
....
// tell our children
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
....
....
}
继续查找这个addViewInner被谁调用了,最后是ViewGroup的addView方法调用的,好像又绕回来了,这不是死局吗,再理一下思路
- DecorView是Activity根布局,他添加一个layout的时候最终会调用ViewGroup的addView方法,其实DecorView就是ViewGroup的子类
- DecorView的layout中有一个FrameLayout是作为mContentParent用来添加我们设置的布局;添加的时候也会调用ViewGroup的addView方法,因为mContentParent就是ViewGroup
- ViewGroup最终会调用View的requestLayout去确定布局,但是ViewGroup也是View的子类,这样第一步和第二步产生的ViewGroup跳过中间环节就是走到了View里,并且View里会让ViewParent去完成最终的绘制工作
- 我们知道View的绘制是先绘制最外层父布局,然后循环迭代绘制里面的view,而绘制工作是由ViewParent完成,而它的实现类是ViewRootImpl,那结果就是DecorView使用ViewRootImpl去绘制视图,也就是整个Activity的视图交由ViewRootImpl去绘制
- DecorView的终极父类是View,那最终的目的就是要搞清View是什么时候和ViewRootImpl产生关联的
- 上面分析可知View持有ViewParent的引用,并且只有一处是ViewParent的初始化,也就是assignParent这个方法;那解决思路就是要找到这个方法是在哪里被调用以让View持有它的引用,用来绘制视图
assignParent被调用之处
这里涉及到Activity的启动相关知识,这一复杂流程就留在下一篇文章分析,这里抛出结果。
每个应用都有一个主线程,也就是main线程,所有的UI操作都要在主线程操作,而这个主线程就是ActivityThread,在这个类里有一个内部类H,继承自Handler,在里面的handleMessage方法中有下面这段代码
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
SomeArgs args = (SomeArgs) msg.obj;
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
args.argi3, "RESUME_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
这里走到了handleResumeActivity这个方法
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
}
// TODO Push resumeArgs into the activity for consideration
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
......
......
......
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
...
...
...
}
方法很长,省略了很多,留了关键的,可以看到在if (r.window == null && !a.mFinished && willBeVisible) 这个判断里
- 先通过 r.activity.getWindow()获取当前Activity的PhoneWindow,再通过PhoneWindow获取DecorView,然后把DecorView设置隐藏;其实这里也说明在这个handler的RESUME_ACTIVITY消息里,一开始Activity的View还没显示
- 通过a.getWindowManager()获取PhoneWindow的ViewManager;最后通过wm.addView(decor, l)把DecorView添加到了ViewManager
ViewManager是个接口,子类是WindowManager,实现类是WindowManagerImpl
我们找到WindowManagerImpl的addview方法
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
mGlobal是WindowManagerGlobal,继续到这个类去
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
....
....
ViewRootImpl root;
View panelParentView = null;
...
...
...
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.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
这个方法里new了一个ViewRootImpl实例,然后调用了它的setView方法,这个方法很长,省略如下
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
……
view.assignParent(this);
}
}
}
可以看到调用了View类的assignParent方法,其实这个view是前面传入的DecorView,到这里DecorView总算跟ViewRootImpl关联起来了。