從
WindowManagerService
的角度來看,標題中所說的“窗口”并非Window類
,而是一個View類
考榨。單從語義來講号显,窗口就是用戶所看到的屏幕上的某個獨立的界面臭猜,比如一個Activity界面,一個對話框等等押蚤。
Window類
是一個頂級窗口外觀和行為策略的抽象基類蔑歌。它只是提供標準的UI策略,如背景揽碘,標題區(qū)域次屠,默認鍵處理等。
窗口的類型
Framework
定義了三種窗口類型钾菊,三種類型的定義在WindowManager類
中帅矗。
- 第一種為應(yīng)用窗口。所謂的應(yīng)用窗口是指該窗口對應(yīng)一個
Activity
,由于加載Activity
是由AmS
完成的煞烫,因此浑此,對于應(yīng)用程序來講,要創(chuàng)建一個應(yīng)用類窗口滞详,只能在Activity
內(nèi)部完成凛俱。 - 第二種是子窗口紊馏。所謂的子窗口是指,該窗口必須有一個父窗口蒲犬,父窗口可以是一個應(yīng)用類型窗口朱监,也可以是任何其他類型的窗口。比如
PopupWindow原叮、OptionMenu
赫编。 - 第三類是系統(tǒng)窗口。系統(tǒng)窗口不需要對應(yīng)任何
Activity
,也不需要有父窗口奋隶。對于應(yīng)用程序而言擂送,理論上是無法創(chuàng)建系統(tǒng)窗口的,因為所有的應(yīng)用程序都沒有這個權(quán)限唯欣,然而系統(tǒng)進程卻可以創(chuàng)建系統(tǒng)窗口嘹吨。
創(chuàng)建應(yīng)用窗口
Android進程啟動流程一文中已經(jīng)分析了創(chuàng)建App進程,創(chuàng)建Application境氢,以及實例化Activity等流程蟀拷。
那么視圖又是如何呈現(xiàn)出來的?直觀提供給開發(fā)者的是onCreate()/onResume()
等回調(diào)方法萍聊,通過setContentView()
設(shè)置需要顯示的布局问芬,那么View
是怎么顯示到屏幕中的?為什么在onCreate
方法中獲取不到View
的寬和高呢脐区?
-
ActivityThread
實例化Activity
愈诚,接著調(diào)用Activity
的attach方法
,此方法的作用①設(shè)置Activity
的內(nèi)部變量牛隅,②為此Activity
創(chuàng)建Window對象
炕柔。
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
//省略設(shè)置內(nèi)部變量代碼
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
//給mWindow中的mWindowManager變量賦值
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
}
- 創(chuàng)建好
Window對象
后,需要給Window對象
中的mWindowManager變量
賦值媒佣,該變量的類型是WindowManager類
匕累。每一個Window
內(nèi)部都有一個WindowManager對象
。而WindowManager
只是一個接口類默伍,真正的實現(xiàn)是WindowManagerImpl
欢嘿。
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//這里可以看出mWindowManager是WindowManagerImpl類型。
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
- 接下來
ActivityThread
調(diào)用了performLaunchActivity()
也糊,內(nèi)部調(diào)用了callActivityOnCreate()
炼蹦,輾轉(zhuǎn)調(diào)用到Activity
的onCreate()
。感覺重見天日一般狸剃,此方法對于我們來說再熟悉不過了掐隐。此時我們毫不猶豫調(diào)用了setContentView()
;實際上調(diào)用了Window對象
的setContentView()
。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
}
接著繼續(xù)分析Window
中如何把一個layout.xml
文件作為Window
界面虑省。
- Window的具體實現(xiàn)是PhoneWindow匿刮,PhoneWindow中setContentView代碼如下:
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
}
①首先調(diào)用installDecor()方法,創(chuàng)建DecorView探颈。
②而后調(diào)用generateLayout(DecorView decor)創(chuàng)建mContentParent熟丸,并把它加入到DecorView中。
③給 mContentParent 變量賦值伪节,其值是通過調(diào)用ViewGroup contentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT)
獲 得 的 ,ID_ANDROID_CONTENT
正是 id=content
的FrameLayout
光羞。
④安裝完窗口修飾后,就可以把用戶界面layout.xml
文件添加到窗口修飾中怀大,這是通過在setContentView()
中 調(diào)用inflate()
方法完成的狞山,該方法的第二個參數(shù)正是mContentParent
,即 id=content
的 FrameLayout
叉寂。
⑤最后,回調(diào) cb.onContentChanged()
方法总珠,通知應(yīng)用程序窗口內(nèi)容發(fā)生了改變屏鳍,因為從無到有了。而cb
正是Activity
自身局服,因為Activity
實現(xiàn)了 Window.CallBack
接口钓瞭,并且在attach()
方法中將自身作為Window
對象的Callback
接口實現(xiàn)。
至此淫奔,僅僅是創(chuàng)建了
DecorView
山涡,并添加了mContentParent
,DecorView
還沒有被WindowManager
正式addView
唆迁。
- 接下來
ActivityThread
調(diào)用了handleResumeActivity()
鸭丛,首先調(diào)用performResumeActivity()
,輾轉(zhuǎn)調(diào)用了Activity
的onResume()
唐责,接著會調(diào)用Activity
的makeVisible()
鳞溉。該方法及后續(xù)的各種調(diào)用將完成真正的把窗口添加進WmS之中。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
- 在第2點中講到
WindowManager
只是一個接口類鼠哥,真正的實現(xiàn)是WindowManagerImpl
熟菲,因此真正的addView操作就在此類中。然而它又交給了WindowManagerGlobal處理朴恳。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
-
WindowManagerGlobal
的addView
過程,一個應(yīng)用程序內(nèi)部無論有多少個Activity
, 但只有一個WindowManagerGlobal
對象在WindowManagerGlobal
類中維護三個集合,用于保存該應(yīng)用程序中所擁有的窗口的狀態(tài)情屹。而后調(diào)用ViewRootImpl.setView()
消请,完成最后的、真正意義上的添加工作恍飘。
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//省略檢查參數(shù)合法性的代碼
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
//省略部分代碼
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) {
//省略
throw e;
}
}
-
ViewRootImpl.setView
方法篇幅相對較長榨崩,其主要執(zhí)行的操作有
①給ViewRoot
的重要變量賦值谴垫;
②調(diào)用requestLayout()
,發(fā)出界面重繪請求母蛛;進而執(zhí)行scheduleTraversals()
翩剪,進行View
的繪制工作。
③)調(diào)用sWindowSession.addToDisplay()
彩郊,通知WmS
顯示View前弯。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
mWindowSession的類型是IWindowSession,它是一個Binder對象秫逝,真正的實現(xiàn)類是Session恕出,也就是說Window的添加過程是一次IPC調(diào)用。添加請求交給WmS去處理了违帆。
到此為止浙巫,從客戶端的角度來講,已經(jīng)完成了窗口創(chuàng)建的全部工作刷后。從而界面才會呈現(xiàn)到用戶面前的畴。可見在onCreate的時候View并沒有做測量尝胆、布局丧裁、繪制操作,因此無法獲取到寬高數(shù)據(jù)含衔。
ViewRootImpl
- 鏈接WindowManager和DecorView的紐帶煎娇,更廣一點可以說是Window和View之間的紐帶。
- 完成View的繪制過程贪染,包括measure缓呛、layout、draw過程杭隙。
- 向DecorView分發(fā)收到的用戶發(fā)起的event事件强经,如按鍵,觸屏等事件
Android中的ViewRootImpl類源碼解析
篇幅較長寺渗,很感謝你能從頭閱讀完匿情。具體內(nèi)容可參閱《Android內(nèi)核剖析》