從
WindowManagerService
的角度來看,標題中所說的“視窗”並非Window類
,而是一個View類
。單從語義來講,視窗就是使用者所看到的螢幕上的某個獨立的介面,比如一個Activity介面,一個對話方塊等等。Window類
是一個頂級視窗外觀和行為策略的抽象基類。它只是提供標準的UI策略,如背景,標題區域,預設鍵處理等。
視窗的型別
Framework
定義了三種視窗型別,三種型別的定義在WindowManager類
中。
- 第一種為應用視窗。所謂的應用視窗是指該視窗對應一個
Activity
,由於載入Activity
是由AmS
完成的,因此,對於應用程式來講,要建立一個應用類視窗,只能在Activity
內部完成。 - 第二種是子視窗。所謂的子視窗是指,該視窗必須有一個父視窗,父視窗可以是一個應用型別視窗,也可以是任何其他型別的視窗。比如
PopupWindow、OptionMenu
。 - 第三類是系統視窗。系統視窗不需要對應任何
Activity
,也不需要有父視窗。對於應用程式而言,理論上是無法建立系統視窗的,因為所有的應用程式都沒有這個許可權,然而系統程式卻可以建立系統視窗。
建立應用視窗
Android程式啟動流程一文中已經描述了App程式的啟動、建立Application,以及例項化Activity。
帶著疑問而來,Activity
直觀提供給開發者的是onCreate()/onResume()
等回撥方法,通過setContentView()
設定需要顯示的佈局,那麼View
是怎麼顯示到螢幕中的?為什麼在onCreate方法
中獲取不到View
的寬和高呢?
看完以下內容,將會豁然開朗。
ActivityThread
例項化Activity
,接著呼叫Activity
的attach方法
,此方法的作用①設定Activity
的內部變數,②為此Activity
建立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) { //省略設定內部變數程式碼 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(); }複製程式碼
建立好
Window物件
後,需要給Window物件
中的mWindowManager變數
賦值,該變數的型別是WindowManager類
。每一個Window
內部都有一個WindowManager物件
。而WindowManager
只是一個介面類,真正的實現是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
呼叫了performLaunchActivity()
,內部呼叫了callActivityOnCreate()
,輾轉呼叫到Activity
的onCreate()
。感覺重見天日一般,此方法對於我們來說再熟悉不過了。此時我們毫不猶豫呼叫了setContentView()
;實際上呼叫了Window物件
的setContentView()
。public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); }複製程式碼
接著繼續分析
Window
中如何把一個layout.xml
檔案作為Window
介面。Window的具體實現是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); } }複製程式碼
①首先呼叫installDecor()方法,建立DecorView。
②而後呼叫generateLayout(DecorView decor)建立mContentParent,並把它加入到DecorView中。
③給 mContentParent 變數賦值,其值是通過呼叫ViewGroup contentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT)
獲 得 的 ,ID_ANDROID_CONTENT
正是id=content
的FrameLayout
。
④安裝完視窗修飾後,就可以把使用者介面layout.xml
檔案新增到視窗修飾中,這是通過在setContentView()
中 呼叫inflate()
方法完成的,該方法的第二個引數正是mContentParent
,即id=content
的FrameLayout
。
⑤最後,回撥cb.onContentChanged()
方法,通知應用程式視窗內容發生了改變,因為從無到有了。而cb
正是Activity
自身,因為Activity
實現了Window.CallBack
介面,並且在attach()
方法中將自身作為Window
物件的Callback
介面實現。
至此,僅僅是建立了DecorView
,並新增了mContentParent
,DecorView
還沒有被WindowManager
正式addView
。接下來
ActivityThread
呼叫了handleResumeActivity()
,首先呼叫performResumeActivity()
,輾轉呼叫了Activity
的onResume()
,接著會呼叫Activity
的makeVisible()
。該方法及後續的各種呼叫將完成真正的把視窗新增進WmS之中。void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); }複製程式碼
- 在第2點中講到
WindowManager
只是一個介面類,真正的實現是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
過程,一個應用程式內部無論有多少個Activity
, 但只有一個WindowManagerGlobal
物件在WindowManagerGlobal
類中維護三個集合,用於儲存該應用程式中所擁有的視窗的狀態。而後呼叫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) { //省略檢查引數合法性的程式碼 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; } }複製程式碼
setView
方法篇幅相對較長,其主要執行的操作有
①給ViewRoot
的重要變數賦值;
②呼叫requestLayout()
,發出介面重繪請求;進而執行scheduleTraversals()
,進行View
的繪製工作。
③)呼叫sWindowSession.addToDisplay()
,通知WmS
新增視窗。res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel);複製程式碼
mWindowSession的型別是IWindowSession,它是一個Binder物件,真正的實現類是Session,也就是說Window的新增過程是一次IPC呼叫。新增請求交給WmS去處理了。
到此為止,從客戶端的角度來講,已經完成了視窗建立的全部工作。從而介面才會呈現到使用者面前。可見在onCreate的時候View並沒有做測量、佈局、繪製操作,因此無法獲取到寬高資料。
希望後續有時間補一張流程圖。
篇幅較長,很感謝你能從頭閱讀完。具體內容可參閱《Android核心剖析》