在Android中所有的視圖都是通過Window來呈現(xiàn)的,Window是View的直接管理者,每一個Activity都對應(yīng)著一個Window,Activity的視圖DecorView會被添加到其Window中;另外,如果我們想要實現(xiàn)懸浮窗的效果,那么也離不開Window的開發(fā)。Android為我們提供了WindowManager類可以用來管理Window,WindowManager可以通過Activity的getWindowManager()獲得,WindowManager實現(xiàn)了ViewManager接口,這個接口有三個方法:addView()、updateViewLayout()、removeView(),通過這三個方法就可以完成Window的添加、更新和刪除操作,接下來我們分別看看這三個方法的執(zhí)行流程。
Window的添加過程
我們通過Activity對Window的處理來分析下Window的添加流程。在啟動一個Activity時,會調(diào)用到ActivityThread的performLaunchActivity()創(chuàng)建Activity實例并調(diào)用它的attach()進行初始化,如下所示:
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.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
...
mWindowManager = mWindow.getWindowManager();
...
}
該方法首先會創(chuàng)建一個Window實例并賦值給mWindow,Window 是個抽象的概念, 具體實現(xiàn)類是 PhoneWindow,接著會獲取一個WindowManager對象,WindowManager是一個接口類型,具體實現(xiàn)是WindowManagerImpl,最后調(diào)用Window.setWindowManager()給Window設(shè)置WindowManager對象,如下所示:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
方法內(nèi)通過調(diào)用WindowManager的createLocalWindowManager()創(chuàng)建了一個新的WindowManager對象并賦值給mWindowManager,該方法內(nèi)部直接new了一個WindowManagerImpl對象,因此可以知道每個Window都會對應(yīng)一個WindowManagerImpl對象,方法如下所示:
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
在Activity初始化完成后會調(diào)用Activity的onCreate(),而調(diào)用onCreate()時會調(diào)用setContentView()設(shè)置布局,其內(nèi)部會調(diào)用剛剛創(chuàng)建的Window的setContentView(),如下所示:
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
...
}
可以看到內(nèi)部會調(diào)用installDecor(),該方法會根據(jù)不同的Theme創(chuàng)建不同的DecorView,DecorView 是一個 FrameLayout,setContentView()最終會把布局設(shè)置到DecorView中id為content的View上。到這里創(chuàng)建了 PhoneWindow和DecorView,但目前二者也沒有任何關(guān)系。當(dāng)Activity的狀態(tài)變成resume時,最終會調(diào)用到ActivityThread的handleResumeActivity(),如下所示:
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
boolean willBeVisible = !a.mStartedActivity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
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 (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}
...
}
該方法首先會調(diào)用performResumeActivity()執(zhí)行Activity的onResume(),接著獲取到前面創(chuàng)建的WindowManagerImpl對象并調(diào)用其addView()創(chuàng)建DecorView對應(yīng)的Window,接下來看下WindowManagerImpl的內(nèi)部實現(xiàn):
public final class WindowManagerImpl implements WindowManager {
public void addView(View view, ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
public void removeView(View view) {
mGlobal.removeView(view, false);
}
...
}
WindowManagerImpl實現(xiàn)了WindowManager的方法,但并沒有具體實現(xiàn)具體的操作,而是調(diào)用WindowManagerGlobal對應(yīng)的方法來完成,接下來看一下WindowManagerGlobal的addView():
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
...
root.setView(view, wparams, panelParentView);
...
}
這個過程創(chuàng)建一個ViewRootImpl,并將View、ViewRootImpl以及LayoutParams等參數(shù)保存到一個列表中。最后會調(diào)用ViewRootImpl的setView(),如下所示:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
mView = view;
...
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
...
view.assignParent(this);
...
}
該方法首先將傳入的View賦值給ViewRootImpl的成員變量mView,這里傳入的View是DecorView,這樣DecorView就交由ViewRootImpl進行管理了;接著調(diào)用mWindowSession.addToDisplay(),mWindowSession是個Binder對象,會與WMS通信由WMS完成Window的創(chuàng)建;最后會調(diào)用傳入的View的assignParent(),該方法會將View的mParent設(shè)為當(dāng)前的ViewRootImpl,mParent是ViewParent類型,ViewRootImpl和ViewGroup都實現(xiàn)了ViewParent接口,當(dāng)ViewGroup(如DecorView)調(diào)用addView()添加子View時,會將子View的mParent置為該ViewGroup,這樣就形成了ViewRootImpl -> DecorView -> ViewGroup -> ... -> View這樣的樹形結(jié)構(gòu)。ViewParent接口最常見的一個方法是requestLayout(),當(dāng)調(diào)用View或ViewGroup的requestLayout()時,會找到View樹的根節(jié)點ViewRootImpl,執(zhí)行ViewRootImpl的requestLayout(),執(zhí)行該方法時會依次執(zhí)行performMeasure()、performLayout()、performDraw(),它們的內(nèi)部會分別調(diào)用mView的measure()、layout()、draw(),上面已經(jīng)說到這里的mView就是DecorView,DecorView會遍歷其子View向下傳遞事件,這樣一個View樹的工作流程就啟動了。
ViewRootImpl可以理解成是WindowManager和DecorView的紐帶,它們的關(guān)系如下如所示:

接下來看一下WMS部分的處理邏輯,在調(diào)用IWindowSession.addToDisplay()后,會調(diào)用到Session的addToDisplay(),其內(nèi)部又調(diào)用了WindowManagerService.addWindow(),該方法主要做了以下幾件事:
- 對所要添加的窗口進行權(quán)限及類型的檢查,如果窗口不滿足一些條件,就不會再執(zhí)行下面的代碼邏輯。
- WindowToken相關(guān)的處理,比如有的窗口類型需要提供WindowToken,沒有提供的話就不會執(zhí)行下面的代碼邏輯,有的窗口類型則需要由WMS隱式創(chuàng)建默認(rèn)windowToken。
- WindowState的創(chuàng)建和相關(guān)處理,將WindowToken和WindowState相關(guān)聯(lián)。WindowState存有窗口的所有的狀態(tài)信息,在WMS中它表示一個窗口。
- 創(chuàng)建和配置DisplayContent,完成窗口添加到系統(tǒng)前的準(zhǔn)備工作。
在創(chuàng)建WindowState時會傳入一個IWindow類型的對象作為參數(shù),這是一個Binder對象,IWindow實現(xiàn)是ViewRootImpl的內(nèi)部類W,IWindow會將WMS中窗口管理的操作回調(diào)給ViewRootlmpl,這樣一個Window就創(chuàng)建完成了,創(chuàng)建Window過程中涉及的各個對象也都建立起了聯(lián)系。
根據(jù)上面的分析可以知道創(chuàng)建一個Window的核心在于調(diào)用WindowManager.addView()并將Window的視圖傳遞進去;接著會為Window創(chuàng)建一個ViewRootImpl,后續(xù)Window視圖的事件都交由ViewRootImpl進行管理;最后與WMS通信完成Window創(chuàng)建。在Activity的Window創(chuàng)建中視圖正是DecorView。而如果我們需要創(chuàng)建一個子窗口,則需要先創(chuàng)建一個View,再調(diào)用WindowManager.addView()即可。但是我們看過源碼后發(fā)現(xiàn),調(diào)用WindowManager的addView()并不會創(chuàng)建一個PhoneWindow類型的對象,那么為什么窗口會創(chuàng)建了?或者說PhoneWindow的職責(zé)究竟是什么?
我理解是表示一個窗口的并不是應(yīng)用內(nèi)的PhoneWindow對象,而是WMS內(nèi)的WindowState對象,前文說到了WindowState有窗口的全部狀態(tài)信息,而且無論是Activity、Dialog的窗口或是我們創(chuàng)建的子窗口,都會調(diào)用WindowManager.addView(),其最終會調(diào)用到WMS.addWindow()創(chuàng)建WindowState對象用于表示窗口。既然Window并不真正表示一個窗口,那么PhoneWindow的作用是什么呢?
- PhoneWindow的一個作用是給view包裹上一層DecorView,而DecorView中的布局結(jié)構(gòu)會根據(jù)Theme決定。
- 我們的Activity和Dialog的布局都比較復(fù)雜,比如都可能有appbar(toolbar/actionbar)等,通過PhoneWindow來封裝下可以更好的解耦代碼。
Dialog和Window一樣都使用了PhoneWindow封裝了DecorView,因此Dialog的樣式也會根據(jù)Theme決定,但是PopupWindow以及Toast同樣是Window,其內(nèi)部并沒有使用PhoneWindow,而是直接通過WindowManager.addWindow()創(chuàng)建的Window,主要原因是PopupWindow和Toast樣式相對較簡單,無需通過PhoneWindow進行一層封裝。
Window的刪除過程
在了解了Window的添加過程后,我們依舊是通過Activity對Window的處理來看下Window的刪除過程。在Activity銷毀時會調(diào)用ActivityThread.handleDestroyActivity(),如下所示:
private void handleDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
ActivityClientRecord r = performDestroyActivity(token, finishing,
configChanges, getNonConfigInstance);
if (r != null) {
cleanUpPendingRemoveWindows(r, finishing);
WindowManager wm = r.activity.getWindowManager();
View v = r.activity.mDecor;
if (v != null) {
...
wm.removeViewImmediate(v);
...
}
...
}
首先獲得了WindowManager以及Activity的DecorView,接著調(diào)用WindowManager的removeViewImmediate()并將DecorView作為參數(shù)傳入,removeViewImmediate()的實現(xiàn)位于WindowManagerImpl中,如下所示:
public void removeView(View view) {
mGlobal.removeView(view, false);
}
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
removeViewImmediate()同樣沒有具體實現(xiàn)操作,也是直接調(diào)用WindowManagerGlobal.removeView(),另外除了removeViewImmediate(),還有前面說到的ViewManager中的removeView(),這兩個接口都是用于刪除Window并都會調(diào)用WindowManagerGlobal.removeView(),但區(qū)別在于前者調(diào)用時immediate為true,這個字段標(biāo)識是否要立即銷毀Window,我們后面會講到。接下來看一下WindowManagerGlobal的removeView(),其內(nèi)部會找到傳入的View在列表中的索引并調(diào)用removeViewLocked(),如下所示:
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
首先會獲取InputMethodManager并調(diào)用windowDismissed()來結(jié)束Window輸入法相關(guān)邏輯,接著會調(diào)用ViewRootImpl.die()并傳入了前文說到的immediate參數(shù),方法如下所示:
boolean die(boolean immediate) {
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
首先若immediate為true且mIsInTraversal為false則會執(zhí)行doDie()并返回,immediate是前文傳遞下來的,若我們執(zhí)行的是WindowManager.removeViewImmediate()則該值為true,mIsInTraversal會在ViewRootImpl執(zhí)行performTraversals()時置為true,執(zhí)行結(jié)束后置為false,因此這里的邏輯就是如果要立即執(zhí)行(immediate為true)且ViewRootImpl不再執(zhí)行performTraversals()時會執(zhí)行doDie(),否則會通過Handler發(fā)送一個MSG_DIE消息,而Handler在處理這個消息時就會執(zhí)行doDie(),因此我們看下doDie()的實現(xiàn),如下所示:
void doDie() {
...
if (mAdded) {
dispatchDetachedFromWindow();
}
...
WindowManagerGlobal.getInstance().doRemoveView(this);
}
上述代碼主要關(guān)注兩個地方,首先是要刪除的Window如果有子View會調(diào)用dispatchDetachedFromWindow()來銷毀View,接著調(diào)用WindowManagerGlobal的doRemoveView(),我們先看下WindowManagerGlobal的doRemoveView(),如下所示:
void doRemoveView(ViewRootImpl root) {
synchronized (mLock) {
final int index = mRoots.indexOf(root);
if (index >= 0) {
mRoots.remove(index);
mParams.remove(index);
final View view = mViews.remove(index);
mDyingViews.remove(view);
}
}
if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
doTrimForeground();
}
}
可以看到該方法主要是從列表中移除了與要刪除的Window對應(yīng)的View、ViewRootImpl和LayoutParams,這幾個列表在前面的添加過程也看到過。接著我們看下dispatchDetachedFromWindow()的實現(xiàn),代碼如下:
void dispatchDetachedFromWindow() {
...
mWindowSession.remove(mWindow);
...
}
dispatchDetachedFromWindow()會進行一些資源的釋放,接著調(diào)用了IWindowSession類型的remove(),IWindowSession在添加過程中已經(jīng)看到過,它用于與WMS通信,會調(diào)用Session.remove(),其內(nèi)部又會調(diào)用WindowManagerService.removeWindow(),該方法會通過Session和IWindow獲取到對應(yīng)的WindowState,并調(diào)用WindowState的removeIfPossible(),最終會調(diào)用到WindowState的removeImmediately(),該方法主要會對Window涉及的一些資源進行回收與清理,到這里Window的刪除過程就完成了。
Window的更新過程
我們要更新Window時,會調(diào)用WindowManager.updateViewLayout(),這個方法的實現(xiàn)在WindowManagerImpl中,代碼如下所示:
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
該方法依舊是調(diào)用WindowManagerGlobal.updateViewLayout()處理,代碼如下所示:
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
...
WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
首先是根據(jù)View找到要更新的Window的索引,再根據(jù)索引更新列表中LayoutParams,接著調(diào)用與Window對應(yīng)的ViewRootImpl的setLayoutParams(),其內(nèi)部會調(diào)用scheduleTraversals(),如下所示:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //1
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
注釋1處的代碼添加了一個mTraversalRunnable,mTraversalRunnable是TraversalRunnable類型,它實現(xiàn)了Runnable接口,run()里的代碼在下一幀渲染時會被執(zhí)行,我們看下mTraversalRunnable的代碼,如下所示:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
因此在下一幀渲染時會執(zhí)行doTraversal(),其內(nèi)部又會調(diào)用performTraversals(),performTraversals()其實我們就很清楚了,它會執(zhí)行View的measure、layout以及draw流程,因此Window里的視圖會執(zhí)行上述的流程,但performTraversals()除了執(zhí)行View的工作流程外,還會調(diào)用relayoutWindow(),代碼如下所示:
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
...
int relayoutResult = mWindowSession.relayout(
mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f),
viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame,
mPendingMergedConfiguration, mSurface);
...
}
這里的mWindowSession已經(jīng)見過很多次了,它是個IWindowSession類型的Binder對象,用于與WMS通信,會調(diào)用到Session.relayout(),其內(nèi)部會調(diào)用WindowManagerService的relayoutWindow(),這樣就完成了Window的更新操作。