不管是 Activity 還是 Dialog 都有一個 window 對象颖御,對應(yīng)的 view 屬于 window汤求,用戶對于界面最直接的感知就是 window议慰,理解和掌握 window 的啟動過程對于開發(fā)者而言尤為重要速址,以下主要分析 Activity 的 window 啟動過程龄糊。
注逆粹,以下代碼都是基于 api=26。
我們從 Activity.attach 方法開始講炫惩,在這個方法里僻弹,完成 window 實(shí)例的創(chuàng)建。(該方法在什么時(shí)候調(diào)用的他嚷,在這里不分析蹋绽,有興趣的朋友可以看另一篇文章: http://www.reibang.com/p/7a904184afc6)
final void attach(Context context, ActivityThread aThread,... {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
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.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
...
}
我們可以看到 window 的實(shí)例其實(shí)就是 PhoneWindow,還有一點(diǎn)值得注意的是 mWindowManager 的賦值筋蓖,這里的 context 實(shí)際是 ContextImpl卸耘,通過 context.getSystemService(Context.WINDOW_SERVICE) 獲取到的實(shí)際是 WindowManagerImpl,是 app 進(jìn)程的一個實(shí)例扭勉,不屬于系統(tǒng)進(jìn)程鹊奖,我們可以隨便看一下該類的方法
WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerImpl 只是一個代理類,實(shí)際操作都是由 WindowManagerGlobal 來完成涂炎,后續(xù)所有 window 相關(guān)操作都與這個類有關(guān)忠聚,我們先繼續(xù)往下走。
Activity.attch 我們直接跳到 Activity.setContentView 方法
// Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Activity.setContentView 直接調(diào)用的是 window 的方法
// PhoneWindow
public void setContentView(int layoutResID) {
...
installDecor();
...
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}
installDecor() 方法主要完成 DecorView 的創(chuàng)建唱捣、為 DecorView 添加 ContentView两蟀、設(shè)置背景、從style中提取對應(yīng)的屬性...
// PhoneWindow
private void installDecor() {
...
mDecor = generateDecor(-1);
...
}
protected ViewGroup generateLayout(DecorView decor) {
...
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 在這里添加的 layout
...
}
// DecorView
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
...
}
這里我們看到代碼中多次使用 LayoutInflater.inflate 和 ViewGroup.addView 方法震缭,前者最終也是調(diào)用 ViewGroup.addView赂毯,我們來看一下 ViewGroup.addView 方法主要干了些什么
// ViewGroup
public void addView(View child, int index, LayoutParams params) {
...
requestLayout(); // 核心方法
invalidate(true);
addViewInner(child, index, params, false); // 完成添加 view 到數(shù)據(jù)結(jié)構(gòu)等操作...
...
}
主要看下 ViewGroup.requestLayout
// ViewGroup
public void requestLayout() {
...
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;
}
...
}
可以看出 requestLayout 操作機(jī)制是逐步往上級請求,直到能受理該操作的實(shí)例拣宰,只有 DecorView 才有 ViewRootImpl党涕,所以實(shí)際上最終是有 ViewRootImpl 來完成 requestLayout操作的,但是這里需要注意此時(shí)的 DecorView mAttachInfo 和 mParent 都等于 null巡社,所以實(shí)際上沒有進(jìn)行任何操作膛堤。
下一個與 Window 相關(guān)的邏輯在 ActivityThread.handleResumeActivity,注意此時(shí)已經(jīng)跑過了 Activity.onCreate
// ActivityThread
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
r = performResumeActivity(token, clearHide, reason); // 這句話會回調(diào) Activity.onResume
...
a.mWindowAdded = true;
wm.addView(decor, l);
...
}
wm 即是 WindowManagerImpl晌该,上文已經(jīng)說過了 WindowManagerImpl 操作都交由 WindowManagerGlobal肥荔,所以我們直接看 WindowManagerGlobal.addView
// WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display); // 創(chuàng)建 ViewRootImpl 實(shí)例,每個 window 只有一個 ViewRootImpl
view.setLayoutParams(wparams);
// 將 view 添加到對應(yīng)的集合中朝群,記錄
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 構(gòu)造方法也值得一看
public ViewRootImpl(Context context, Display display) {
...
mWindowSession = WindowManagerGlobal.getWindowSession(); // 獲取的是系統(tǒng)進(jìn)程的遠(yuǎn)程對象燕耿,通過它與 WindowManagerService 連接
...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
...
}
WindowManagerGlobal.addView 主要完成了 ViewRootImpl 創(chuàng)建,DecorView 與 ViewRootImpl 建立關(guān)系是在 ViewRootImpl.setView 方法中完成
// ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
requestLayout();
...
// 通過 mWindowSession 對象最終會調(diào)用 WindowManagerService.addView
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
...
view.assignParent(this); // 將 DecorView mParent = this
}
我們先看下 ViewRootImpl.requestLayout()
// ViewRootImpl
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
// 最終會啟動 TraversalRunnable -> run -> doTraversal() - >performTraversals()
scheduleTraversals();
}
}
private void performTraversals() {
...
if (mFirst) {
...
// 通過 ViewGroup.dispatchAttachedToWindow 最終響應(yīng)每個子 View 的 dispatchAttachedToWindow 方法姜胖,響應(yīng)每個監(jiān)聽 listener.onViewAttachedToWindow
host.dispatchAttachedToWindow(mAttachInfo, 0);
...
}
...
}
我們可以看到在 performTraversals 方法中觸發(fā) dispatchAttachedToWindow 事件誉帅,到這一步已經(jīng)完成了 window 的添加。
總結(jié)
一個 window 對應(yīng)一個 ViewRootImpl右莱,一個 ViewRootImpl 對應(yīng) 一個 DecorView堵第,與 wms 發(fā)生關(guān)聯(lián)操作是由 ViewRootImpl 完成的,requestLayout 是由 ViewRootImpl 完成隧出,dispatchAttachedToWindow 也是由 ViewRootImpl 觸發(fā)踏志,依次往下走,用 Context 獲取的 WindowManager 調(diào)用 addView 并不是直接調(diào)用 wms.addView 方法胀瞪,而是存在于 app 進(jìn)程的 WindowMnagerGlobal.addView()方法针余。