Android 界面显示的过程可以分为两个步骤
1.是将我们要显示的布局添加到window上
2.在进行测量、布局、绘制
通过这两步我们想看到的View就显示在Window上了
首先要从Activity的setContentView开始
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
看一下getWindow(),也是activity中的方法
public Window getWindow() {
return mWindow;
}
那么看下mWindow这个成员变量是什么
private Window mWindow;
再看下mWindow在哪里初始化的
final void attach(Context context, ActivityThread aThread,
...
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
...
}
原来mWindow 是 PhoneWindow,那么我们再找到setContentView
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
...
}
首先判断mContentParent是否为null,如果为null执行方法installDecor();
那么mContentParent是什么?
看下他的注释吧
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
ViewGroup mContentParent;
意思大概是:这是放置视图窗口的内容,内容是mDecor本身,或mDecor的一个孩子
通过这句话大概能猜想到,但是我们继续往下看
我们看下mContentParent的实例化,在方法installDecor()中实例化的,我们看下installDecor()方法做了什么
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
...
}
看到 mContentParent = generateLayout(mDecor);然后我们再追踪下方法generateLayout(mDecor),里面有这样一行代码
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
再看下 ID_ANDROID_CONTENT是什么?
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
原来这就是为什么叫setContentView的原因,因为他的id就是content。
我们在回到installDecor()方法接着看 ,
installDecor()方法中还实例化了DecorView,而DecorView是ViewTree的最顶层的View,代表了整个应用界面,DecorView包括标题view和内容View,而内容View就是我们上面说的mContentParent,其实就是将我们想要显示的View添加到DecorView的内容部分,
我们再回到setContentView()方法,有这样一行代码
mLayoutInflater.inflate(layoutResID, mContentParent);
通过这行代码就知道mContentParent是我们要显示的View的父控件
到现在为止将我们要显示的布局添加到DecorView上了,那么DecorView又如何添加到Window上
首先要知道在Window上添加view是通过WindowManager,addView()方法,然后我们要定位到Activity的创建过程,
在ActivityThread中,当Activity被创建完毕后,会将DecorView添加到Window中,ActivityThread中的方法handleResumeActivity();
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
if (r != null) {
final Activity a = r.activity;
if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//通过activity获取WindowManager的实现类
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);//将decor添加到window上
}
...
}
在handleResumeActivity方法中
获取WindowManager的实现类 ViewManager wm = a.getWindowManager();
wm.addView();
其实是调用了WindowManagerGlobal#addView()
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;
}
}
在addView的过程中实例化了ViewRootImpl类,然后调用ViewRootImpl的setView方法,并把DecorView作为参数传递进去
这就是将DecorView添加到Window的过程
到现在为止通过setContentView方法,创建了DecorView和加载了我们提供的布局,又将DecorView 添加到Window上,但是现在还是不可见的,因为还没用进行测量,布局和绘制
从ViewRootImpl中的performTraversals()方法开始View的测量、布局、绘制流程
关系总结:
PhoneWindow 是 Window的子类
DecorView 是Window的最顶层View
ViewRoot是建立DecorView与WindowsManger通讯的桥梁