Activity啓動時,View加載到Window流程
1、Window類,是一個抽象類,Window可以理解爲一個載體,所有視圖View的載體。
2、PhoneWindow,Window的主要實現體,該類內部包含了一個DecorView對象,該DectorView對象是所有應用窗口(Activity界面)的根View。
簡而言之,PhoneWindow類是把一個FrameLayout類即DecorView對象進行一定的包裝,將它作爲應用窗口的根View,並提供一組通用的窗口操作
3、DecorView類,Activity視圖的根,該類是PhoneWindow類的內部類,其實就是一個FrameLayout,將內容呈現到PhoneWindow上面。
4、當Activity啓動之後,會將PhoneWindow綁定到當前activity,並在onCreate方法中調用setContentView方法。具體實現爲PhoneWindow裏面的setContentView方法。
5、activity啓動之後,在performLaunchActivity方法中會初始化好activity,並調用attach方法,在該方法中,初始化好了window:mWindow = new PhoneWindow(this, window);
並且設置好windowManager。
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
該方法會拿到WMS遠程服務的代理,並且生成一個本地windowManager:WindowManagerImpl.而WindowManagerImpl中用來實現window與View操作的類是WindowManagerGlobal
6、在performLaunchActivity最後會調用activity的onCreate方法,這裏會調用setContentView來設定用戶需要顯示的內容
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);//①調用PhoneWindow的setContentView的方法
initWindowDecorActionBar();
}
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();//②初始化DecorView,即整個activity的根view
}
mLayoutInflater.inflate(layoutResID, mContentParent);//⑥最後將我們需要設定的佈局文件初始化出來加入到mContentParent中
}
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();//③生成一個DecorView(繼承的FrameLayout)
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//④初始化DecorView的佈局文件,拿到給用戶定義content內容的ViewGroup。
}
protected ViewGroup generateLayout(DecorView decor) {//⑤
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
}
7、內容設定好之後,會在handleResumeActivity方法裏面,在activity的狀態全部初始化完成,走完onResume方法之後,通過window將之前setContentView
初始化好的View添加到窗口上
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);
}
可以看到,最終調用了WindowManager的addView方法,這個方法最終會走到WindowManagerGlobal的addView方法中
8、在WindowManagerGlobal的addView主要做了幾件事:
root = new ViewRootImpl(view.getContext(), display);//新建ViewRoot,通過windowManager加入的View都會有一個自己的ViewRoot
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);//將參數加入到對應的集合中,方便以後管理
root.setView(view, wparams, panelParentView);調用root將View繪製到窗口上
9、setView中最關鍵的2個方法:
1、requestLayout,該方法會觸發整個View樹的繪製。會調用scheduleTraversals,觸發任務mTraversalRunnable調用 doTraversal()最後調用performTraversals方法
在該方法中調動這3個方法來觸發以下3個流程來完成view的繪製
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
performDraw();
2、設定DecorView的parent爲ViewRoot: view.assignParent(this);這樣,在整個View樹中,無論哪個子View調用了requestLayout方法,都會最終
找到ViewRoot的requestLayout方法去執行重新繪製界面的流程。代碼如下:
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
10、View繪製完成之後,會在handleResumeActivity方法裏面調用Activity的makeVisible方法 ,顯示我們剛纔創建的mDecor視圖族。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
可以看到如果沒有加入到window中,會先加入到window中,走上面剛剛分析過的流程,然後設置爲顯示狀態。
View實例化過程:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
factory.inflate(resource, root);
inflate(resource, root, root != null);
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
最終都會調用上面這個方法。參數說明:
resoure,就是佈局文件的資源id
root,所屬父佈局,如果root爲null,那麼該View會沒有layoutparam參數。參考源碼。在RecycleView或者listView這種有時候需要重新設定子item佈局參數的時候需要注意
attachToRoot,是否加入到root中,如果設定爲true,則實例化完之後自動添加進入root中,如果false,則不是。在RecycleView或者listView中,由於它們會自動
將child加入,所以使用這2個控件的時候,實例化子item的時候一定要傳入false。否則會報錯java.lang.IllegalStateException: The specified child already
has a parent. You must call removeView() on the child's parent first.
<merge />只能作爲XML佈局的根標籤使用。當Inflate以<merge />開頭的佈局文件時,必須指定一個父ViewGroup,並且必須設定attachToRoot爲true。
子線程更新UI:子線程更新UI必須要在子線程中有自己的ViewRoot,因爲在ViewRoot初始化的時候會拿到當前的線程對象,而在繪製View的時候,會執行CheckThread方法會檢查當前線程是否是創建ViewRoot的這個線程。如果不是,那麼會拋出異常,如果是,那麼不會。所以我們在主線程創建UI的時候,默認的ViewRoot中的當前線程對象是UI線程,在其他線程更新UI就會報錯。