Android焦点窗口

概述

何为焦点窗口?顾名思义就是有焦点窗口,要注意和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});
}

至此 ,分析完毕

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值