Android 界面顯示的過程可以分爲兩個步驟
1.是將我們要顯示的佈局添加到window上
2.在進行測量、佈局、繪製
通過這兩步我們想看到的View就顯示在Window上了
今天說下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上
將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通訊的橋樑