本篇基於AndroidQ代碼分析
我們知道Android系統有三種類型窗口,應用窗口,系統窗口,子窗口,無論哪種窗口在WMS都會用一個WindowState來描述,Android窗口Z軸計算以及WindowState排列規則詳細介紹了WindowState的排列規則,每個窗口都需要一種token以識別身份,應用窗口對應AppWindowToken,系統窗口對應WindowToken,子窗口對應父窗口ViewRootImpl的W對象,另外三種類型窗口在WMS對應的WindowState會分別添加在不同的地方,應用窗口添加在AppWindowToken的WindowList中,系統窗口添加在WindowToken的WindowList中,子窗口添加在父窗口的WindowState的WindowList中,AppWindowToken繼承WindowToken,WindowToken繼承WindowContainer,WindowState也是繼承WindowContainer,WindowList是WindowContainer的成員變量,是一個ArrayList
// List of children for this window container. List is in z-order as the children appear on
// screen with the top-most window container at the tail of the list.
protected final WindowList<E> mChildren = new WindowList<E>();
這張圖引自羅昇陽的博客Android窗口管理服務WindowManagerService對窗口的組織方式分析
我們可以從圖中分析窗口的關係;
一個Activity對應一個ActivityRecord,一個ActivityRecord對應一個,AppWindowToken,一個AppWindowToken對應一組WindowState,一個WindowState可能還有子WindowState,當ActivityRecord順序定好了,AppWindowToken順序也好了,WindowState的順序自然也定好了,
Android窗口Z軸計算以及WindowState排列規則已經介紹了WindowState的排序規則,WindowState排好之後再對AppWindowToken和WindowToken排序,這樣整個窗口的Z-order就排好了,這篇文章來分析下AppWindowToken和WindowToken的添加排序過程
AppWindowToken是在Activity啓動過程創建的
ActivityStack.startActivityLocked
void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
boolean newTask, boolean keepCurTransition, ActivityOptions options) {
......
if (!startIt) {
r.createAppWindowToken();
.....
return;
}
......
}
ActivityRecord.createAppWindowToken
這個方法裏爲Activity創建了AppWindowToken,並通過Task進行添加,這裏的Task同樣繼承自WindowContainer
void createAppWindowToken() {
......
if (mAppWindowToken != null) {
// TODO: Should this throw an exception instead?
Slog.w(TAG, "Attempted to add existing app token: " + appToken);
} else {
final Task container = task.getTask();
if (container == null) {
throw new IllegalArgumentException("createAppWindowToken: invalid task =" + task);
}
mAppWindowToken = createAppWindow(mAtmService.mWindowManager, appToken,
task.voiceSession != null, container.getDisplayContent(),
ActivityTaskManagerService.getInputDispatchingTimeoutLocked(this)
* 1000000L, fullscreen,
(info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, appInfo.targetSdkVersion,
info.screenOrientation, mRotationAnimationHint,
mLaunchTaskBehind, isAlwaysFocusable());
container.addChild(mAppWindowToken, Integer.MAX_VALUE /* add on top */);
}
......
}
AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token,
boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
int rotationAnimationHint, boolean launchTaskBehind,
boolean alwaysFocusable) {
return new AppWindowToken(service, token, mActivityComponent, voiceInteraction, dc,
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
rotationAnimationHint, launchTaskBehind, alwaysFocusable,
this);
}
new了一個AppWindowToken,這裏傳遞的token是ActivityRecord構造方法中創建的IApplicationToken.Stub對象
AppWindowToken(WindowManagerService service, IApplicationToken token,
ComponentName activityComponent, boolean voiceInteraction, DisplayContent dc,
long inputDispatchingTimeoutNanos, boolean fullscreen, boolean showForAllUsers,
int targetSdk, int orientation, int rotationAnimationHint,
boolean launchTaskBehind, boolean alwaysFocusable,
ActivityRecord activityRecord) {
this(service, token, activityComponent, voiceInteraction, dc, fullscreen);
//省略賦值代碼
.....
}
AppWindowToken繼承WindowToken,這裏調用了WindowToken構造方法
WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) {
super(service);
token = _token;
windowType = type;
mPersistOnEmpty = persistOnEmpty;
mOwnerCanManageAppTokens = ownerCanManageAppTokens;
mRoundedCornerOverlay = roundedCornerOverlay;
onDisplayChanged(dc);
}
onDisplayChanged
void onDisplayChanged(DisplayContent dc) {
dc.reParentWindowToken(this);
//子類回調方法
super.onDisplayChanged(dc);
}
DisplayContent.reParentWindowToken
void reParentWindowToken(WindowToken token) {
final DisplayContent prevDc = token.getDisplayContent();
if (prevDc == this) {
return;
}
if (prevDc != null) {
if (prevDc.mTokenMap.remove(token.token) != null && token.asAppWindowToken() == null) {
// Removed the token from the map, but made sure it's not an app token before
// removing from parent.
token.getParent().removeChild(token);
}
if (prevDc.mLastFocus == mCurrentFocus) {
// The window has become the focus of this display, so it should not be notified
// that it lost focus from the previous display.
prevDc.mLastFocus = null;
}
}
//添加AppWindowToken
addWindowToken(token.token, token);
}
在看addWindowToken之前先看下DisplayContent構造方法,DisplayContent繼承WindowContainers,這裏將四個容器添加到了父類的WindowList中
DisplayContent(Display display, WindowManagerService service,
ActivityDisplay activityDisplay) {
.....
//mBelowAppWindowsContainers存儲壁紙窗口WindowToken
super.addChild(mBelowAppWindowsContainers, null);
//mTaskStackContainers存儲TaskStack而不是AppWindowToken
super.addChild(mTaskStackContainers, null);
//mAboveAppWindowsContainers存儲系統窗口WindowToken
super.addChild(mAboveAppWindowsContainers, null);
//mImeWindowsContainers存儲輸入法相關窗口WindowToken
super.addChild(mImeWindowsContainers, null);
.....
}
這四個類都是WindowContainer的間接子類,並且除mTaskStackContainers外的其他三個類存儲的都是WindowToken類型,mTaskStackContainers存儲的是TaskStack類型,可以猜測TaskStack裏面存儲的應該就是AppWindowToken
private final class TaskStackContainers extends
DisplayChildWindowContainer<TaskStack>
private final TaskStackContainers mTaskStackContainers = new TaskStackContainers(mWmService);
private final class AboveAppWindowContainers extends
NonAppWindowContainers
private final AboveAppWindowContainers mAboveAppWindowsContainers =
new AboveAppWindowContainers("mAboveAppWindowsContainers", mWmService);
private class NonAppWindowContainers extends
DisplayChildWindowContainer<WindowToken>
private final NonAppWindowContainers mBelowAppWindowsContainers =
new NonAppWindowContainers("mBelowAppWindowsContainers", mWmService);
private final NonAppWindowContainers mImeWindowsContainers =
new NonAppWindowContainers("mImeWindowsContainers", mWmService);
static class DisplayChildWindowContainer<E extends WindowContainer>
extends WindowContainer<E>
WindowContainer的addChild方法通過傳遞的自定義比較器對child進行排序,
DisplayContent中添加的四個類沒有傳入比較器,則按默認ArrayList添加順序排序,即按[mBelowAppWindowsContainers,mTaskStackContainers,mAboveAppWindowsContainers,mImeWindowsContainers]的順序,到這裏我們可以知道了mImeWindowsContainers裏面放的窗口在最上面,mBelowAppWindowsContainers裏面放的窗口在最底下
protected void addChild(E child, Comparator<E> comparator) {
......
int positionToAdd = -1;
if (comparator != null) {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (comparator.compare(child, mChildren.get(i)) < 0) {
positionToAdd = i;
break;
}
}
}
//-1代表按順序添加
if (positionToAdd == -1) {
mChildren.add(child);
} else {
//插入特定位置
mChildren.add(positionToAdd, child);
}
...
}
addWindowToken方法裏面可以看到,將系統類型窗口分了三類,壁紙類型,輸入法相關類型,其他類型,三種分別添加到mBelowAppWindowsContainers,mImeWindowsContainers和mAboveAppWindowsContainers
private void addWindowToken(IBinder binder, WindowToken token) {
......
//mTokenMap是一個hashMap,只要創建了WindowToken的窗口
//都會在這個map裏面,包括AppWindowToken和WindowToken
mTokenMap.put(binder, token);
//token.asAppWindowToken爲空代表不是應用類型窗口
if (token.asAppWindowToken() == null) {
switch (token.windowType) {
case TYPE_WALLPAPER:
//壁紙類型添加到mBelowAppWindowsContainers
mBelowAppWindowsContainers.addChild(token);
break;
case TYPE_INPUT_METHOD:
case TYPE_INPUT_METHOD_DIALOG:
//輸入法窗口或者輸入法對話窗口添加到mImeWindowsContainers
mImeWindowsContainers.addChild(token);
break;
default:
//其他類型添加到mAboveAppWindowsContainers
mAboveAppWindowsContainers.addChild(token);
break;
}
}
}
最終我們發現這個方法裏沒有添加AppWindowToken,那AppWindowToken在哪裏添加的呢?我們回到ActivityRecord.createAppWindowToken方法
void createAppWindowToken() {
......
if (mAppWindowToken != null) {
// TODO: Should this throw an exception instead?
Slog.w(TAG, "Attempted to add existing app token: " + appToken);
} else {
final Task container = task.getTask();
if (container == null) {
throw new IllegalArgumentException("createAppWindowToken: invalid task =" + task);
}
mAppWindowToken = createAppWindow(mAtmService.mWindowManager, appToken,
task.voiceSession != null, container.getDisplayContent(),
ActivityTaskManagerService.getInputDispatchingTimeoutLocked(this)
* 1000000L, fullscreen,
(info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, appInfo.targetSdkVersion,
info.screenOrientation, mRotationAnimationHint,
mLaunchTaskBehind, isAlwaysFocusable());
//傳遞一個int值最大數,保證添加的AppWindowToken總是在集合尾部
container.addChild(mAppWindowToken, Integer.MAX_VALUE /* add on top */);
}
......
}
Task.addChild
Task繼承WindowContainer,getAdjustedAddPosition方法獲取具體添加的position
@Override
void addChild(AppWindowToken wtoken, int position) {
position = getAdjustedAddPosition(position);
super.addChild(wtoken, position);
mDeferRemoval = false;
}
getAdjustedAddPosition
getAdjustedAddPosition這個方法就是獲取AppWindowToken集合的size
private int getAdjustedAddPosition(int suggestedPosition) {
//獲取當前AppWindowToken集合的大小
final int size = mChildren.size();
//suggestedPosition傳遞的是int最大值,肯定是大於size的
if (suggestedPosition >= size) {
return Math.min(size, suggestedPosition);
}
for (int pos = 0; pos < size && pos < suggestedPosition; ++pos) {
// TODO: Confirm that this is the behavior we want long term.
if (mChildren.get(pos).removed) {
// suggestedPosition assumes removed tokens are actually gone.
++suggestedPosition;
}
}
return Math.min(size, suggestedPosition);
}
接着調用父類addChild方法將AppWindowToken添加到集合尾部
void addChild(E child, int index) {
.....
//對index做基本判斷
if ((index < 0 && index != POSITION_BOTTOM)
|| (index > mChildren.size() && index != POSITION_TOP)) {
throw new IllegalArgumentException("addChild: invalid position=" + index
+ ", children number=" + mChildren.size());
}
//如果index是int最大值就直接等於AppWindowToken集合大小
if (index == POSITION_TOP) {
index = mChildren.size();
//如果index是int最小值就等於0
} else if (index == POSITION_BOTTOM) {
index = 0;
}
//添加到集合
mChildren.add(index, child);
.....
}
到這裏AppWindowToken就添加到了Task的WindowList中,前面我們說到mTaskStackContainers存儲的是TaskStack,TaskStack存儲的是Task,而Task存儲AppWindowToken,這樣AppWindowToken就被間接存儲到mTaskStackContainers中,最終他們對應關係應該是:
mTaskStackContainers存儲一組TaskStack,TaskStack存儲一組Task,Task存儲一組AppWindowToken,AppWindowToken存儲一組WindowState,WindowState還可以有子WindowState
我們這篇文章只分析AppWindowToken添加和排序,mTaskStackContainers的數據填充這裏就不去看了,涉及Activity的啓動過程,非常複雜
AppWindowToken分析完了接着分析WindowToken的添加和排序,其實之前我們已經提到了WindowToken的添加,系統類型窗口被分了三類,壁紙類型,輸入法相關類型,其他類型,三種分別添加到mBelowAppWindowsContainers,mImeWindowsContainers和mAboveAppWindowsContainers,添加過程同樣通過WindowToken的構造方法 -> onDisplayChanged -> DisplayContent.reParentWindowToken->addWindowToken
addWindowToken
private void addWindowToken(IBinder binder, WindowToken token) {
......
mTokenMap.put(binder, token);
//系統類型窗口才添加
if (token.asAppWindowToken() == null) {
switch (token.windowType) {
case TYPE_WALLPAPER:
//壁紙類型
mBelowAppWindowsContainers.addChild(token);
break;
case TYPE_INPUT_METHOD:
case TYPE_INPUT_METHOD_DIALOG:
//輸入法和輸入法對話框類型
mImeWindowsContainers.addChild(token);
break;
default:
//其他類型
mAboveAppWindowsContainers.addChild(token);
break;
}
}
}
壁紙和輸入法兩種特殊類型窗口先不用管,我們看其他類型的系統窗口
AboveAppWindowContainers繼承NonAppWindowContainers,addChild調用的是父類的方法,父類NonAppWindowContainers又調用了父類WindowContainer的方法,WindowContainer的addChild方法之前的文章Android窗口Z軸計算以及WindowState排列規則已經分析過多次了,主要就是通過傳遞的自定義比較器對傳遞的child進行排序
void addChild(WindowToken token) {
addChild(token, mWindowComparator);
}
我們看一下mWindowComparator的比較規則,getWindowLayerFromTypeLw這個方法會通過不同窗口類型返回不同數值,有三十多種類型,不同類型就代表不同層級
private final Comparator<WindowToken> mWindowComparator = (token1, token2) ->
mWmService.mPolicy.getWindowLayerFromTypeLw(token1.windowType,
token1.mOwnerCanManageAppTokens)
< mWmService.mPolicy.getWindowLayerFromTypeLw(token2.windowType,
token2.mOwnerCanManageAppTokens) ? -1 : 1;
default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow) {
if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
return APPLICATION_LAYER;
}
switch (type) {
case TYPE_WALLPAPER:
// wallpaper is at the bottom, though the window manager may move it.
return 1;
case TYPE_PRESENTATION:
case TYPE_PRIVATE_PRESENTATION:
return APPLICATION_LAYER;
case TYPE_DOCK_DIVIDER:
return APPLICATION_LAYER;
case TYPE_QS_DIALOG:
return APPLICATION_LAYER;
case TYPE_PHONE:
return 3;
case TYPE_SEARCH_BAR:
case TYPE_VOICE_INTERACTION_STARTING:
return 4;
case TYPE_VOICE_INTERACTION:
// voice interaction layer is almost immediately above apps.
return 5;
case TYPE_INPUT_CONSUMER:
return 6;
case TYPE_SYSTEM_DIALOG:
return 7;
case TYPE_TOAST:
// toasts and the plugged-in battery thing
return 8;
.....
default:
Slog.e("WindowManager", "Unknown window type: " + type);
return APPLICATION_LAYER;
所以如果當前添加的窗口的層級小於與之進行比較的窗口層級則返回-1,否則返回1,返回1和返回-1的結果就是向mAboveAppWindowsContainers的WindowList中添加元素的位置不同,-1則positionToAdd會重新賦值,賦的值等於與當前添加窗口進行比較的窗口的位置,即我們添加的窗口會插入positionToAdd的位置,返回1則直接添加到WindowList的尾部即可
protected void addChild(E child, Comparator<E> comparator) {
.....
int positionToAdd = -1;
if (comparator != null) {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (comparator.compare(child, mChildren.get(i)) < 0) {
positionToAdd = i;
break;
}
}
}
if (positionToAdd == -1) {
mChildren.add(child);
} else {
mChildren.add(positionToAdd, child);
}
......
child.setParent(this);
}
可以得出結論mAboveAppWindowsContainers的WindowList一定是按窗口的層級數值由小到大進行排列的
到這裏我們已經分析完了AppWindowToken和WindowToken的添加流程和排序規則,我們通過系統dump命令來看一下系統所有窗口已經token存儲即排列規則
創建一個簡單的Activity
在MainActivity界面dump一下
Dump time : 2020-01-10 04:19:28.976
ROOT type=undefined mode=fullscreen
#0 Display 0 name="Built-in screen" type=undefined mode=fullscreen
#3 mImeWindowsContainers type=undefined mode=fullscreen
#0 WindowToken{f127d64 android.os.Binder@d21eaf7} type=undefined mode=fullscreen
#2 mAboveAppWindowsContainers type=undefined mode=fullscreen
#3 WindowToken{d72862d android.os.BinderProxy@856e544} type=undefined mode=fullscreen
#0 2730762 NavigationBar type=undefined mode=fullscreen
#2 WindowToken{c351b88 android.os.BinderProxy@2f95b2b} type=undefined mode=fullscreen
#0 4305921 StatusBar type=undefined mode=fullscreen
#1 WindowToken{1800ac7 android.os.BinderProxy@9af9406} type=undefined mode=fullscreen
#0 3b768f4 AssistPreviewPanel type=undefined mode=fullscreen
#0 WindowToken{8a36a18 android.os.BinderProxy@2ac89fb} type=undefined mode=fullscreen
#0 e77ac71 DockedStackDivider type=undefined mode=fullscreen
#1 com.android.server.wm.DisplayContent$TaskStackContainers@edb3b60 type=undefined mode=fullscreen
#2 Stack=9 type=standard mode=fullscreen
#0 Task=13 type=standard mode=fullscreen
#0 AppWindowToken{472456c token=Token{6532e1f ActivityRecord{d064dbe u0 com.tct.aidl/.MainActivity t13}}} type=standard mode=fullscreen
#0 b20eb59 com.tct.aidl/com.tct.aidl.MainActivity type=standard mode=fullscreen
#1 Stack=0 type=home mode=fullscreen
#1 Task=3 type=home mode=fullscreen
#0 AppWindowToken{e3fcb14 token=Token{4822167 ActivityRecord{de76126 u0 com.tct.launcher/.Launcher t3}}} type=home mode=fullscreen
#0 a1b2ccd com.tct.launcher/com.tct.launcher.Launcher type=home mode=fullscreen
#0 Task=4 type=home mode=fullscreen
#0 AppWindowToken{764dc81 token=Token{4e61068 ActivityRecord{773978b u0 com.tct.launcher/.Launcher t4}}} type=home mode=fullscreen
#0 Stack=8 type=recents mode=fullscreen
#0 Task=12 type=recents mode=fullscreen
#0 AppWindowToken{3696d7b token=Token{472c10a ActivityRecord{12bfd75 u0 com.android.systemui/.recents.RecentsActivity t12}}} type=recents mode=fullscreen
#0 ce2c94f com.android.systemui/com.android.systemui.recents.RecentsActivity type=recents mode=fullscreen
#0 mBelowAppWindowsContainers type=undefined mode=fullscreen
#0 WallpaperWindowToken{91b8faa token=android.os.Binder@f126295} type=undefined mode=fullscreen
#0 8ff11c1 com.android.systemui.ImageWallpaper type=undefined mode=fullscreen
Window{2730762 u0 NavigationBar}
Window{4305921 u0 StatusBar}
Window{3b768f4 u0 AssistPreviewPanel}
Window{e77ac71 u0 DockedStackDivider}
Window{b20eb59 u0 com.tct.aidl/com.tct.aidl.MainActivity}
Window{a1b2ccd u0 com.tct.launcher/com.tct.launcher.Launcher}
Window{ce2c94f u0 com.android.systemui/com.android.systemui.recents.RecentsActivity}
Window{8ff11c1 u0 com.android.systemui.ImageWallpaper}
注意前面的空格,這段dump信息意思是:
1.Display下有一個mImeWindowsContainers,一個mAboveAppWindowsContainers,一個DisplayContent$TaskStackContainers,一個mBelowAppWindowsContainers,這四個類就是我們前面分析的四大窗口容器
2.mImeWindowsContainers下有一個WindowToken這應該是輸入法,但是沒有WindowState,說明界面上是沒有輸入法的
3.mAboveAppWindowsContainers下有多個WindowToken,每個WindowToken下都有一個WindowState,分別是NavigationBar,StatusBar,AssistPreviewPanel,DockedStackDivider
4.DisplayContent$TaskStackContainers下有多個TaskStack,每個TaskStack有一個Task,每個Task下有一個AppWindowToken,每個AppWindowToken下有一個WindowState,分別是Window{b20eb59 u0 com.tct.aidl/com.tct.aidl.MainActivity},
Window{a1b2ccd u0 com.tct.launcher/com.tct.launcher.Launcher},
Window{ce2c94f u0 com.android.systemui/com.android.systemui.recents.RecentsActivity}
5.mBelowAppWindowsContainers下有一個WallpaperWindowToken,WallpaperWindowToken下有一個WindowState爲Window{8ff11c1 u0 com.android.systemui.ImageWallpaper}
dump信息最後一段就是當前窗口的Z-order,NavigationBar最大,ImageWallpaper最小
Window{2730762 u0 NavigationBar}
Window{4305921 u0 StatusBar}
Window{3b768f4 u0 AssistPreviewPanel}
Window{e77ac71 u0 DockedStackDivider}
Window{b20eb59 u0 com.tct.aidl/com.tct.aidl.MainActivity}
Window{a1b2ccd u0 com.tct.launcher/com.tct.launcher.Launcher}
Window{ce2c94f u0 com.android.systemui/com.android.systemui.recents.RecentsActivity}
Window{8ff11c1 u0 com.android.systemui.ImageWallpaper}
可以看到和我們代碼分析一樣,Android系統創建的所有WindowToken和AppWindowToken都是直接或間接存儲在這四個容器中,應用窗口和系統窗口描述信息WindowState則是存儲在AppWindowToken或者WindowToken中,子窗口的WindowState存儲在父窗口的WindowState中,子窗口也不會創建自己的WindowToken
Android窗口Z軸計算以及WindowState排列規則和這篇詳細介紹了AppWindowToken,WindowToken,WindowState這三種對象的創建和添加以及排序流程,
最終會分別添加到四種類型窗口容器,這個四個容器在DisplayContent構造方法中添加到了DisplayContent的WindowList中,傳遞的比較器爲空即按默認ArrayList順序添加,這四個窗口容器順序排好了,再把他們裏面的WindowToken排好順序,再把WindowToken裏面的WindowState排好順序,這樣一層一層下去,最終系統所有窗口都按照Z軸位置從低到高排列