概述
何为焦点窗口?顾名思义就是有焦点窗口,要注意和view的焦点概念要做区分,两个不是一个层面上的概念。为什么窗口要拥有焦点呢,下面都是我的理解,有不对的地方欢迎讨论:焦点窗口主要使用在InputDispachar分发按键事件——keyevent阶段,在keyevent分发阶段,此时的焦点窗口就是该事件应该分发给的窗口。不像触摸事件,在分发过程中可以通过触摸的坐标推断给事件应该分发给那个窗口,keyeven来自物理按键,没有对应的坐标,只能通过焦点窗口确定发送给那个窗口。
解释完毕焦点窗口的作用,在来简单介绍下一种常见的ANR,即no focus anr,这种anr发生就是因为在分发一次keyEvent时,没有找到焦点窗口,便开始了anr计时,5s之后,分发事件的过程中发依旧没有对应的焦点窗口,便会发生无焦点ANR。
焦点窗口的更新流程在Android12之后,涉及到了三个模块:WMS, Surfaceflinger, InputDispachar,下文将会分析具体得人更新焦点窗口流程。
首先明确三个概念:
1. 顶部焦点屏幕:mTopFocusedDisplayId,这个变量的声明位于窗口的根节点RootWindowContainer中.
这个变量代表了响应没有指明屏幕的key或者pointer类型的event的屏幕的id。
frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
// The ID of the display which is responsible for receiving display-unspecified key and pointer
// events.
private int mTopFocusedDisplayId = INVALID_DISPLAY;
2. 焦点app:mFocusedApp代表了一个DisplayContent上正处于前台的ActivityRecord,一般这个ActivityRecord下的窗口将会成为焦点窗口,存在当焦点app属于该ActivityRecord,但是焦点窗口不属于该ActivityRecord下属的窗口的情况。
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
/**
* The foreground app of this display. Windows below this app cannot be the focused window. If
* the user taps on the area outside of the task of the focused app, we will notify AM about the
* new task the user wants to interact with.
*/
ActivityRecord mFocusedApp = null;
3. 焦点窗口:mCurrentFocus,当前正在与用户交互的窗口。此窗口负责接收来自用户的键事件和指针事件。
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
/**
/**
* Window that is currently interacting with the user. This window is responsible for receiving
* key events and pointer events from the user.
*/
WindowState mCurrentFocus = null;
mTopFocusedDisplayId定义在根节点RootWindowContainer中,这表明整个Android系统中只能有一个顶部焦点屏幕,mFocusedApp和mCurrentFocus定义在DisplayContent中,表明每个屏幕都有自己的焦点app和焦点窗口。
在WindowState的构建过程中,会构建出一个类型为InputWindowHandle的对象,该对象的主要作用是保存窗口的一些信息,随后发送给InputDispatcher,InputWindowHandle在input系代表该应用窗口的对象,用于识别以及分发事件。
先来总结一下焦点窗口的产生过程,这个流程设计到WMS,surfaceflinger,InputDispatcher:焦点窗口的生成过程可以说是一个选拔,选送的过程,在窗口发生一些改变的时候会触发到wms去
frameworks/base/services/core/java/com/android/server/wm/WindowState.java
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
PowerManagerWrapper powerManagerWrapper) {
super(service);
mTmpTransaction = service.mTransactionFactory.get();
mSession = s;
mClient = c;
mAppOp = appOp;
mToken = token;
mActivityRecord = mToken.asActivityRecord();
mOwnerUid = ownerId;
mShowUserId = showUserId;
mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
mWindowId = new WindowId(this);
mAttrs.copyFrom(a);
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
mViewVisibility = viewVisibility;
mPolicy = mWmService.mPolicy;
mContext = mWmService.mContext;
DeathRecipient deathRecipient = new DeathRecipient();
mPowerManagerWrapper = powerManagerWrapper;
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
// 如下:
mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
mActivityRecord != null
? mActivityRecord.getInputApplicationHandle(false /* update */) : null,
getDisplayId()));
mInputWindowHandle.setOwnerPid(s.mPid);
mInputWindowHandle.setOwnerUid(s.mUid);
mInputWindowHandle.setName(getName());
mInputWindowHandle.setPackageName(mAttrs.packageName);
mInputWindowHandle.setLayoutParamsType(mAttrs.type);
frameworks/base/services/core/java/com/android/server/wm/WindowState.java
/**
* Functions as a handle for a window that can receive input.
* Enables the native input dispatcher to refer indirectly to the window manager's window state.
* @hide
*/
public final class InputWindowHandle {
// Pointer to the native input window handle.
// This field is lazily initialized via JNI.
@SuppressWarnings("unused")
private long ptr;
// The input application handle.
public InputApplicationHandle inputApplicationHandle;
// The token associates input data with a window and its input channel. The client input
// channel and the server input channel will both contain this token.
public IBinder token;
// The window name.
public String name;
// Window layout params attributes. (WindowManager.LayoutParams)
public int layoutParamsFlags;
public int layoutParamsType;
// Dispatching timeout.
public long dispatchingTimeoutMillis;
// Window frame.
public int frameLeft;
public int frameTop;
public int frameRight;
public int frameBottom;
public int surfaceInset;
// Global scaling factor applied to touch events when they are dispatched
// to the window
public float scaleFactor;
frameworks/base/core/java/android/view/InputApplicationHandle.java
/**
* Functions as a handle for an application that can receive input.
* Enables the native input dispatcher to refer indirectly to the window manager's
* application window token.
* @hide
*/
public final class InputApplicationHandle {
// Pointer to the native input application handle.
// This field is lazily initialized via JNI.
@SuppressWarnings("unused")
private long ptr;
// Application name.
public final @NonNull String name;
// Dispatching timeout.
public final long dispatchingTimeoutMillis;
public final @NonNull IBinder token;
private native void nativeDispose();
public InputApplicationHandle(@NonNull IBinder token, @NonNull String name,
long dispatchingTimeoutMillis) {
this.token = token;
this.name = name;
this.dispatchingTimeoutMillis = dispatchingTimeoutMillis;
}
public InputApplicationHandle(InputApplicationHandle handle) {
this.token = handle.token;
this.dispatchingTimeoutMillis = handle.dispatchingTimeoutMillis;
this.name = handle.name;
}
@Override
protected void finalize() throws Throwable {
try {
nativeDispose();
} finally {
super.finalize();
}
}
}
一.WMS端刷新流程
在framework中,触发焦点窗口更新的流程的入口是WMS.updateFocusedWindowLocked,触发该函数的位置在fw中非常的多,比如在窗口布局过程中,窗口添加过程中,窗口的移除过程中:
updateFocusedWindowLocked
可见该函数的参数有两个,第一个参数代表更新焦点的场景,如下,共5个场景,可以简单代表调用updateFocusedWindowLocked的场景,第二个参数代表在更新焦点窗口的过程是否顺道更新一下InputWindows信息。
在wms的updateFocusedWindowLocked方法中进一步调用了RootWindowContainer的updateFocusedWindowLocked
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
static final int UPDATE_FOCUS_NORMAL = 0;
/** Caller will assign layers */
static final int UPDATE_FOCUS_WILL_ASSIGN_LAYERS = 1;
/** Caller is performing surface placement */
static final int UPDATE_FOCUS_PLACING_SURFACES = 2;
/** Caller will performSurfacePlacement */
static final int UPDATE_FOCUS_WILL_PLACE_SURFACES = 3;
/** Indicates we are removing the focused window when updating the focus. */
static final int UPDATE_FOCUS_REMOVING_FOCUS = 4;
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;
}
在RootWindowContainer方法中可见随即遍历了每一个DisplayContent,开始选拔每个DisplayContent上的焦点窗口:dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);
todo 去描述一下topFocusedDisplayId的刷新过程
frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
mTopFocusedAppByProcess.clear();
boolean changed = false;
// 先初始化一下topFocusedDisplayId
int topFocusedDisplayId = INVALID_DISPLAY;
遍历RootWindowContainer下的每一个DisplayContent
for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayContent dc = mChildren.get(i);
// 调用每一DisplayContent上的updateFocusedWindowLocked,去更新每一个DisplayContent上的焦点窗口
changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);
final WindowState newFocus = dc.mCurrentFocus;
if (newFocus != null) {
final int pidOfNewFocus = newFocus.mSession.mPid;
if (mTopFocusedAppByProcess.get(pidOfNewFocus) == null) {
mTopFocusedAppByProcess.put(pidOfNewFocus, newFocus.mActivityRecord);
}
if (topFocusedDisplayId == INVALID_DISPLAY) {
topFocusedDisplayId = dc.getDisplayId();
}
} else if (topFocusedDisplayId == INVALID_DISPLAY && dc.mFocusedApp != null) {
// The top-most display that has a focused app should still be the top focused
// display even when the app window is not ready yet (process not attached or
// window not added yet).
topFocusedDisplayId = dc.getDisplayId();
}
}
// 选了一圈下来,如果topFocusedDisplayId == INVALID_DISPLAY,就把topFocusedDisplayId = DEFAULT_DISPLAY
if (topFocusedDisplayId == INVALID_DISPLAY) {
topFocusedDisplayId = DEFAULT_DISPLAY;
}
if (mTopFocusedDisplayId != topFocusedDisplayId) {
mTopFocusedDisplayId = topFocusedDisplayId;
mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId);
}
return changed;
}
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
/**
* Update the focused window and make some adjustments if the focus has changed.
*
* @param mode Indicates the situation we are in. Possible modes are:
* {@link WindowManagerService#UPDATE_FOCUS_NORMAL},
* {@link WindowManagerService#UPDATE_FOCUS_PLACING_SURFACES},
* {@link WindowManagerService#UPDATE_FOCUS_WILL_PLACE_SURFACES},
* {@link WindowManagerService#UPDATE_FOCUS_REMOVING_FOCUS}
* @param updateInputWindows Whether to sync the window information to the input module.
* @param topFocusedDisplayId Display id of current top focused display.
* @return {@code true} if the focused window has changed.
*/
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
int topFocusedDisplayId) {
WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
if (mCurrentFocus == newFocus) {
// add by sd. fix not trust virtual display binder leak.
if (newFocus == null && !isTrusted()) {
mWinRemovedSinceNullFocus.clear();
mWinAddedSinceNullFocus.clear();
}
// end
return false;
}
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;
if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
// If we defer assigning layers, then the caller is responsible for doing this part.
getInputMonitor().setInputFocusLw(newFocus, updateInputWindows);
}
adjustForImeIfNeeded();
// We may need to schedule some toast windows to be removed. The toasts for an app that
// does not have input focus are removed within a timeout to prevent apps to redress
// other apps' UI.
scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);
if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
}
// Notify the accessibility manager for the change so it has the windows before the newly
// focused one starts firing events.
// TODO(b/151179149) investigate what info accessibility service needs before input can
// dispatch focus to clients.
if (mWmService.mAccessibilityController != null) {
mWmService.mH.sendMessage(PooledLambda.obtainMessage(
this::updateAccessibilityOnWindowFocusChanged,
mWmService.mAccessibilityController));
}
mLastFocus = mCurrentFocus;
return true;
}
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
/**
* Looking for the focused window on this display if the top focused display hasn't been
* found yet (topFocusedDisplayId is INVALID_DISPLAY) or per-display focused was allowed.
*
* @param topFocusedDisplayId Id of the top focused display.
* @return The focused window or null if there isn't any or no need to seek.
*/
WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {
return (mWmService.mPerDisplayFocusEnabled || topFocusedDisplayId == INVALID_DISPLAY)
? findFocusedWindow() : null;
}
WindowState findFocusedWindow() {
mTmpWindow = null;
forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);
if (mTmpWindow == null) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: No focusable windows, display=%d",
getDisplayId());
return null;
}
return mTmpWindow;
}
如下会从上到下遍历该DisplayContent上的所有WindowState,如果找到符合条件的窗口,那么该窗口,则将该窗口保存在mTmpWindow,并返回ture。因为是从上往下遍历,所以层级越高的窗口越有机会成为焦点窗口。
首先不满足canReceiveKeys条件的是不能成为焦点窗口的.其他成为焦点窗口的条件不赘述。
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
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 */));
// 焦点窗口首先要满足 canReceiveKeys,否则无法成为焦点窗口。
if (!w.canReceiveKeys()) {
return false;
}
// 输入法相关,暂时不分析
if (w.mIsImWindow && w.isChildWindow() && (mImeLayeringTarget == null
|| !mImeLayeringTarget.getRequestedVisibility(ITYPE_IME))) {
return false;
}
if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null
&& !mImeLayeringTarget.getRequestedVisibility(ITYPE_IME)
&& mImeLayeringTarget.isAnimating(PARENTS | TRANSITION,
ANIMATION_TYPE_APP_TRANSITION)) {
return false;
}
// 获取当前窗口所属的ActivityRecord
final ActivityRecord activity = w.mActivityRecord;
//
if (focusedApp == null) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
"findFocusedWindow: focusedApp=null using new focus @ %s", w);
mTmpWindow = w;
return true;
}
if (!focusedApp.windowsAreFocusable()) {
// Current focused app windows aren't focusable...
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: focusedApp windows not"
+ " focusable using new focus @ %s", w);
mTmpWindow = w;
return true;
}
// Descend through all of the app tokens and find the first that either matches
// win.mActivityRecord (return win) or mFocusedApp (return null).
//如果当前所遍历到的窗口的层级位于focusedApp之下且该窗口属于activity的窗口,则把焦点窗口置为null。然后返回。
if (activity != null && w.mAttrs.type != TYPE_APPLICATION_STARTING) {
if (focusedApp.compareTo(activity) > 0) {
// App root task below focused app root task. No focus for you!!!
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
"findFocusedWindow: Reached focused app=%s", focusedApp);
mTmpWindow = null;
return true;
}
}
// 以上条件都不符合
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: Found new focus @ %s", w);
mTmpWindow = w;
return true;
};
canReceiveKeys == true的条件
frameworks/base/services/core/java/com/android/server/wm/WindowState.java
boolean canReceiveKeys() {
return canReceiveKeys(false /* fromUserTouch */);
}
public String canReceiveKeysReason(boolean fromUserTouch) {
return "fromTouch= " + fromUserTouch
+ " isVisibleOrAdding=" + isVisibleOrAdding()
+ " mViewVisibility=" + mViewVisibility
+ " mRemoveOnExit=" + mRemoveOnExit
+ " flags=" + mAttrs.flags
+ " appWindowsAreFocusable="
+ (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
+ " canReceiveTouchInput=" + canReceiveTouchInput()
+ " displayIsOnTop=" + getDisplayContent().isOnTop()
+ " displayIsTrusted=" + getDisplayContent().isTrusted();
}
public boolean canReceiveKeys(boolean fromUserTouch) {
final boolean canReceiveKeys = isVisibleOrAdding()
&& (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
&& (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
&& canReceiveTouchInput();
if (!canReceiveKeys) {
return false;
}
// Do not allow untrusted virtual display to receive keys unless user intentionally
// touches the display.
return fromUserTouch || getDisplayContent().isOnTop()
|| getDisplayContent().isTrusted();
}
@Override
public boolean canShowWhenLocked() {
final boolean showBecauseOfActivity =
mActivityRecord != null && mActivityRecord.canShowWhenLocked();
final boolean showBecauseOfWindow = (getAttrs().flags & FLAG_SHOW_WHEN_LOCKED) != 0;
return showBecauseOfActivity || showBecauseOfWindow;
}
/**
* @return {@code true} if this window can receive touches based on among other things,
* windowing state and recents animation state.
**/
boolean canReceiveTouchInput() {
if (mActivityRecord == null || mActivityRecord.getTask() == null) {
return true;
}
return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
&& mActivityRecord.mVisibleRequested
&& !isRecentsAnimationConsumingAppInput();
}
回到DisplayCpntent.updateFocusedWindowLocked
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
/**
* Update the focused window and make some adjustments if the focus has changed.
*
* @param mode Indicates the situation we are in. Possible modes are:
* {@link WindowManagerService#UPDATE_FOCUS_NORMAL},
* {@link WindowManagerService#UPDATE_FOCUS_PLACING_SURFACES},
* {@link WindowManagerService#UPDATE_FOCUS_WILL_PLACE_SURFACES},
* {@link WindowManagerService#UPDATE_FOCUS_REMOVING_FOCUS}
* @param updateInputWindows Whether to sync the window information to the input module.
* @param topFocusedDisplayId Display id of current top focused display.
* @return {@code true} if the focused window has changed.
*/
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
int topFocusedDisplayId) {
// 此时的newFocus为找到的新焦点窗口
WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
// 如该Displaycontent上的焦点窗口没有发生变化,则不用去更新,返回
if (mCurrentFocus == newFocus) {
// add by sd. fix not trust virtual display binder leak.
if (newFocus == null && !isTrusted()) {
mWinRemovedSinceNullFocus.clear();
mWinAddedSinceNullFocus.clear();
}
// end
return false;
}
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;
// 如果更新模式不是UPDATE_FOCUS_WILL_ASSIGN_LAYERS,则把刚才选举出的焦点窗口设置给getInputMonitor,随后会交给surfaceflinger。
if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
// If we defer assigning layers, then the caller is responsible for doing this part.
getInputMonitor().setInputFocusLw(newFocus, updateInputWindows);
}
.....
mLastFocus = mCurrentFocus;
return true;
}
frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Input focus has changed to %s display=%d",
newWindow, mDisplayId);
final IBinder focus = newWindow != null ? newWindow.mInputChannelToken : null;
// 没有发生改变 返回
if (focus == mInputFocus) {
return;
}
if (newWindow != null && newWindow.canReceiveKeys()) {
// Displaying a window implicitly causes dispatching to be unpaused.
// This is to protect against bugs if someone pauses dispatching but
// forgets to resume.
newWindow.mToken.paused = false;
}
// 设置 mUpdateInputWindowsNeeded = true;
setUpdateInputWindowsNeededLw();
if (updateInputWindows) {
updateInputWindowsLw(false /*force*/);
}
}
frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
/* Updates the cached window information provided to the input */
void updateInputWindowsLw(boolean force) {
if (!force && !mUpdateInputWindowsNeeded) {
return;
}
scheduleUpdateInputWindows();
}
private void scheduleUpdateInputWindows() {
if (mDisplayRemoved) {
return;
}
if (!mUpdateInputWindowsPending) {
mUpdateInputWindowsPending = true;
mHandler.post(mUpdateInputWindows);
}
}
frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
private class UpdateInputWindows implements Runnable {
@Override
public void run() {
synchronized (mService.mGlobalLock) {
mUpdateInputWindowsPending = false;
mUpdateInputWindowsNeeded = false;
if (mDisplayRemoved) {
return;
}
// Populate the input window list with information about all of the windows that
// could potentially receive input.
// As an optimization, we could try to prune the list of windows but this turns
// out to be difficult because only the native code knows for sure which window
// currently has touch focus.
// If there's a drag in flight, provide a pseudo-window to catch drag input
final boolean inDrag = mService.mDragDropController.dragDropActiveLocked();
// Add all windows on the default display.
mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag);
}
}
}
frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
private void updateInputWindows(boolean inDrag) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
mPipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP);
mWallpaperInputConsumer = getInputConsumer(INPUT_CONSUMER_WALLPAPER);
mRecentsAnimationInputConsumer = getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
mAddPipInputConsumerHandle = mPipInputConsumer != null;
mAddWallpaperInputConsumerHandle = mWallpaperInputConsumer != null;
mAddRecentsAnimationInputConsumerHandle = mRecentsAnimationInputConsumer != null;
mDisableWallpaperTouchEvents = false;
mInDrag = inDrag;
resetInputConsumers(mInputTransaction);
// 遍历对应屏幕上的所有WindowState,更新他们的InputWindowHandle,随后发送给SurfaceFlinger
mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */);
// 更新焦点窗口信息给SurfaceFlinger
updateInputFocusRequest(mRecentsAnimationInputConsumer);
if (!mUpdateInputWindowsImmediately) {
mDisplayContent.getPendingTransaction().merge(mInputTransaction);
mDisplayContent.scheduleAnimation();
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
更新所有窗口的InputWindowHandle信息
public void accept(WindowState w) {
......
if (w.mWinAnimator.hasSurface()) {
//更新该窗口的inputWindowHandle信息
populateInputWindowHandle(inputWindowHandle, w);
// 将更新后的inputWindowHandle发送给Surfaceflinger
setInputWindowInfoIfNeeded(mInputTransaction,
w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
}
}
}
更新所有焦点窗口
private void updateInputFocusRequest(InputConsumerImpl recentsAnimationInputConsumer) {
final WindowState focus = mDisplayContent.mCurrentFocus;
......
requestFocus(focusToken, focus.getName());
}
}
private void requestFocus(IBinder focusToken, String windowName) {
if (focusToken == mInputFocus) {
return;
}
mInputFocus = focusToken;
mInputTransaction.setFocusedWindow(mInputFocus, windowName, mDisplayId);
EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + windowName,
"reason=UpdateInputWindows");
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", windowName);
}
二.SurfaceFlinger端刷新流程
SurfaceFlinger中省略了处理Trasnaction的流程,直接分析SurfaceFlinger向InputDispatcher更新layer的信息和焦点信息.
1.收集windowInfos 遍历SurfaceFlinger 当前绘制的Layer集合mDrawingState,当对应的layer有inputChannel时把该layer的 信息封装后 保存在 windowInfos. 收集 所有的display信息,添加到 displayInfos中.
2.更新 WindowInfo等信息 到相关的监听,InputDispatcher 便是其中的监听之一
3.更新 FocusedWindow信息至 inputFlinger.
void SurfaceFlinger::updateInputFlinger() {
ATRACE_CALL();
if (!mInputFlinger) {
return;
}
std::vector<WindowInfo> windowInfos;
std::vector<DisplayInfo> displayInfos;
bool updateWindowInfo = false;
// 只有当有mVisibleRegionsDirty || mInputInfoChanged变化的之后才去收集 windowInfos 和displayInfos
if (mVisibleRegionsDirty || mInputInfoChanged) {
mInputInfoChanged = false;
updateWindowInfo = true;
// 1.收集windowInfos 遍历SurfaceFlinger 当前绘制的Layer集合mDrawingState,当对应的layer有inputChannel时
// 把该layer的 信息封装后 保存在 windowInfos
// 2.收集 所有的display信息,添加到 displayInfos中
buildWindowInfos(windowInfos, displayInfos);
}
// 当 上层 有请求 焦点窗口时, mInputWindowCommands 不为空
if (!updateWindowInfo && mInputWindowCommands.empty()) {
return;
}
BackgroundExecutor::getInstance().sendCallbacks({[updateWindowInfo,
windowInfos = std::move(windowInfos),
displayInfos = std::move(displayInfos),
inputWindowCommands =
std::move(mInputWindowCommands),
inputFlinger = mInputFlinger, this]() {
ATRACE_NAME("BackgroundExecutor::updateInputFlinger");
if (updateWindowInfo) {
// 更新 WindowInfo等信息 到相关的监听,InputDispatcher 便是其中的监听之一
mWindowInfosListenerInvoker->windowInfosChanged(windowInfos, displayInfos,
inputWindowCommands.syncInputWindows);
} else if (inputWindowCommands.syncInputWindows) { // syncInputWindows 在特殊情况下为 false,普通更新信息 都为false
// If the caller requested to sync input windows, but there are no
// changes to input windows, notify immediately.
windowInfosReported();
}
// 更新 FocusedWindow信息至 inputFlinger,当上层有请求新的焦点窗口时 inputWindowCommands.focusRequests 就有值
for (const auto& focusRequest : inputWindowCommands.focusRequests) {
inputFlinger->setFocusedWindow(focusRequest);
}
}});
mInputWindowCommands.clear();
}
至此Surfaceflinger的相关分析完毕.
三.InputFlinger端焦点刷新流程
1.更新InputFlinger端的InputWindowInfo信息
1.更新Display信息到 mDisplayInfos
2.更新 WindowInfoHandle 到 mWindowHandlesByDisplay中
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::onWindowInfosChanged(const gui::WindowInfosUpdate& update) {
// The listener sends the windows as a flattened array. Separate the windows by display for
// more convenient parsing.
std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;
for (const auto& info : update.windowInfos) {
handlesPerDisplay.emplace(info.displayId, std::vector<sp<WindowInfoHandle>>());
handlesPerDisplay[info.displayId].push_back(sp<WindowInfoHandle>::make(info));
}
{ // acquire lock
std::scoped_lock _l(mLock);
// Ensure that we have an entry created for all existing displays so that if a displayId has
// no windows, we can tell that the windows were removed from the display.
for (const auto& [displayId, _] : mWindowHandlesByDisplay) {
handlesPerDisplay[displayId];
}
// 更新Display信息到 mDisplayInfos
mDisplayInfos.clear();
for (const auto& displayInfo : update.displayInfos) {
mDisplayInfos.emplace(displayInfo.displayId, displayInfo);
}
// 将会更新 WindowInfoHandle 到 mWindowHandlesByDisplay中
for (const auto& [displayId, handles] : handlesPerDisplay) {
setInputWindowsLocked(handles, displayId);
}
if (update.vsyncId < mWindowInfosVsyncId) {
ALOGE("Received out of order window infos update. Last update vsync id: %" PRId64
", current update vsync id: %" PRId64,
mWindowInfosVsyncId, update.vsyncId);
}
mWindowInfosVsyncId = update.vsyncId;
}
// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();
}
2.更新InputFlinger端的焦点信息点窗口:
该过程中主要做了两件事:
- 在更新 屏幕–焦点map,返回更新结果的结构体,
struct FocusChanges { sp<IBinder> oldFocus; sp<IBinder> newFocus; int32_t displayId; std::string reason;
可见该结构体包含原始焦点信息,更新的焦点窗口信息,屏幕id,焦点改变原因。 - 把更新结果通知到窗口端
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::setFocusedWindow(const FocusRequest& request) {
{ // acquire lock
std::scoped_lock _l(mLock);
// 去更新对应屏幕上的焦点信息,并返回更新结果的结构体
std::optional<FocusResolver::FocusChanges> changes =
mFocusResolver.setFocusedWindow(request, getWindowHandlesLocked(request.displayId));
// change不为空,说明更新成功,接下把更新结果通知到窗口端
if (changes) {
onFocusChangedLocked(*changes);
}
} // release lock
// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();
}
2.1 在更新 屏幕–焦点map,返回更新结果的结构体
frameworks/native/services/inputflinger/dispatcher/FocusResolver.cpp
std::optional<FocusResolver::FocusChanges> FocusResolver::setFocusedWindow(
const FocusRequest& request, const std::vector<sp<InputWindowHandle>>& windows) {
const int32_t displayId = request.displayId;
const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
if (currentFocus == request.token) {
ALOGD_IF(DEBUG_FOCUS,
"setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused",
request.windowName.c_str(), displayId);
return std::nullopt;
}
// request.focusedToken 一般为null,跳过
if (request.focusedToken) {
if (currentFocus != request.focusedToken) {
ALOGW("setFocusedWindow %s on display %" PRId32
" ignored, reason: focusedToken %s is not focused",
request.windowName.c_str(), displayId, request.focusedWindowName.c_str());
return std::nullopt;
}
// 判断新请求的窗口是否可以成为焦点窗口
Focusability result = isTokenFocusable(request.token, windows);
if (result == Focusability::OK) {
return updateFocusedWindow(displayId, "setFocusedWindow with focus check",
request.token, request.windowName);
}
ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason: %s",
request.windowName.c_str(), displayId, NamedEnum::string(result).c_str());
return std::nullopt;
}
Focusability result = isTokenFocusable(request.token, windows);
// Update focus request. The focus resolver will always try to handle this request if there is
// no focused window on the display.
mFocusRequestByDisplay[displayId] = request;
mLastFocusResultByDisplay[displayId] = result;
if (result == Focusability::OK) {
return updateFocusedWindow(displayId, "setFocusedWindow", request.token,
request.windowName);
}
// The requested window is not currently focusable. Wait for the window to become focusable
// but remove focus from the current window so that input events can go into a pending queue
// and be sent to the window when it becomes focused.
return updateFocusedWindow(displayId, "Waiting for window because " + NamedEnum::string(result),
nullptr);
}
如下将会更新对应屏幕上的焦点窗口:
在更新完 屏幕–焦点map后,返回更新结果的结构体,struct FocusChanges { sp<IBinder> oldFocus; sp<IBinder> newFocus; int32_t displayId; std::string reason;
可见该结构体包含原始焦点信息,更新的焦点窗口信息,屏幕id,焦点改变原因。
frameworks/native/services/inputflinger/dispatcher/FocusResolver.cpp
std::optional<FocusResolver::FocusChanges> FocusResolver::updateFocusedWindow(
int32_t displayId, const std::string& reason, const sp<IBinder>& newFocus,
const std::string& tokenName) {
//获取对应屏幕上的旧的焦点信息
sp<IBinder> oldFocus = getFocusedWindowToken(displayId);
// 没有发生变化 返回
if (newFocus == oldFocus) {
return std::nullopt;
}
if (newFocus) {
// 如果设置的新焦点不为空,则更新 屏幕--焦点map
mFocusedWindowTokenByDisplay[displayId] = {tokenName, newFocus};
} else {
// 如果设置的新焦点为空,则删除屏幕--焦点map中该屏幕的焦点信息
mFocusedWindowTokenByDisplay.erase(displayId);
}
/* 返回struct FocusChanges {
sp<IBinder> oldFocus;
sp<IBinder> newFocus;
int32_t displayId;
std::string reason;
};结构体*/
return {{oldFocus, newFocus, displayId, reason}};
}
2.2 把更新结果通知到窗口端
如上当InputFlinger的焦点窗口完成改变时将会调用到该函数:
- 生成焦点离开旧的焦点窗口的生成,随后该事件会发给旧的焦点窗口。
- 生成焦点进入新的窗口的事件,这个事件稍后会发送给新的焦点窗口。
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& changes) {
if (changes.oldFocus) {
// 如果旧的焦点窗口的InputChannel还可用
std::shared_ptr<InputChannel> focusedInputChannel = getInputChannelLocked(changes.oldFocus);
if (focusedInputChannel) {
CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
"focus left window");
synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);
// 入队一个 焦点从旧的窗口撤出的事件,这个事件稍后会发送给旧的焦点窗口
enqueueFocusEventLocked(changes.oldFocus, false /*hasFocus*/, changes.reason);
}
}
if (changes.newFocus) {
// 新的焦点窗口不为空,入队一个 焦点进入新的窗口的事件,这个事件稍后会发送给新的焦点窗口
enqueueFocusEventLocked(changes.newFocus, true /*hasFocus*/, changes.reason);
}
发送InputDiapatchar端焦点改变的事件给应用端,紧接着会打印对应的event log:
焦点撤出: input_focus: [Focus leaving 7d52ce4 NotificationShade (server),reason=setFocusedWindow]
焦点进入: input_foc: [Focus entering 106fc21 com.android.settings/com.android.settings.Settings$ConnectedDeviceDashboardActivity (server),reason=setFocusedWind
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
void InputDispatcher::(nsecs_t currentTime, std::shared_ptr<FocusEntry> entry) {
std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken);
if (channel == nullptr) {
return; // Window has gone away
}
InputTarget target;
target.inputChannel = channel;
target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
entry->dispatchInProgress = true;
std::string message = std::string("Focus ") + (entry->hasFocus ? "entering " : "leaving ") +
channel->getName();
std::string reason = std::string("reason=").append(entry->reason);
// 打印event log
android_log_event_list(LOGTAG_INPUT_FOCUS) << message << reason << LOG_ID_EVENTS;
// 分发焦点事件
dispatchEventLocked(currentTime, entry, {target});
}
至此 ,分析完毕