SwallowJoe的博客

Be a real go-getter,
NEVER SETTLE!

0%

WMS(6)-焦点窗口的更新

以下分析基于Android S.

简述

这篇文章中我们重点关注焦点窗口的更新,所谓焦点窗口就是当前选择的窗口。在Android里可以通过下面的adb命令来查看当前的焦点窗口:

adb shell dumpsys window |grep -iE “mCurr*”

我们知道一个实体显示器对应一个DisplayId, 相应的有一个DisplayContent,如同我们人眼或者相机的对焦,同一时刻只能有一个焦点,那么对应一个Display一般来说也只有一个焦点窗口了。

我们之前一直分析的WMS.addWindow,在应用进程调用setView传入对应窗口属性之后,当然也会有焦点窗口的重新计算了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, InsetsState requestedVisibility,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
......
// 窗口类型保存在LayoutParams中
final int type = attrs.type;
synchronized (mGlobalLock) {
......
// 这里DisplayContent中至少包含该App的一个WindowToke,也就是Activity被start时创建的ActivityRecord!
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
...
// 初始化WindowState
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);
......
// 创建SurfaceSession, 用来和SurfaceFlinger通信
win.attach();
// 保存WindowState, key为ViewRoomImpl.W
mWindowMap.put(client.asBinder(), win);
// 和应用窗口权限有关,后续分析
win.initAppOpsState();
......
// 将该WindowState保存在其mToken(ActivityRecord)中
win.mToken.addWindow(win);
......
// 标记焦点窗口是否更新
boolean focusChanged = false;
// [1.1] 检查该窗口是否可以接收input事件
if (win.canReceiveKeys()) {
// [1.2] 更新焦点窗口
focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
false /*updateInputWindows*/);
if (focusChanged) {
imMayMove = false;
}
}
......
// [2.1] 如果焦点窗口有更新,也需要更新input相关设置
if (focusChanged) {
displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
false /*updateInputWindows*/);
}
......
// 该窗口成功被添加或者是可见的而且当前display需要更新方向时,更新display相关配置
if (win.isVisibleOrAdding() && displayContent.updateOrientation()) {
displayContent.sendNewConfiguration();
}
......
}

一. 焦点窗口更新

应用的第一个addWindow应该是启动窗口的,所以我们以启动窗口为例分析焦点窗口更新。

1.1 WindowState.canReceiveKeys

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
boolean canReceiveKeys() {
return canReceiveKeys(false /* fromUserTouch */);
}

public boolean canReceiveKeys(boolean fromUserTouch) {
// [1.1.1] 检查该窗口是否可见
final boolean canReceiveKeys = isVisibleOrAdding()
&& (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
// [1.1.2] 确认该ActivityRecord是否可聚焦
&& (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
// [1.1.3] 判断该WindowState是否允许接收input事件
&& canReceiveTouchInput();
if (!canReceiveKeys) {
return false;
}
// 除非用户有意触摸显示器,否则不允许不受信任的虚拟显示接收input事件
return fromUserTouch || getDisplayContent().isOnTop()
|| getDisplayContent().isTrusted();
}

这里检查了一堆参数,我们一一分析:

  1. isVisibleOrAdding(): 判断当前窗口是否可见或者被添加
  2. mViewVisibility == View.VISIBLE: 窗口可见属性为VISIBLE
  3. !mRemoveOnExit: 该窗口没有移除或者退出
  4. ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0): 该窗口的属性不带有FLAG_NOT_FOCUSABLE,是可聚焦的
  5. (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch)):该窗口不是Activity对应的窗口,如果是那么需要满足windowsAreFocusable
  6. canReceiveTouchInput(): 该窗口可以接收touch事件

只有当上述条件都满足且窗口所处的DisplayContent是最上层的并且该Display是受信任的,该窗口才可能接收事件。

1.1.1 WindowState.isVisibleOrAdding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
boolean isVisibleOrAdding() {
// 该窗口不一定对应Activity,比如启动窗口
final ActivityRecord atoken = mActivityRecord;
return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
&& isVisibleByPolicy() && !isParentWindowHidden()
&& (atoken == null || atoken.mVisibleRequested)
&& !mAnimatingExit && !mDestroying;
}

boolean isVisibleByPolicy() {
// 判断mPolicyVisibility中的POLICY_VISIBILITY_ALL位是否被设置了
// mPolicyVisibility默认就是POLICY_VISIBILITY_ALL
return (mPolicyVisibility & POLICY_VISIBILITY_ALL) == POLICY_VISIBILITY_ALL;
}

boolean isParentWindowHidden() {
// 当前窗口的父窗口是否被隐藏了
final WindowState parent = getParentWindow();
return parent != null && parent.mHidden;
}

判断该窗口是否可见或者处于被添加状态。

  1. (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE)):
    1. mHasSurface:该窗口是否有对应Surface,该Surface会在该窗口对应的启动或退出动画时创建
    2. (!mRelayoutCalled && mViewVisibility == View.VISIBLE):
      1. !mRelayoutCalled: 该窗口被调用了relayoutWindow
      2. mViewVisibility == View.VISIBLE: 窗口View属性对应的是VISIBLE
    3. 如果该窗口已经有Surface或者没有被relayoutWindow但窗口可见属性是VISIBLE的时
  2. isVisibleByPolicy():判断mPolicyVisibility中的POLICY_VISIBILITY_ALL位都被设置了
  3. !isParentWindowHidden(): 判断父窗口没有被隐藏
  4. (atoken == null || atoken.mVisibleRequested):
    1. atoken == null:该窗口不对应ActivityRecord
    2. atoken.mVisibleRequested: 如果对应,需要mVisibleRequested被设置为true
    3. !mAnimatingExit:该窗口当前没有执行退出动画
    4. !mDestroying:该窗口没有被销毁

1.1.2 ActivityRecord.windowsAreFocusable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
boolean windowsAreFocusable(boolean fromUserTouch) {
// fromUserTouch为false
if (!fromUserTouch && mTargetSdk < Build.VERSION_CODES.Q) {
final int pid = getPid();
final ActivityRecord topFocusedAppOfMyProcess =
mWmService.mRoot.mTopFocusedAppByProcess.get(pid);
if (topFocusedAppOfMyProcess != null && topFocusedAppOfMyProcess != this) {
// 对于Q以下的应用程序,每个进程只能有一个具有聚焦窗口的应用程序,因为以往的应用程序可能无法用于多聚焦系统
return false;

}
}
// 确认该ActivityRecord是否已经attach了
// isAttached是其父类的父类WindowContainer中的方法,用于判断该ActivityRecord的parent的DisplayArea是否为null
return (canReceiveKeys() || isAlwaysFocusable()) && isAttached();
}

boolean canReceiveKeys() {
// 确定该ActivityRecord对应的窗口配置是否允许接收input事件
return getWindowConfiguration().canReceiveKeys()
&& (task == null || task.getWindowConfiguration().canReceiveKeys());
}

private boolean isAlwaysFocusable() {
return (info.flags & FLAG_ALWAYS_FOCUSABLE) != 0;
}

windowsAreFocusable用于判断该ActivityRecord对应窗口是否可以接收input事件。

1.1.3 WindowState.canReceiveTouchInput

1
2
3
4
5
6
7
8
9
boolean canReceiveTouchInput() {
if (mActivityRecord == null || mActivityRecord.getTask() == null) {
return true;
}

return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
&& mActivityRecord.mVisibleRequested
&& !isRecentsAnimationConsumingAppInput();
}

这里可以看到如果是非ActivityRecord对应的Window那么直接返回true,如果该ActivityRecord还没有被加入task也直接返回true.

  1. !mActivityRecord.getTask().getRootTask().shouldIgnoreInput():该ActivityRecord的task所处的rootTask没有被设置忽略input事件
  2. mActivityRecord.mVisibleRequested: 该ActivityRecord被设置了VISIBLE
  3. !isRecentsAnimationConsumingAppInput():该窗口没有作为最近任务窗口的动画的一部分时

1.2 WMS.updateFocusedWindowLocked

1
2
3
4
5
6
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return changed;
}

这里传入的mode是UPDATE_FOCUS_WILL_ASSIGN_LAYERS,而且updateInputWindows为false. WMS.mRoot就是RootWindowContainer.

1.3 RootWindowContainer.updateFocusedWindowLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
// 首先清除mTopFocusedAppByProcess中的元素:ArrayMap<Integer, ActivityRecord> mTopFocusedAppByProcess
mTopFocusedAppByProcess.clear();
boolean changed = false;
int topFocusedDisplayId = INVALID_DISPLAY;
// 遍历所有DisplayContent, 并依次更新焦点窗口, 注意这里是倒序更新计算
for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayContent dc = mChildren.get(i);
// [1.4] 更新单个DisplayContent的焦点窗口
changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);
final WindowState newFocus = dc.mCurrentFocus;
// DC存在新的焦点窗口时
if (newFocus != null) {
final int pidOfNewFocus = newFocus.mSession.mPid;
// 将新焦点窗口的ActivityRecord保存在mTopFocusedAppByProcess中
if (mTopFocusedAppByProcess.get(pidOfNewFocus) == null) {
mTopFocusedAppByProcess.put(pidOfNewFocus, newFocus.mActivityRecord);
}
// 更新topFocusedDisplayId为该DC的DisplayId
if (topFocusedDisplayId == INVALID_DISPLAY) {
topFocusedDisplayId = dc.getDisplayId();
}
} else if (topFocusedDisplayId == INVALID_DISPLAY && dc.mFocusedApp != null) {
// 即使应用窗口还没有准备好(未附加进程或未添加窗口),具有焦点应用的顶部显示仍然应该是焦点顶部显示
topFocusedDisplayId = dc.getDisplayId();
}
}
if (topFocusedDisplayId == INVALID_DISPLAY) {
topFocusedDisplayId = DEFAULT_DISPLAY;
}
// 当焦点所处的display有变化时
if (mTopFocusedDisplayId != topFocusedDisplayId) {
mTopFocusedDisplayId = topFocusedDisplayId;
// [2.1] 通知input系统更新displayId
mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
// 将PhoneWindowManager中的mTopFocusedDisplayId更新为新焦点窗口所处的DisplayId
mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId);
}
return changed;
}

计算更新焦点窗口是对所有DisplayContent的依次计算更新焦点窗口。首先清除mTopFocusedAppByProcess中的元素,然后依次更新所有DisplayContent的焦点窗口。

1.4 DisplayContent.updateFocusedWindowLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
int topFocusedDisplayId) {
// [1.4.1] 计算当前DisplayContent的焦点窗口,注意传入的topFocusedDisplayId是INVALID_DISPLAY
WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
// 如果计算的新焦点窗口和当前焦点窗口是同一个,则无需额外动作
if (mCurrentFocus == newFocus) {
return false;
}

boolean imWindowChanged = false;
final WindowState imWindow = mInputMethodWindow;
// 如果当前存在输入法窗口,这里我们先假设不存在
if (imWindow != null) {
......
}

ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "Changing focus from %s to %s displayId=%d Callers=%s",
mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));
// 更新焦点窗口
final WindowState oldFocus = mCurrentFocus;
mCurrentFocus = newFocus;
// 如果新的焦点窗口不为null
if (newFocus != null) {
mWinAddedSinceNullFocus.clear();
mWinRemovedSinceNullFocus.clear();

if (newFocus.canReceiveKeys()) {
// 隐式地显示一个窗口将导致取消调度
// 这是为了防止有人暂停调度但忘记resume
newFocus.mToken.paused = false;
}
}
// [1.4.2] 通知其他模组焦点窗口更新了,主要是更新两个Task的阴影
onWindowFocusChanged(oldFocus, newFocus);

// [1.4.3] 更新DisplayPolicy中相关参数
int focusChanged = getDisplayPolicy().focusChangedLw(oldFocus, newFocus);

......

if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
// 焦点的变化导致需要做一个布局, SystemUI状态栏或者导航栏可见性有变化了
// 将参数mLayoutNeeded置位true
setLayoutNeeded();
// 这里传入的mode为UPDATE_FOCUS_WILL_ASSIGN_LAYERS
if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
performLayout(true /*initial*/, updateInputWindows);
} else if (mode == UPDATE_FOCUS_REMOVING_FOCUS) {
mWmService.mRoot.performSurfacePlacement();
}
}

......

// 将没有焦点的窗口的toast在超时时间后移除,防止其覆盖UI
scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);
......
// 记录新焦点窗口
mLastFocus = mCurrentFocus;
return true;
}

DisplayContent更新焦点窗口的流程如下:

  1. 计算当前DisplayContent的焦点窗口
  2. 如果计算的新焦点窗口和当前焦点窗口是同一个,则无需额外动作
  3. 如果当前存在输入法窗口,执行相关操作(后续分析输入法窗口)
  4. 更新焦点窗口,保存在mCurrentFocus中
    1. 清空mWinAddedSinceNullFocus、mWinRemovedSinceNullFocus
  5. 通知其他模组焦点窗口更新了,主要是更新两个Task(当前焦点窗口和新的焦点窗口所处的Task)的阴影
  6. 更新DisplayPolicy中相关参数
    1. 更新DisplayFoldController中的mFocusedApp(焦点窗口包名),然后根据需要更新SystemUI的可见性
  7. 如果因焦点窗口变化导致SystemUI状态栏或者导航栏可见性有变化了,将参数mLayoutNeeded置位true
  8. 将没有焦点的窗口的toast在超时时间后移除,防止其覆盖UI
  9. 记录新焦点窗口,保存在mLastFocus中

1.4.1 DisplayContent.findFocusedWindowIfNeeded

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {
return (mWmService.mPerDisplayFocusEnabled || topFocusedDisplayId == INVALID_DISPLAY)
? findFocusedWindow() : null;
}

WindowState findFocusedWindow() {
mTmpWindow = null;
// [1.4.1.1] 从上到下遍历所有的ActivityRecord,并对每一个执行mFindFocusedWindow方法
forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);

if (mTmpWindow == null) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: No focusable windows, display=%d",
getDisplayId());
return null;
}
return mTmpWindow;
}

注意这里的mFindFocusedWindow其实是一个函数: ToBooleanFunction mFindFocusedWindow, 这里又是使用了函数式编程的方法。我们先看forAllWindows方法,这个是被继承的祖父类WindowContainer中的方法。

1.4.1.1 WindowContainer.forAllWindows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
return true;
}
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
return true;
}
}
}
return false;
}

对该WindowContainer的mChildren中的每一个元素执行forAllWindows,调用callback。traverseTopToBottom参数如果为true,则按z顺序从上到下遍历层次结构,否则从下到上遍历。如果因为传入的函数callback执行后的返回值为true,那么会中止遍历直接返回true。

回顾下DisplayContent的类图:

6-1

可以看到DisplayContent的是从WindowContainer一路继承下来的,所以其mChildren就是 WindowList

而DisplayArea也是继承了WindowContainer,所以到底这个DisplayContent里存储了什么元素呢? 回到之前我们研究过的DisplayContent的创建 WMS(2)-WMS中RootDisplayArea的创建, 在这里我们知道DisplayContent的mChildren中存储的是DisplayArea的对象,所以而DisplayArea又是继承了WindowContainer,然而DisplayArea中的mChildren存储的是ActivityRecord或者WindowState.

1.4.1.2 WindowState.forAllWindows

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
if (mChildren.isEmpty()) {
// The window has no children so we just return it.
return applyInOrderWithImeWindows(callback, traverseTopToBottom);
}

if (traverseTopToBottom) {
return forAllWindowTopToBottom(callback);
} else {
return forAllWindowBottomToTop(callback);
}
}

这里我们假设该mChildren为null:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback,
boolean traverseTopToBottom) {
if (traverseTopToBottom) {
// applyImeWindowsIfNeeded是用于处理输入法窗口的,暂时先不管。
if (applyImeWindowsIfNeeded(callback, traverseTopToBottom)
// [1.5] 对该Window执行传入的函数,即mFindFocusedWindow
|| callback.apply(this)) {
return true;
}
} else {
if (callback.apply(this)
|| applyImeWindowsIfNeeded(callback, traverseTopToBottom)) {
return true;
}
}
return false;
}

1.4.2 DisplayContent.onWindowFocusChanged

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static void onWindowFocusChanged(WindowState oldFocus, WindowState newFocus) {
final Task focusedTask = newFocus != null ? newFocus.getTask() : null;
final Task unfocusedTask = oldFocus != null ? oldFocus.getTask() : null;
// 如果是同一个task就不需要额外动作
if (focusedTask == unfocusedTask) {
return;
}
// 通知新的焦点窗口所处的Task有焦点了
if (focusedTask != null) {
focusedTask.onWindowFocusChanged(true /* hasFocus */);
}
// 之前焦点窗口所处的Task失去焦点
if (unfocusedTask != null) {
unfocusedTask.onWindowFocusChanged(false /* hasFocus */);
}
}

从WindowState中拿到的Task其实是其对应的ActivityRecord所在的Task:

1
2
3
4
5
6
7
8
9
// WindowState.java
Task getTask() {
return mActivityRecord != null ? mActivityRecord.getTask() : null;
}

// ActivityRecord.java
Task getTask() {
return task;
}

这个ActivityRecord.task就是 WMS(3)-ActivityRecord和WindowToken 中 [1.7.1]里赋值的,其实就是该ActivityRecord的mParent(创建的新Task)。

1.4.2.1 Task.onWindowFocusChanged

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void onWindowFocusChanged(boolean hasFocus) {
updateShadowsRadius(hasFocus, getSyncTransaction());
// TODO(b/180525887): Un-comment once there is resolution on the bug.
// dispatchTaskInfoChangedIfNeeded(false /* force */);
}

private void updateShadowsRadius(boolean taskIsFocused,
SurfaceControl.Transaction pendingTransaction) {

if (!mWmService.mRenderShadowsInCompositor || !isRootTask()) return;
// 根据窗口模式和任务焦点状态更新阴影的长度
final float newShadowRadius = getShadowRadius(taskIsFocused);
if (mShadowRadius != newShadowRadius) {
mShadowRadius = newShadowRadius;
pendingTransaction.setShadowRadius(getSurfaceControl(), mShadowRadius);
}
}

这里根据该task是否持有焦点来更新阴影,后续研究

1.4.3 DisplayPolicy.focusChangedLw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int focusChangedLw(WindowState lastFocus, WindowState newFocus) {
mFocusedWindow = newFocus;
mLastFocusedWindow = lastFocus;
if (mDisplayContent.isDefaultDisplay) {
// 更新PhoneWindowManager中相关参数,就是更新DisplayFoldController中的mFocusedApp(焦点窗口包名)
mService.mPolicy.onDefaultDisplayFocusChangedLw(newFocus);
}
// 更新SystemUi的可见与否,比如状态栏等,后续研究
if (updateSystemUiVisibilityLw()) {
// 如果导航栏已经被隐藏或显示,需要做另一个布局传递来更新窗口
return FINISH_LAYOUT_REDO_LAYOUT;
}
return 0;
}

这里更新DisplayPolicy中存储的焦点窗口相关信息,包括更新DisplayFoldController中的mFocusedApp(焦点窗口包名),然后根据需要更新SystemUI的可见性。

1.5 DisplayContent.mFindFocusedWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
final ActivityRecord focusedApp = mFocusedApp;
ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
w, w.mAttrs.flags, w.canReceiveKeys(),
w.canReceiveKeysReason(false /* fromUserTouch */));

// 检查该窗口是否可以接收input事件
// 无法接收输入事件的窗口没有资格作为焦点窗口
if (!w.canReceiveKeys()) {
return false;
}

final ActivityRecord activity = w.mActivityRecord;
// 如果当前没有焦点窗口,那么遍历的第一个可接受input事件的窗口就是焦点窗口了
if (focusedApp == null) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
"findFocusedWindow: focusedApp=null using new focus @ %s", w);
mTmpWindow = w;
return true;
}
// 如果当前焦点窗口已经不在可聚焦了,那么也是遍历的第一个可接受input事件的窗口作为焦点窗口
if (!focusedApp.windowsAreFocusable()) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: focusedApp windows not"
+ " focusable using new focus @ %s", w);
mTmpWindow = w;
return true;
}

// 遍历所有app的token, 找到第一个符合要求的作为焦点窗口(从上到下遍历所有的ActivityRecord,所以第一个符合的token对应窗口Z轴最大)
if (activity != null && w.mAttrs.type != TYPE_APPLICATION_STARTING) {
// WindowState对应的有Activity, 而且类型不能是启动窗口
// [1.6] 对比计算Z轴大小
if (focusedApp.compareTo(activity) > 0) {
// 当前焦点窗口Z轴比该Activity窗口大
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
"findFocusedWindow: Reached focused app=%s", focusedApp);
mTmpWindow = null;
return true;
}
}
// 找到了新的焦点窗口,暂存在mTmpWindow中
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: Found new focus @ %s", w);
mTmpWindow = w;
return true;
};

更新焦点窗口的步骤也不复杂:

  1. 遍历该DisplayContent中mChildren中的所有DisplayArea
  2. 依次对比DisplayArea中的mChildren集合里面的Window(ActivityRecord或WindowState)
    1. 检查该Window是否符合要求
    2. 无法接收输入事件的窗口没有资格作为焦点窗口
    3. 如果当前没有焦点窗口,那么遍历的第一个可接受input事件的窗口就是焦点窗口了
    4. 如果当前焦点窗口已经不在可聚焦了,那么也是遍历的第一个可接受input事件的窗口作为焦点窗口
    5. 遍历所有app的token, 找到第一个符合要求的作为焦点窗口
      1. WindowState对应的有Activity, 而且类型不能是启动窗口
      2. 该Activity窗口Z轴比当前焦点窗口大
      3. 更新mTmpWindow为当前Activity窗口,否则置为null

1.6 ActivityRecord.compareTo

ActivityRecord其实是调用了父类WindowContainer中的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Override
public int compareTo(WindowContainer other) {
if (this == other) {
return 0;
}

// 如果该Activity的mParent是同一个,那么就对比两个在其中所处的位置
// 序号就代表了Z轴,可以理解为序号大的在上层,盖住序号小的
if (mParent != null && mParent == other.mParent) {
final WindowList<WindowContainer> list = mParent.mChildren;
return list.indexOf(this) > list.indexOf(other) ? 1 : -1;
}

// 如果两个WindowContainer不是同一个mParent, 那么先找到z轴最大的包含两个WC的父容器,然后对比z轴大小
final LinkedList<WindowContainer> thisParentChain = mTmpChain1;
final LinkedList<WindowContainer> otherParentChain = mTmpChain2;
try {
// [1.6.1] 获取当前WindowContainer的所有父容器
getParents(thisParentChain);
other.getParents(otherParentChain);

// 找到z轴最大的且包含两个WC的父容器
WindowContainer commonAncestor = null;
WindowContainer thisTop = thisParentChain.peekLast();
WindowContainer otherTop = otherParentChain.peekLast();
while (thisTop != null && otherTop != null && thisTop == otherTop) {
// 移除拿到最后一个父容器
commonAncestor = thisParentChain.removeLast();
otherParentChain.removeLast();
thisTop = thisParentChain.peekLast();
otherTop = otherParentChain.peekLast();
}

......

// 子容器总是被认为比父容器大,所以如果将一个容器与另一个容器的父容器进行比较,那么无论如何都是子容器更大。
if (commonAncestor == this) {
return -1;
} else if (commonAncestor == other) {
return 1;
}

// 各自对比两个WC在Z轴最大的父容器中的位置
final WindowList<WindowContainer> list = commonAncestor.mChildren;
return list.indexOf(thisParentChain.peekLast()) > list.indexOf(otherParentChain.peekLast())
? 1 : -1;
} finally {
mTmpChain1.clear();
mTmpChain2.clear();
}
}

可以将WC的整个结构理解为树形结构,对比两个WC的Z轴大小就是对比这两个WC与最近的共同父节点的距离大小,距离越大,说明Z轴越大。

1.6.1 WindowContainer.getParents

1
2
3
4
5
6
7
8
private void getParents(LinkedList<WindowContainer> parents) {
parents.clear();
WindowContainer current = this;
do {
parents.addLast(current);
current = current.mParent;
} while (current != null);
}

依次遍历,将该WindowContainer的所有mParent添加到队列中.

二. 小结

总的来说,更新焦点窗口就是对所有DisplayContent倒序遍历,依次计算新的焦点窗口:

  1. 计算当前DisplayContent的焦点窗口
    1. 遍历该DisplayContent中mChildren中的所有DisplayArea
    2. 依次对比DisplayArea中的mChildren集合里面的Window(ActivityRecord或WindowState)
      1. 检查该Window是否符合要求
      2. 无法接收输入事件的窗口没有资格作为焦点窗口
      3. 如果当前没有焦点窗口,那么遍历的第一个可接受input事件的窗口就是焦点窗口了
      4. 如果当前焦点窗口已经不在可聚焦了,那么也是遍历的第一个可接受input事件的窗口作为焦点窗口
      5. 遍历所有app的token, 找到第一个符合要求的作为焦点窗口
        1. WindowState对应的有Activity, 而且类型不能是启动窗口
        2. 该Activity窗口Z轴比当前焦点窗口大
        3. 更新mTmpWindow为当前Activity窗口,否则置为null
  2. 如果计算的新焦点窗口和当前焦点窗口是同一个,则无需额外动作
  3. 如果当前存在输入法窗口,执行相关操作(后续分析输入法窗口)
  4. 更新焦点窗口,保存在mCurrentFocus中
    1. 清空mWinAddedSinceNullFocus、mWinRemovedSinceNullFocus
  5. 通知其他模组焦点窗口更新了,主要是更新两个Task(当前焦点窗口和新的焦点窗口所处的Task)的阴影
  6. 更新DisplayPolicy中相关参数
    1. 更新DisplayFoldController中的mFocusedApp(焦点窗口包名),然后根据需要更新SystemUI的可见性
  7. 如果因焦点窗口变化导致SystemUI状态栏或者导航栏可见性有变化了,将参数mLayoutNeeded置位true
  8. 将没有焦点的窗口的toast在超时时间后移除,防止其覆盖UI
  9. 记录新焦点窗口,保存在mLastFocus中

2.1 通知input系统更新焦点窗口

在WMS计算更新完焦点窗口之后,需要同步通知给input系统:

1
2
3
4
5
// [2.1] 如果焦点窗口有更新,也需要更新input相关设置
if (focusChanged) {
displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
false /*updateInputWindows*/);
}

这里很自然就有疑问,窗口可大可小,不同的窗口层叠起来,input系统是如何判断分发事件到正确的窗口呢? 接下来我们先看看应用窗口和input的关系。

欢迎关注我的其它发布渠道