前言
經(jīng)過上一篇章的討論曹阔,我們理清楚了ActivityRecord,TaskRecord和窗體容器之間的關系蚁趁。同時達到了應用啟動時干发,啟動的第一個啟動窗口,StartingWindow叙甸。這個時候颖医,我們可以看到一個直指核心的代碼段:
wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
view = win.getDecorView();
...
wm.addView(view, params);
這個方法聯(lián)通了WMS中的addView方法。
上一篇:Android 重學系列 WMS在Activity啟動中的職責(二)
正文
Context 獲取系統(tǒng)服務
在正式聊WMS之前裆蒸,我們先來看看context.getSystemService其核心原理熔萧,才能找到WindowManager的實現(xiàn)類:
文件:/frameworks/base/core/java/android/app/ContextImpl.java
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
文件: /frameworks/base/core/java/android/app/SystemServiceRegistry.java
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
能看到是實際上所有的我們通過Context獲取系統(tǒng)服務,是通過SYSTEM_SERVICE_FETCHERS這個提前存放在HashMap的服務集合中光戈。這個服務是在靜態(tài)代碼域中提前注冊哪痰。
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
能看到此時實際上WindowManager的interface是由WindowManagerImpl實現(xiàn)的。
這里先上一個WindowManager的UML類圖久妆。
我們能夠從這個UML圖能夠看到,其實所有的事情都委托給WindowManagerGlobal工作跷睦。因此我們只需要看WindowManagerGlobal中做了什么筷弦。
因此我們要尋求WindowManager的addView的方法,實際上就是看WindowManagerGlobal的addView方法。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
...
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
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) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
這里能夠看到一個新的addView的時候烂琴,會找到是否有父Window爹殊。沒有則繼續(xù)往后走,判斷新建窗體的type是否是子窗口類型奸绷,是則查找傳進來的Binder對象和存儲在緩存中的Binder對象又沒有對應的Window梗夸。有則作為本次新建窗口的復窗口。
最后能夠看到我們熟悉的類ViewRootImpl号醉。這個類可以說是所有View繪制的根部核心反症,這個類會在后面View繪制流程聊聊。最后會調用ViewRootImpl的setView進一步的溝通系統(tǒng)應用端畔派。
這里涉及到了幾個有趣的宏铅碍,如WindowManager.LayoutParams.FIRST_SUB_WINDOW 。它們象征這當前Window處于什么層級线椰。
Window的層級
Window的層級胞谈,我們大致可以分為3大類:System Window(系統(tǒng)窗口),Application Window(應用窗口)憨愉,Sub Window(子窗口)
Application Window(應用窗口)
Application值得注意的有這么幾個宏:
type | 描述 |
---|---|
FIRST_APPLICATION_WINDOW = 1 | 應用程序窗口初始值 |
TYPE_BASE_APPLICATION = 1 | 應用窗口類型初始值烦绳,其他窗口以此為基準 |
TYPE_APPLICATION = 2 | 普通應用程序窗口類型 |
TYPE_APPLICATION_STARTING = 3 | 應用程序的啟動窗口類型,不是應用進程支配配紫,當?shù)谝粋€應用進程誕生了啟動窗口就會銷毀 |
TYPE_DRAWN_APPLICATION = 4 | 應用顯示前WindowManager會等待這種窗口類型繪制完畢爵嗅,一般在多用戶使用 |
LAST_APPLICATION_WINDOW = 99 | 應用窗口類型最大值 |
因此此時我們能夠清楚,應用窗口的范圍在1~99之間笨蚁。
Sub Window(子窗口)
type | 描述 |
---|---|
FIRST_SUB_WINDOW = 1000 | 子窗口初始值 |
TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW | 應用的panel窗口睹晒,在父窗口上顯示 |
TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1 | 多媒體內容子窗口,在父窗口之下 |
TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2 | 也是一種panel子窗口括细,位于所有TYPE_APPLICATION_PANEL之上 |
TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3 | dialog彈窗 |
TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4 | 多媒體內容窗口的覆蓋層 |
TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5 | 位于子panel之上窗口 |
LAST_SUB_WINDOW = 1999 | 子窗口類型最大值 |
能夠看到子窗口的范圍從1000~1999
System Window(系統(tǒng)窗口)
type | 描述 |
---|---|
FIRST_SYSTEM_WINDOW = 2000 | 系統(tǒng)窗口初始值 |
TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW | 系統(tǒng)狀態(tài)欄 |
TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1 | 搜索條窗口 |
TYPE_PHONE = FIRST_SYSTEM_WINDOW+2 | 通話窗口 |
TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3 | alert窗口伪很,電量不足時警告 |
TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4 | 屏保窗口 |
TYPE_TOAST = FIRST_SYSTEM_WINDOW+5 | Toast提示窗口 |
TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6 | 系統(tǒng)覆蓋層窗口,這個層不會響應點擊事件 |
TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7 | 電話優(yōu)先層奋单,在屏保狀態(tài)下顯示通話 |
TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8 | 系統(tǒng)層級的dialog锉试,比如RecentAppDialog |
TYPE_KEYGUARD_DIALOG= FIRST_SYSTEM_WINDOW+9 | 屏保時候對話框(如qq屏保時候的聊天框) |
TYPE_SYSTEM_ERROR= FIRST_SYSTEM_WINDOW+10 | 系統(tǒng)錯誤窗口 |
TYPE_INPUT_METHOD= FIRST_SYSTEM_WINDOW+11 | 輸入法窗口 |
TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12 | 輸入法窗口上的對話框 |
TYPE_WALLPAPER= FIRST_SYSTEM_WINDOW+13 | 壁紙窗口 |
TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14 | 滑動狀態(tài)欄窗口 |
LAST_SYSTEM_WINDOW = 2999 | 系統(tǒng)窗口最大值 |
常見的系統(tǒng)級別窗口主要是這幾個。能夠注意到系統(tǒng)窗口層級是從2000~2999览濒。
這些層級有什么用的呆盖?這些層級會作為參考,將會插入到顯示棧的位置贷笛,層級值越高应又,越靠近用戶。這個邏輯之后會聊到乏苦。
ViewRootImpl setView
ViewRootImpl里面包含了許多事情株扛,主要是包含了我們熟悉的View的繪制流程尤筐,以及添加Window實例的流程。
本文是關于WMS洞就,因此我們只需要看下面這個核心函數(shù)
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mAttachInfo.mDisplayState = mDisplay.getState();
//注冊屏幕變換監(jiān)聽
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
//點擊事件分發(fā)
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
attrs = mWindowAttributes;
setTag();
...
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
setAccessibilityFocus(null, null);
....
...
mSoftInputMode = attrs.softInputMode;
mWindowAttributesChanged = true;
mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
mAttachInfo.mRootView = view;
mAttachInfo.mScalingRequired = mTranslator != null;
mAttachInfo.mApplicationScale =
mTranslator == null ? 1.0f : mTranslator.applicationScale;
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
}
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
...
}
}
這個方法有兩個核心requestLayout以及addToDisplay盆繁。
- 1.requestLayout實際上就是指View的繪制流程,并且最終會把像素數(shù)據(jù)發(fā)送到Surface底層旬蟋。
- 2.mWindowSession.addToDisplay 添加Window實例到WMS中油昂。
本文主要討論WMS,requestLayout的方法暫時不談倾贰。
WindowManager的Session設計思想
先來看看Session類:
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient
得知此時Session實現(xiàn)了一個IWindowSession的Binder對象冕碟。并且實現(xiàn)了Binder的死亡監(jiān)聽。
那么這個Session是從哪里來的呢躁染?實際上是通過WMS通過跨進程通信把數(shù)據(jù)這個Binder對象傳遞過來的:
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
IInputContext inputContext) {
if (client == null) throw new IllegalArgumentException("null client");
if (inputContext == null) throw new IllegalArgumentException("null inputContext");
Session session = new Session(this, callback, client, inputContext);
return session;
}
通著這種方式鸣哀,就能把一個Session帶上WMS相關的環(huán)境送給客戶端操作。這種方式和什么很相似吞彤,實際上和servicemanager查詢服務Binder的思路幾乎一模一樣我衬。
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
}
很有趣的是,我們能夠看到饰恕,按照道理我們需要添加窗體實例到WMS中挠羔。從邏輯上來講,我們只需要做一次跨進程通信即可埋嵌。但是為什么需要一個Session作為中轉站呢破加?
能夠看到實際上Session(會話)做的事情不僅僅只有溝通WMS這么簡單。實際上它還同時處理了窗口上的拖拽雹嗦,輸入法等邏輯范舀,更加重要的是Session面對著系統(tǒng)多個服務,但是通過這個封裝了罪,應用程序只需要面對這個Sesion接口锭环,真的是名副其實的"會話"。
這種設計想什么泊藕?實際上就是我們常說的門面設計模式辅辩。
IWindow對象
注意,這里面除了IWindowSession之外,當我們調用addWindow添加Window到WMS中的時候娃圆,其實還存在一個IWindow接口.這個IWindow是指PhoneWindow嗎玫锋?
很遺憾。并不是讼呢。PhoneWindow基礎的接口只有Window接口撩鹿。它并不是一個IBinder對象。我們轉過頭看看ViewRootImpl.
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
mWidth = -1;
mHeight = -1;
mDirty = new Rect();
mTempRect = new Rect();
mVisRect = new Rect();
mWinFrame = new Rect();
mWindow = new W(this);
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mAdded = false;
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
...
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
if (!sCompatibilityDone) {
sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
sCompatibilityDone = true;
}
loadSystemProperties();
}
能看到此時吝岭,實際上在ViewRootImpl的構造函數(shù)會對應當前生成一個W的內部類三痰。這個內部類:
static class W extends IWindow.Stub
這個內部類實際上就是一個Binder類吧寺,里面回調了很多方法來操作當前的ViewRootImpl窜管。換句話說散劫,就是把當前的ViewRootImpl的代理W交給WMS去管理。
那么我們可以總結幕帆,IWindow是WMS用來間接操作ViewRootImpl中的View获搏,IWindowSession是App用來間接操作WMS。
WMS.addWindow
WMS的addWindow很長失乾,因此我這邊拆開成3部分聊
添加窗體的準備步驟
public int addWindow(Session session, IWindow client, int seq,
LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
int[] appOp = new int[1];
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
boolean reportNewConfig = false;
WindowState parentWindow = null;
long origId;
final int callingUid = Binder.getCallingUid();
final int type = attrs.type;
synchronized(mWindowMap) {
if (!mDisplayReady) {
throw new IllegalStateException("Display has not been initialialized");
}
final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
if (displayContent == null) {
...
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
if (!displayContent.hasAccess(session.mUid)
&& !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
...
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
if (mWindowMap.containsKey(client.asBinder())) {
...
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
//如果是子窗口常熙,則通過Binder找父窗口
parentWindow = windowForClientLocked(null, attrs.token, false);
if (parentWindow == null) {
...
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
&& parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
...
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
}
if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
...
return WindowManagerGlobal.ADD_PERMISSION_DENIED;
}
AppWindowToken atoken = null;
final boolean hasParent = parentWindow != null;
//從DisplayContent找到對應的WIndowToken
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
final int rootType = hasParent ? parentWindow.mAttrs.type : type;
....
}
我們拋開大部分的校驗邏輯。實際上可以把這個過程總結為以下幾點:
- 1.判斷又沒有相關的權限
- 2.嘗試著獲取當前displayId對應的DisplayContent碱茁,沒有則創(chuàng)建裸卫。其邏輯實際上和我上一篇說的創(chuàng)建DisplayContent一摸一樣
- 3.通過mWindowMap,判斷當前IWindow是否被添加過纽竣,是的話說明已經(jīng)存在這個Window墓贿,不需要繼續(xù)添加
- 4.如果當前窗口類型是子窗口,則會通過WindowToken.attrs參數(shù)中的token去查找當前窗口的父窗口是什么蜓氨。
- 5.如果有父窗口聋袋,則從DisplayContent中以父窗口的IWindow獲取父窗口WindowToken的對象,否則嘗試的獲取當前窗口對應的WindowToken對象穴吹。
我們稍微探索一下其中的幾個核心:
通過windowForClientLocked查找父窗口的WindowState
final WindowState windowForClientLocked(Session session, IBinder client, boolean throwOnError) {
WindowState win = mWindowMap.get(client);
if (localLOGV) Slog.v(TAG_WM, "Looking up client " + client + ": " + win);
if (win == null) {
if (throwOnError) {
throw new IllegalArgumentException(
"Requested window " + client + " does not exist");
}
Slog.w(TAG_WM, "Failed looking up window callers=" + Debug.getCallers(3));
return null;
}
if (session != null && win.mSession != session) {
if (throwOnError) {
throw new IllegalArgumentException("Requested window " + client + " is in session "
+ win.mSession + ", not " + session);
}
Slog.w(TAG_WM, "Failed looking up window callers=" + Debug.getCallers(3));
return null;
}
return win;
}
實際上可以看到這里面是從mWindowMap通過IWindow獲取WindowState對象幽勒。還記得我上篇說過很重要的數(shù)據(jù)結構嗎?mWindowMap實際上是保存著WMS中IWindow對應WindowState對象港令。IWindow本質上是WMS控制ViewRootImpl的Binder接口啥容。因此我們可以把WindowState看成應用進程的對應的對象也未嘗不可。
獲取對應的WindowToken
AppWindowToken atoken = null;
final boolean hasParent = parentWindow != null;
//從DisplayContent找到對應的WIndowToken
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
從這里面我們能夠看到WindowToken顷霹,是通過DisplayContent獲取到的咪惠。
WindowToken getWindowToken(IBinder binder) {
return mTokenMap.get(binder);
}
這樣就能看到我前兩篇提到過的很重要的數(shù)據(jù)結構:mTokenMap以及mWindowMap。這兩者要稍微區(qū)分一下:
mWindowMap是以IWindow為key泼返,WindowState為value硝逢。
mTokenMap是以WindowState的IBinder(一般為IApplicationToken)為key,WindowToken為value
還記得mTokenMap在Activity的啟動流程中做的事情嗎绅喉?在創(chuàng)建AppWIndowContainer的時候渠鸽,會同時創(chuàng)建AppWindowToken,AppWIndowToken的構造會把當前的IBinder作為key柴罐,AppWindowToken作為value添加到mTokenMap中徽缚。
也就是說,如果系統(tǒng)想要通過應用進程給的IWindow找到真正位于WMS中Window的句柄革屠,必須通過這兩層變換才能真正找到凿试。
拆分情況獲取對應的WindowToken和AppWindowToken
這個時候就分為兩種情況排宰,一種是存在WindowToken,一種是不存在WindowToken那婉。
boolean addToastWindowRequiresToken = false;
if (token == null) {
//校驗窗口參數(shù)是否合法
...
final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
final boolean isRoundedCornerOverlay =
(attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
token = new WindowToken(this, binder, type, false, displayContent,
session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
} else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
atoken = token.asAppWindowToken();
if (atoken == null) {
return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
}
...
} else if (atoken.removed) {
...
} else if (type == TYPE_APPLICATION_STARTING && atoken.startingWindow != null) {
...
}
} else if (rootType == TYPE_INPUT_METHOD) {
...
} else if (rootType == TYPE_VOICE_INTERACTION) {
...
} else if (rootType == TYPE_WALLPAPER) {
...
} else if (rootType == TYPE_DREAM) {
...
} else if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
...
} else if (type == TYPE_TOAST) {
....
} else if (type == TYPE_QS_DIALOG) {
...
} else if (token.asAppWindowToken() != null) {
attrs.token = null;
token = new WindowToken(this, client.asBinder(), type, false, displayContent,
session.mCanAddInternalSystemWindow);
}
當我們通過mTokenMap獲取WindowToken的時候板甘,大致分為四種情況。WindowToken會嘗試的獲取父窗口對應的Token详炬,找不到則使用WindowManager.LayoutParams中的WindowToken盐类。一般來說我們找到的都有父親的WindowToken。
- 1.無關應用的找不到WindowToken
- 2.有關應用找不到WindowToken呛谜。
- 3.無關應用找到WindowToken
- 4.有關應用找到WindowToken
前兩種情況解析
實際上前兩種情況在跳,一旦發(fā)現(xiàn)找不到WindowToken,如果當前的窗口和應用相關的隐岛,就一定爆錯誤猫妙。如Toast,輸入法聚凹,應用窗口等等割坠。
因此在Android 8.0開始,當我們想要顯示Toast的時候元践,加入傳入的Context是Application而不是Activity韭脊,此時一旦發(fā)現(xiàn)mTokenMap中找不到IApplicationToken對應的WindowToken就爆出了錯誤。正確的做法應該是需要獲取Activity當前的Context单旁。
在上面的情況應用啟動窗口沪羔,此時并沒有啟動Activity。因此不可能會被校驗攔下象浑,因此并沒有異常拋出蔫饰。就會自己創(chuàng)建一個WindowToken。
后兩種的解析
當找到WindowToken愉豺,一般是指Activity啟動之后篓吁,在AppWindowToken初始化后,自動加入了mTokenMap中蚪拦。此時的情況稍微復雜了點杖剪。
當是子窗口的時候,則會判斷當前的WindowToken是不是AppWindowToken驰贷。不是盛嘿,或者被移除等異常情況則報錯。
如果是壁紙括袒,輸入法次兆,系統(tǒng)彈窗,toast等窗口模式锹锰,子窗口和父窗口的模式必須一致芥炭。
當此時的AppWindowToken不為空的時候漓库,說明在New的時候已經(jīng)生成,且沒有移除园蝠,將會生成一個新的WindowToken渺蒿。
為什么要生成一個新的windowToken?可以翻閱之前我寫的文章,只要每一次調用一次構造函數(shù)將會把當前的WindowToken添加到mTokenMap中砰琢,實際上也是擔心蘸嘶,對應的AppWindowToken出現(xiàn)的重新綁定的問題良瞧。
添加WindowState實例到數(shù)據(jù)結構
但是別忘了陪汽,我們這個時候還需要把相關的數(shù)據(jù)結構存儲到全局。
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
if (win.mDeathRecipient == null) {
...
return WindowManagerGlobal.ADD_APP_EXITING;
}
if (win.getDisplayContent() == null) {
...
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
final boolean hasStatusBarServicePermission =
mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
== PackageManager.PERMISSION_GRANTED;
mPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission);
win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
res = mPolicy.prepareAddWindowLw(win, attrs);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
// From now on, no exceptions or errors allowed!
res = WindowManagerGlobal.ADD_OKAY;
if (mCurrentFocus == null) {
mWinAddedSinceNullFocus.add(win);
}
if (excludeWindowTypeFromTapOutTask(type)) {
displayContent.mTapExcludedWindows.add(win);
}
origId = Binder.clearCallingIdentity();
win.attach();
//以IWindow為key褥蚯,WindowState為value存放到WindowMap中
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();
....
win.mToken.addWindow(win);
因為完全可能出現(xiàn)新的WindowToken挚冤,因此干脆會創(chuàng)建一個新的WindowState。此時會對調用WindowState.attach方法
void attach() {
mSession.windowAddedLocked(mAttrs.packageName);
}
這方法挺重要的赞庶,Session做了一次添加鎖定训挡。
文件:/frameworks/base/services/core/java/com/android/server/wm/Session.java
void windowAddedLocked(String packageName) {
mPackageName = packageName;
mRelayoutTag = "relayoutWindow: " + mPackageName;
if (mSurfaceSession == null) {
if (WindowManagerService.localLOGV) Slog.v(
TAG_WM, "First window added to " + this + ", creating SurfaceSession");
mSurfaceSession = new SurfaceSession();
if (SHOW_TRANSACTIONS) Slog.i(
TAG_WM, " NEW SURFACE SESSION " + mSurfaceSession);
mService.mSessions.add(this);
if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
mService.dispatchNewAnimatorScaleLocked(this);
}
}
mNumWindow++;
}
此時的工作是什么?聯(lián)系上下文歧强,當我們新增了PhoneWindow澜薄,就會一個ViewRootImpl,也因此新增了Session摊册。此時說明誕生一個新界面肤京,此時已經(jīng)誕生了相關的容器對象,但是相關的繪制到底層對象還沒有創(chuàng)建出來茅特。
命名邏輯和Session很相似忘分。Session是WMS給應用App的會話對象,SurfaceSession是SurfaceFlinger面向上層每一個WIndow需要繪制內容對象白修。
這個SurfaceSession和SurfaceControl都是重點,聯(lián)通到SurfaceFlinger很重要的對象肯骇。
最后再添加到mWindowMap中祖很。并且把WindowState添加到WindowToken中,讓每一個WindowToken賦予狀態(tài)的信息若债。我們稍微探索一下addWindow的方法。
WindowState 添加Window的策略
有沒有考慮過WindowManager.LayoutParams是從哪里來的token拆融?
當我們沒有指定當前窗口的type,則會自動設置為TYPE_APPLICATION = 2蓝牲,同時token將會是原來的appwindowtoken.當我們在addView傳入了父親窗口時候例衍,則會通過adjustLayoutParamsForSubWindow先不設定application的值,而是先拿到父親窗口的token:
文件:/frameworks/base/core/java/android/view/Window.java
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
CharSequence curTitle = wp.getTitle();
if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
if (wp.token == null) {
View decor = peekDecorView();
if (decor != null) {
wp.token = decor.getWindowToken();
}
}
...
} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
//設置title
} else {
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}
//設置title
}
if (wp.packageName == null) {
wp.packageName = mContext.getPackageName();
}
if (mHardwareAccelerated ||
(mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
wp.flags |= FLAG_HARDWARE_ACCELERATED;
}
}
能夠看到此時將會初始化WindowManager.LayoutParams的Token。此時Token在Activity啟動流程中已經(jīng)先一步初始化AppWindowToken奥吩。
在聊WindowState的添加窗口的策略之前霞赫,我們先來看看WindowState的構造函數(shù)端衰。
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow,
PowerManagerWrapper powerManagerWrapper) {
super(service);
....
try {
c.asBinder().linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
...
return;
}
mDeathRecipient = deathRecipient;
if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
// The multiplier here is to reserve space for multiple
// windows in the same type layer.
mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
mIsChildWindow = true;
parentWindow.addChild(this, sWindowSubLayerComparator);
mLayoutAttached = mAttrs.type !=
WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
mIsImWindow = parentWindow.mAttrs.type == TYPE_INPUT_METHOD
|| parentWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
mIsWallpaper = parentWindow.mAttrs.type == TYPE_WALLPAPER;
} else {
// The multiplier here is to reserve space for multiple
// windows in the same type layer.
mBaseLayer = mPolicy.getWindowLayerLw(this)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
mSubLayer = 0;
mIsChildWindow = false;
mLayoutAttached = false;
mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD
|| mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
mIsWallpaper = mAttrs.type == TYPE_WALLPAPER;
}
mIsFloatingLayer = mIsImWindow || mIsWallpaper;
if (mAppToken != null && mAppToken.mShowForAllUsers) {
// Windows for apps that can show for all users should also show when the device is
// locked.
mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED;
}
...
}
我們把目光幾種在mBaseLayer和mSubLayer的初始化上。我們能夠看到在初始化WindowState的時候央渣,會獲取WindowState的type是子窗口還是不是子窗口。
此時我們把這個問題分為兩種情況:
1.是子窗口
當我們發(fā)現(xiàn)當前窗口子窗口参淫,會分為如下2個層級作為基準值涎才。獲取當前傳進進來的層級type:
符合如下公式:
mBaselayer = 父窗口層級type(見上文Window層級的表格) * 10000 + 1000邑闺;
mSubLayer = 子窗口本身的層級type(見上文Window層級的表格)
private static final Comparator<WindowState> sWindowSubLayerComparator =
new Comparator<WindowState>() {
@Override
public int compare(WindowState w1, WindowState w2) {
final int layer1 = w1.mSubLayer;
final int layer2 = w2.mSubLayer;
if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
return -1;
}
return 1;
};
};
此時就會直接添加到parentWindow當中。會不斷的比對比當前mSubLayer大的值靶衍,直到找到第一個插入颅眶。
2.不是子窗口
此時也會根據(jù)當前傳進來的層級去計算當前window應該插入的地方帚呼。
符合如下公式:
mBaselayer = 當前的窗口層級type(見上文Window層級的表格) * 10000 + 1000眷蜈;
mSubLayer = 0;
將會在接下來通過WindowState的addWindow做進一步調整酌儒。
文件:/frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
private final Comparator<WindowState> mWindowComparator =
(WindowState newWindow, WindowState existingWindow) -> {
final WindowToken token = WindowToken.this;
if (newWindow.mToken != token) {
throw new IllegalArgumentException("newWindow=" + newWindow
+ " is not a child of token=" + token);
}
if (existingWindow.mToken != token) {
throw new IllegalArgumentException("existingWindow=" + existingWindow
+ " is not a child of token=" + token);
}
return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
};
protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
WindowState existingWindow) {
// New window is considered greater if it has a higher or equal base layer.
return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
}
void addWindow(final WindowState win) {
if (DEBUG_FOCUS) Slog.d(TAG_WM,
"addWindow: win=" + win + " Callers=" + Debug.getCallers(5));
if (win.isChildWindow()) {
// Child windows are added to their parent windows.
return;
}
if (!mChildren.contains(win)) {
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
addChild(win, mWindowComparator);
mService.mWindowsChanged = true;
// TODO: Should we also be setting layout needed here and other places?
}
}
這一段都是繼上面調整非子窗口邏輯,能夠很輕松的看出來孽惰,實際上此時會去不斷的比對mBaseLayer直到找到一個大于等于的層級添加到上面勋功。
層級初步計算總結
還記得此時在DisplayContent中,把整個WindowContainer的集合拆分成幾個層次嗎骚揍?棧區(qū)域纤掸,statusbar區(qū)域借跪,壁紙區(qū)域,輸入法區(qū)域果港。
每當我們new了一個WindowToken,將會自動的根據(jù)此時窗口類型綁定到對應的區(qū)域的末尾萝衩。這個時候,當我們addWindow要添加WindowState的時候牌捷,將會根據(jù)這個句柄去查找WindowToken中的層級暗甥,插入到對應的層級中。
用一幅圖總結如下:
層級第二次計算
經(jīng)過上面的區(qū)域劃分,把窗體大致上區(qū)分到了幾個區(qū)域當中剥懒,并且有了大致的順序,但是實際上保檐,我們只是粗略的處理了Window夜只。實際上在App應用中不是簡單的擺好,我們在平時使用的時候并非如此场躯。
還有一種情況需要特殊處理,當我們嘗試著執(zhí)行窗口動畫的時候签舞,一般很少遇到有什么東西把Activity的Window動畫給遮擋住。實際上也是得益于第二次的調整师妙。
if (type == TYPE_INPUT_METHOD) {
win.mGivenInsetsPending = true;
setInputMethodWindowLocked(win);
imMayMove = false;
} else if (type == TYPE_INPUT_METHOD_DIALOG) {
displayContent.computeImeTarget(true /* updateImeTarget */);
imMayMove = false;
} else {
if (type == TYPE_WALLPAPER) {
displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
} else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
} else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
}
...
if (imMayMove) {
displayContent.computeImeTarget(true /* updateImeTarget */);
}
// Don't do layout here, the window must call
// relayout to be displayed, so we'll do it there.
win.getParent().assignChildLayers();
....
此時褪秀,會從DisplayContent頂部向下重新對層級進行排序媒吗。能看到核心方法是就是computeImeTarget以及assignChildLayers。
computeImeTarget
WindowState computeImeTarget(boolean updateImeTarget) {
if (mService.mInputMethodWindow == null) {
if (updateImeTarget) {
setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
}
return null;
}
final WindowState curTarget = mService.mInputMethodTarget;
if (!canUpdateImeTarget()) {
return curTarget;
}
mUpdateImeTarget = updateImeTarget;
WindowState target = getWindow(mComputeImeTargetPredicate);
if (target != null && target.mAttrs.type == TYPE_APPLICATION_STARTING) {
final AppWindowToken token = target.mAppToken;
if (token != null) {
final WindowState betterTarget = token.getImeTargetBelowWindow(target);
if (betterTarget != null) {
target = betterTarget;
}
}
}
if (curTarget != null && curTarget.isDisplayedLw() && curTarget.isClosing()
&& (target == null || target.isActivityTypeHome())) {
return curTarget;
}
if (target == null) {
if (updateImeTarget) {
setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
}
return null;
}
if (updateImeTarget) {
AppWindowToken token = curTarget == null ? null : curTarget.mAppToken;
if (token != null) {
WindowState highestTarget = null;
if (token.isSelfAnimating()) {
highestTarget = token.getHighestAnimLayerWindow(curTarget);
}
if (highestTarget != null) {
final AppTransition appTransition = mService.mAppTransition;
if (appTransition.isTransitionSet()) {
setInputMethodTarget(highestTarget, true);
return highestTarget;
} else if (highestTarget.mWinAnimator.isAnimationSet() &&
highestTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer) {
setInputMethodTarget(highestTarget, true);
return highestTarget;
}
}
}
setInputMethodTarget(target, false);
}
return target;
}
這里面有兩個對象需要區(qū)分以下遇伞,一個是curTarget巍耗,一個是Target炬太。
- curTarget是來自WMS的mInputMethodTarget炒考。也就意味著此時是WMS預定的輸入法窗口層級票腰。也就代表當前的輸入法窗口。
target 是來自DisplayContent對自己的孩子進行搜索到最頂部能夠稱為輸入法窗口的WIndow缘滥。也就代表著下一個層級最高(可見的)輸入法窗口。根據(jù)之前的文章,我們可以推斷出來此時就是找添加到DisplayContent層級最高的NonMagnifiableWindowContainers的彈窗搂捧。也就是下一個要彈出的窗口
1.如果此時WMS中沒有IME(輸入法)的Window,此時就沒有生成頂部的Window聋丝,就直接獲取mInputMethodTargetWaitingAnim (因為此時需要做輸入法窗口動畫)作為新的輸入法彈窗,并不需要調整每篷。
2.通過getWindow找到最頂層能夠成為輸入法彈窗層級的DisplayContent的子窗口子库。也就是NonMagnifiableWindowContainers。
3.如果當前可以作為輸入法彈窗是啟動窗口類型仓技,因為啟動窗口本身很特殊,類似中轉站的角色地沮。則會自動找到下面那一層的窗口,判斷是否能夠作為彈窗雷袋。
4.如果當前的輸入法彈窗不為空,同時當前的進程還存在,并且下一個要啟動的窗口是Home。則直接返回當前進程的輸入法彈窗每界。避免屏幕閃動眨层。這里也就解釋為什么馒闷,我們在自己應用啟動了輸入法彈窗,點擊回退鍵盤回退Home之后疏虫,有些時候,輸入法還留在Home上。
5.下一個目標輸入法彈窗為空蚯涮,則獲取上一個。
6.輸入法動畫播放,會根據(jù)方法參數(shù)updateImeTarget這個標志位是否打開嗦哆,來判斷是否處理彈窗動畫。因為是做需要做動畫,所以需要找到當前輸入法彈窗下旁舰,最高層級(可見)的窗口。
這個方法到處都調用了另一個比較核心的方法setInputMethodTarget纳猫,去設定當前的輸入法彈窗目標。
setInputMethodTarget
private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
if (target == mService.mInputMethodTarget
&& mService.mInputMethodTargetWaitingAnim == targetWaitingAnim) {
return;
}
mService.mInputMethodTarget = target;
mService.mInputMethodTargetWaitingAnim = targetWaitingAnim;
assignWindowLayers(false /* setLayoutNeeded */);
}
能看到除了賦值之外物遇,還做一個和我上面提過十分相似的方法assignWindowLayers。
assignWindowLayers
/** Updates the layer assignment of windows on this display. */
void assignWindowLayers(boolean setLayoutNeeded) {
assignChildLayers(getPendingTransaction());
if (setLayoutNeeded) {
setLayoutNeeded();
}
scheduleAnimation();
}
能看到此時會調用assignChildLayers這個方法,并且執(zhí)行窗口動畫。
assignChildLayers
void assignChildLayers(Transaction t) {
int layer = 0;
// We use two passes as a way to promote children which
// need Z-boosting to the end of the list.
for (int j = 0; j < mChildren.size(); ++j) {
final WindowContainer wc = mChildren.get(j);
wc.assignChildLayers(t);
if (!wc.needsZBoost()) {
wc.assignLayer(t, layer++);
}
}
for (int j = 0; j < mChildren.size(); ++j) {
final WindowContainer wc = mChildren.get(j);
if (wc.needsZBoost()) {
wc.assignLayer(t, layer++);
}
}
}
void assignChildLayers() {
assignChildLayers(getPendingTransaction());
scheduleAnimation();
}
這里做了什么事情呢?實際上很巧妙族扰,首先對整個WindowContainer的子窗體做一次調整,接著打開了needsZBoost標志位的窗口再添加到上面。
這樣就分離了需要做動畫的層級,以及普通層級巩检。保證了做動畫的窗口一定再普通窗口之上领舰。
添加窗口層級調整總結
從這里我們看到舍咖,Android 9.0對窗口層級的管理窍株,比起過去的Android4.4的窗口層級調整有了十足的進步。
Android 4.4的窗口管理通過復雜的循環(huán)對窗口進行管理,這里就不分析了开睡。到了Android 9.0 先把窗口層級大致劃分出幾個區(qū)域之后凶杖,再對每個區(qū)域進行循環(huán)管理蝗茁,最后再調整動畫的窗口。這么做的有點很顯然易見饭寺,那就是抽象出了WIndowContainer提高了擴展性,并且減少了循環(huán)次數(shù)叫挟。
到這里窗口添加的大致上的邏輯艰匙,大體上已經(jīng)弄透徹了,但是還有其他內容抹恳。
接下來讓我們繼續(xù)聊聊WMS面向應用暴露的三個接口,剩下的兩個接口奋献,updateViewLayout,removeView.以及Window如何計算Window的邊緣健霹。
updateViewLayout
從上一篇文章看到ViewManager還有另外一個很重要的方法updateViewLayout旺上。我們直奔WindowManagerGlobal看看真正的實現(xiàn)類做了什么:
文件:/frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final 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);
}
}
能看到其核心十分簡單,就是獲取Windowmanager.LayoutParams需要更新的ViewRootImpl糖埋,最后調用setLayoutParams把新的LayoutParams設置到ViewRootImpl中宣吱,最后通過requestLayout做一次更新。
由于這是基于ViewRootImpl做一次一個邏輯屏幕上所有View的更新瞳别,因此使用的地方并不多征候。
removeView
removeView是最后一個ViewManager的接口。這個接口使用的次數(shù)很多祟敛。我們接著啟動窗口繼續(xù)聊聊疤坝,既然我們在啟動我們自己的真正的窗口之前會現(xiàn)有一個啟動窗口顯示,那么當Activity準備好下一步創(chuàng)建的時候垒棋,就必定會移除這個啟動窗口卒煞,讓我們找找看,是哪里移除的叼架。
文件:/frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
讓我們把焦點放在:resumeTopActivityInnerLocked方法上.只有這個方法畔裕,才是真正開始跨進程通信,準備啟動應用的Activity乖订。
private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
if (!mService.mBooting && !mService.mBooted) {
// Not ready yet!
return false;
}
// Find the next top-most activity to resume in this stack that is not finishing and is
// focusable. If it is not focusable, we will fall into the case below to resume the
// top activity in the next focusable task.
final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
final boolean hasRunningActivity = next != null;
// TODO: Maybe this entire condition can get removed?
if (hasRunningActivity && !isAttached()) {
return false;
}
mStackSupervisor.cancelInitializingActivities();
....
return true;
}
銷毀啟動窗口就是通過ActivityStackSupervisor.cancelInitializingActivities扮饶。
void cancelInitializingActivities() {
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
final ActivityStack stack = display.getChildAt(stackNdx);
stack.cancelInitializingActivities();
}
}
}
能看到會獲取ActivityDisplay中ActivityStack中所有的啟動窗口,進行銷毀乍构。
void cancelInitializingActivities() {
final ActivityRecord topActivity = topRunningActivityLocked();
boolean aboveTop = true;
// We don't want to clear starting window for activities that aren't behind fullscreen
// activities as we need to display their starting window until they are done initializing.
boolean behindFullscreenActivity = false;
if (!shouldBeVisible(null)) {
// The stack is not visible, so no activity in it should be displaying a starting
// window. Mark all activities below top and behind fullscreen.
aboveTop = false;
behindFullscreenActivity = true;
}
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
final ActivityRecord r = activities.get(activityNdx);
if (aboveTop) {
if (r == topActivity) {
aboveTop = false;
}
behindFullscreenActivity |= r.fullscreen;
continue;
}
r.removeOrphanedStartingWindow(behindFullscreenActivity);
behindFullscreenActivity |= r.fullscreen;
}
}
}
aboveTop默認是true甜无。換句話說,從Task歷史棧中獲取所有Activity的啟動窗口親切銷毀哥遮,如果當前的要銷毀啟動窗口的Activity和本次要啟動的Activity是同一個對象岂丘,說明沒有必要再去銷毀。
文件:/frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java
void removeOrphanedStartingWindow(boolean behindFullscreenActivity) {
if (mStartingWindowState == STARTING_WINDOW_SHOWN && behindFullscreenActivity) {
if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY, "Found orphaned starting window " + this);
mStartingWindowState = STARTING_WINDOW_REMOVED;
mWindowContainerController.removeStartingWindow();
}
}
behindFullscreenActivity 這個標志位代表著是否真的執(zhí)行銷毀啟動窗體眠饮,只要有一個Activity是全屏模式奥帘,就一定會去銷毀。
AppWindowContainerController.removeStartingWindow
public void removeStartingWindow() {
synchronized (mWindowMap) {
final StartingSurface surface;
....
// Use the same thread to remove the window as we used to add it, as otherwise we end up
// with things in the view hierarchy being called from different threads.
mService.mAnimationHandler.post(() -> {
if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Removing startingView=" + surface);
try {
surface.remove();
} catch (Exception e) {
Slog.w(TAG_WM, "Exception when removing starting window", e);
}
});
}
}
能看到此時的操作和addStartingWindow相似仪召,也是把操作丟給WMS的動畫處理Handler mAnimationHandler完成寨蹋,對這個StartingSurface 進行移除。
而這個StartingSurface 在上一篇文章我們就能看到扔茅,實際上是一個SplashScreenSurface對象已旧。
文件:/frameworks/base/services/core/java/com/android/server/policy/SplashScreenSurface.java
public void remove() {
final WindowManager wm = mView.getContext().getSystemService(WindowManager.class);
wm.removeView(mView);
}
能看到此時就是通過WindowManagerService對啟動窗體進行銷毀。
WindowManagerGlobal removeView
文件:/frameworks/base/core/java/android/view/WindowManagerGlobal.java
private int findViewLocked(View view, boolean required) {
final int index = mViews.indexOf(view);
if (required && index < 0) {
throw new IllegalArgumentException("View=" + view + " not attached to window manager");
}
return index;
}
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
在removeView的時候召娜,我們需要注意view為空有異常运褪,當我們要銷毀的view,從mViews中找到和mRoot中找到的不一致,則會報錯吐句。這兩個對象都是在addView胁后,同步添加到mRoots和mView中。
因為此時是WindowManager的銷毀嗦枢,那么必定會去銷毀當前對應的ViewRootImpl,換句話屯断,我們要銷毀ViewGroup中的View的時候當然不會使用這個方法銷毀文虏,這個方法銷毀的是整個窗體對應的根部View。
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);
}
}
}
能看到在清除的行為中殖演,首先獲取對應的ViewRootImpl氧秘,先通過windowDismissed銷毀輸入法。通過ViewRootImpl做一次相關的銷毀行為趴久,再通過assignParent清除其View指定的父View丸相,如果不是立即銷毀則把view對象添加到正在死亡的View集合中,等到做完所有的清除操作后彼棍,再清除這個集合中的view灭忠。
ViewRootImpl die
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
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;
}
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
dispatchDetachedFromWindow();
}
if (mAdded && !mFirst) {
destroyHardwareRenderer();
if (mView != null) {
int viewVisibility = mView.getVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
if (mWindowAttributesChanged || viewVisibilityChanged) {
// If layout params have been changed, first give them
// to the window manager to make sure it has the correct
// animation info.
try {
if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
}
}
mSurface.release();
}
}
mAdded = false;
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
能看到,如果需要理解銷毀則會直接執(zhí)行doDie的方法座硕,否則會把doDie委托到handler中完成弛作。
doDie做了幾件事情,一個是分發(fā)DetachedFromWindow事件給下面的View华匾,接著銷毀所有的硬件加速的渲染線程內該View的資源(這里不做更多討論映琳,之后會有專門的文章討論),釋放繪制對象Surface蜘拉,如果當前的View是可見的則通過Session溝通WMS進行結束繪制萨西,最后調用WindowManagerGlobal的doRemoveView。
我們這里暫時只關心兩個方法finishDrawing以及doRemoveView旭旭。讓我們一個個的看一遍里面做了什么東西谎脯。
WMS finishDrawingWindow
void finishDrawingWindow(Session session, IWindow client) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "finishDrawingWindow: " + win + " mDrawState="
+ (win != null ? win.mWinAnimator.drawStateToString() : "null"));
if (win != null && win.mWinAnimator.finishDrawingLocked()) {
if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
win.getDisplayContent().pendingLayoutChanges |=
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
}
win.setDisplayLayoutNeeded();
mWindowPlacerLocked.requestTraversal();
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
此時先找到IWindow對應的WindowState,設置對應WindowState中的DisplayContent標志位設置為true您机。并且重新測量窗體邊緣穿肄。稍后會稍微深入WindowPlacerLocked.requestTraversal中做了什么事情。
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();
}
}
private void doTrimForeground() {
boolean hasVisibleWindows = false;
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
final ViewRootImpl root = mRoots.get(i);
if (root.mView != null && root.getHostVisibility() == View.VISIBLE
&& root.mAttachInfo.mThreadedRenderer != null) {
hasVisibleWindows = true;
} else {
root.destroyHardwareResources();
}
}
}
if (!hasVisibleWindows) {
ThreadedRenderer.trimMemory(
ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
}
}
能看到际看,經(jīng)過doDie釋放了必須的資源咸产,如硬件渲染啟動時候的渲染線程和Surface。并且重新計算窗體邊緣仲闽。
最后再把WindowManagerGlobal 中的mRoots中的對象和mDyingView的對象全部移除脑溢。doTrimForeground則是清除那些看不見的Window中的view對應的渲染線程的資源。
removeView的核心邏輯就是這么多了。
小結
updateViewLayout本質上是設置WindowParams屑彻,重新測量繪制整個Window中的view验庙。
removeView做了以下幾個事情:
- 1.關閉鍵盤
- 2.ViewRootImpl調用die方法,清除硬件加速渲染線程中對應的view資源社牲,重新執(zhí)行Window的大小計算粪薛。
- 3.清空殘留在WindowManagerGlobal中的對象。