安卓焦点窗口切换,安卓焦点窗口切换方法详解

马肤

温馨提示:这篇文章已超过459天没有更新,请注意相关的内容是否还可用!

摘要:安卓系统通过焦点窗口切换功能,实现用户在不同应用之间的快速导航。该功能允许用户在不同窗口之间轻松切换,提高操作效率和便捷性。用户可以通过简单的手势或按键操作,快速切换到所需的应用窗口,实现多任务处理。这一功能在智能手机和平板电脑等移动设备上广泛应用,提升了用户体验和工作效率。

一、背景

我们经常会遇到一种Application does not hava focused window的ANR异常,这种异常一般是没有焦点窗口FocusedWindow导致,且这类异常只会发生在key事件的派发,因为key事件是需要找到一个焦点窗口然后再派发,而触摸事件只需要找到当前显示的窗口即可

焦点窗口设定

焦点窗口是指当前正在与用户交互的窗口,该窗口负责接收键事件和触摸事件。当启动新的Activity、添加新的窗口、移除旧窗口、分屏来回操作时,都会涉及到焦点窗口的更新。

安卓焦点窗口切换,安卓焦点窗口切换方法详解 第1张

WMS只管理窗口,无法确定是否有窗口盖住当前画面 SurfaceFlinger管理显示,最贴近于用户看到的画面,可以知道可以知道是否有窗口盖住当前画面,根据真实的显示窗口设置对应的window信息给InputDispatcher

关键日志

1.window

在dumpsys window中查看mCurrentFocus和mFocusedApp,也可以通过如下shell命令来查看当前的FocusWindow:

mCurrentFocus=Window{f96644 u0 NotificationShade}

mFocusedApp=ActivityRecord{e9566ee u0 com.android.launcher3/.uioverrides.QuickstepLauncher} t12}

mCurrentFocus指的是当前的焦点窗口

mFocusedApp指的是当前的焦点Activity

 $ adb shell dumpsys window d | grep "mCurrentFocus" 
 mCurrentFocus=Window{135c912 mode=0 rootTaskId=4 u0 com.example.myapplication/com.example.myapplication.MainActivity}

InputWindow是指能接收input事件的窗口,当WMS中状态发生变化后,会将所有符合条件的窗口设置给底层InputFlinger中,在派发事件时,将对从这些窗口中选择目标窗口进行派发,这些窗口就是InputWindow。焦点窗口只有一个,但InputWindow可以有多个。

2.SurfaceFlinger

在dumpsys SurfaceFlinger中查看 HWC layers

Display 4619827259835644672 (active) HWC layers:
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 Layer name
           Z |  Window Type |  Comp Type |  Transform |   Disp Frame (LTRB) |          Source Crop (LTRB) |     Frame Rate (Explicit) (Seamlessness) [Focused]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 com.example.mysystemdialog/com.example.mysystemdialog.MainActivity#118
  rel      0 |            1 |     CLIENT |          0 |    0    0 1440 2960 |    0.0    0.0 1440.0 2960.0 |                                              [*]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 StatusBar#75
  rel      0 |         2000 |     CLIENT |          0 |    0    0 1440   84 |    0.0    0.0 1440.0   84.0 |                                              [ ]
---------------------------------------------------------------------------------------------------------------------------------------------------------------
 NavigationBar0#74
  rel      0 |         2019 |     CLIENT |          0 |    0 2792 1440 2960 |    0.0    0.0 1440.0  168.0 |                                              [ ]
---------------------------------------------------------------------------------------------------------------------------------------------------------------

[Focused]这一列有带[*]号,则说明是焦点窗口

3.input

在dumpsys input中查看FocusedApplications和FocusedWindows

  FocusedApplications:
    displayId=0, name='ActivityRecord{e9566ee u0 com.android.launcher3/.uioverrides.QuickstepLauncher} t12}', dispatchingTimeout=5000ms
  FocusedWindows:
    displayId=0, name='f96644 NotificationShade'

如果发生ANR,焦点窗口以dumpsys input为主

Input Dispatcher State at time of last ANR:
        ANR:
                Time:......
                Reason:......
                Window:......
        FocusedApplications:......
        FocusedWindows: 

4.eventlog

11-27 16:15:58.902  3932  4137 I input_focus: [Focus request 5e78d93 com.android.mms/com.android.mms.ui.MmsTabActivity,reason=UpdateInputWindows]
11-27 16:15:58.922  3932  6384 I input_focus: [Focus receive :5e78d93 com.android.mms/com.android.mms.ui.MmsTabActivity,reason=setFocusedWindow]
11-27 16:15:59.027  3932  4436 I input_focus: [Focus entering 5e78d93 com.android.mms/com.android.mms.ui.MmsTabActivity (server),reason=Window became focusable. Previous reason: NOT_VISIBLE]

request 和 entering正常情况下是一一对应,打印了entering则表示真正的焦点已经进入到对应的窗口

发生Application does not hava focused window时,一般request 有打印,我们可以通过是否有entering的打印来分析

1.entering部分有打印,代表焦点已经在input里面,但是仍然有ANR,就需要从input等方面分析 2.entering部分未打印,代表input没有被触发焦点窗口设置到input,需排查SurfaceFlinger或WMS

焦点切换三部log打印位置

java层InputMonitor.java的requestFocus(); // "Focus request"

native层InputDispatcher.cpp的setFocusedWindow(),binder线程; // "Focus receive"

native层InputDispatcher.cpp的dispatchFocusLocked(),InputDispatcher线程 // "Focus " + ("entering " : "leaving ")

二、Focused Window的更新

安卓焦点窗口切换,安卓焦点窗口切换方法详解 第2张

启动App时更新inputInfo/请求焦点窗口流程:

App主线程调ViewRootImpl.java的relayoutWindow();然后调用到Wms的relayoutWindow(),窗口布局流程。

 代码路径:framework/services/core/java/com/android/server/wm/WindowManagerService.java

    public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewVisibility, int flags,
            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls, Bundle outSyncIdBundle) {
                ......
        synchronized (mGlobalLock) {
            ......
            if (focusMayChange) {
               if (updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/)) {
                   imMayMove = false;
               }
            }
            ......
        Binder.restoreCallingIdentity(origId);
        //返回result
        return result;
    }

焦点窗口的更新,通过WMS#updateFocusedWindowLocked()方法开始,下面从这个方法开始,看下整个更新流程。

2.1 WMS#updateFocusedWindowLocked()

代码路径:framework/services/core/java/com/android/server/wm/WindowManagerService.java

boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
    // 进入RootWindowContainer中
    boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows);
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    return changed;
}

其中,第一个参数mode表示更新焦点窗口时所处的阶段,共有五个参数:

// 表示正常更新    
static final int UPDATE_FOCUS_NORMAL = 0;    
// 表示此次更新焦点窗口发生在window layer分配之前    
static final int UPDATE_FOCUS_WILL_ASSIGN_LAYERS = 1;    
// 表示此次更新焦点窗口发生在进行放置Surface过程中,在performSurfacePlacement()时    
static final int UPDATE_FOCUS_PLACING_SURFACES = 2;    
// 表示此次更新焦点窗口发生在进行放置Surface之前    
static final int UPDATE_FOCUS_WILL_PLACE_SURFACES = 3;    
// 表示此次更新焦点窗口发生在焦点窗口移除后    
static final int UPDATE_FOCUS_REMOVING_FOCUS = 4;

如在Window添加过程的addWindow()方法中,更新焦点窗口发生在分配窗口layer流程之前,因此使用UPDATE_FOCUS_WILL_ASSIGN_LAYERS作为第一个参数,表示此次更新时,还没有分配layer。 针对不同阶段,会有不同的操作。

第二个参数表示是否同步更新InputWindow,一般在调用的地方会写死

 

2.2 RootWindowContainer#updateFocusedWindowLocked

代码路径:frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

/**
 * Updates the children's focused window and the top focused display if needed.
 */
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
    // 存储了当前焦点窗口的Pid和ActivityRecord的Map映射
    mTopFocusedAppByProcess.clear();
    boolean changed = false;
    int topFocusedDisplayId = INVALID_DISPLAY;
    // Go through the children in z-order starting at the top-most
    // 遍历DisplayContent
    for (int i = mChildren.size() - 1; i >= 0; --i) {
        final DisplayContent dc = mChildren.get(i);
        // 针对每个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();
        }
    }
    if (topFocusedDisplayId == INVALID_DISPLAY) {
        topFocusedDisplayId = DEFAULT_DISPLAY;
    }
    // 更新mTopFocusedDisplayId,表示焦点屏幕
    if (mTopFocusedDisplayId != topFocusedDisplayId) {
        mTopFocusedDisplayId = topFocusedDisplayId;
        mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
        mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
        mWmService.mAccessibilityController.setFocusedDisplay(topFocusedDisplayId);
        ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId);
    }
    return changed;
}

这里会遍历DisplayContent,并在每个DisplayContent中进行更新,然后将更新的结果返回给DisplayContent#mCurrentFocus变量,该变量表示全局的焦点窗口。同时更新mTopFocusedDisplayId变量,表示当前焦点屏(即焦点窗口所在的屏)。

2.3 DisplayContent#updateFocusedWindowLocked

代码路径:frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
        int topFocusedDisplayId) {
    // 不要自动将焦点重新分配离开应该保持焦点的应用程序窗口。
    // findFocusedWindow` 将始终抓取瞬态启动的应用程序,因为它位于“顶部”
    // 会造成不匹配,所以早点出去。
    if (mCurrentFocus != null && mTransitionController.shouldKeepFocus(mCurrentFocus)
            // 这只是保持焦点,所以如果焦点应用程序已被显式更改(例如通过 setFocusedTask),请不要提前退出。
            && mFocusedApp != null && mCurrentFocus.isDescendantOf(mFocusedApp)
            && mCurrentFocus.isVisible() && mCurrentFocus.isFocusable()) {
        ProtoLog.v(WM_DEBUG_FOCUS, "Current transition prevents automatic focus change");
        return false;
    }
    // 寻找焦点窗口
    WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
    if (mCurrentFocus == newFocus) {
        return false;
    }
    boolean imWindowChanged = false;
    final WindowState imWindow = mInputMethodWindow;
    if (imWindow != null) {
        // 更新IME target窗口
        final WindowState prevTarget = mImeLayeringTarget;
        //获取新的IME Target窗口
        final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);
        imWindowChanged = prevTarget != newTarget;
        if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
                && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
            //进行window layer的分配
            assignWindowLayers(false /* setLayoutNeeded */);
        }
        // IME target窗口发生变化,重新获取一次焦点窗口
        if (imWindowChanged) {
            mWmService.mWindowsChanged = true;
            setLayoutNeeded();
            newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
        }
    }
    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 (newFocus != null) {
        // 这两个列表用于记录ANR状态,表示自从焦点窗口为空时添加和移除的窗口
        mWinAddedSinceNullFocus.clear();
        mWinRemovedSinceNullFocus.clear();
        if (newFocus.canReceiveKeys()) {
            // Displaying a window implicitly causes dispatching to be unpaused.
            // This is to protect against bugs if someone pauses dispatching but
            // forgets to resume.
            // 设置焦点窗口所在mToken.paused属性为false
            newFocus.mToken.paused = false;
        }
    }
    getDisplayPolicy().focusChangedLw(oldFocus, newFocus);
    mAtmService.mBackNavigationController.onFocusChanged(newFocus);
    // IME target窗口发生变化,且旧焦点窗口非输入法窗口时
    if (imWindowChanged && oldFocus != mInputMethodWindow) {
        // Focus of the input method window changed. Perform layout if needed.
        if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
            performLayout(true /*initial*/,  updateInputWindows);
        } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
            // Client will do the layout, but we need to assign layers
            // for handleNewWindowLocked() below.
            assignWindowLayers(false /* setLayoutNeeded */);
        }
    }
    // 向InputMonitor中设置焦点窗口
    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);
    }
    // 为IME窗口进行调整
    adjustForImeIfNeeded();
    updateKeepClearAreas();
    // 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.hasCallbacks()) {
        mWmService.mH.sendMessage(PooledLambda.obtainMessage(
                this::updateAccessibilityOnWindowFocusChanged,
                mWmService.mAccessibilityController));
    }
    return true;
}

上述方法中:

  1. 通过findFocusedWindowIfNeeded()方法寻找焦点窗口;

  2. 根据焦点窗口的变化,更新Input Target窗口;

  3. 更新全局焦点窗口对象mCurrentFocus;

  4. 根据更新的不同阶段做不同处理。

  5. 向InputMonitor中设置焦点窗口setInputFocusLw

下面看下如何寻找到焦点窗口。

2.4.DisplayContent#findFocusedWindowIfNeeded()

代码路径: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 (hasOwnFocus() || topFocusedDisplayId == INVALID_DISPLAY)
                ? findFocusedWindow() : null;
}

当topFocusedDisplayId为INVALID_DISPLAY时,认为当前焦点display没有焦点窗口,需要寻找重新确认,所以又继续执行findFocusedWindow()方法寻找

代码路径:frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

/**
 * Find the focused window of this DisplayContent. The search takes the state of the display
 * content into account
 * @return The focused window, null if none was found.
 */
WindowState findFocusedWindow() {
    mTmpWindow = null;
    // 遍历windowstate mFindFocusedWindow会在找到新的焦点窗口时填充 mTmpWindow。
    forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);
    if (mTmpWindow == null) {
        ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: No focusable windows, display=%d",
                getDisplayId());
        return null;
    }
    return mTmpWindow;
}

该方法中,会遍历所有WindowState,然后将寻找到的WindowState赋值给mTmpWindow,并返回给WMS。接下来看下这个mFindFocusedWindow函数接口对象

代码路径:frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

/**
 *用于查找给定窗口的聚焦窗口的 lambda 函数。 
 如果找到聚焦窗口,则lambda返回true,否则返回 false。
 如果找到焦点窗口,它将被存储在mTmpWindow中。
 */
private final ToBooleanFunction mFindFocusedWindow = w -> {
    // 当前处于前台的ActivityRecord
    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 */));
    // 如果窗口无法接收key事件,则不能作为焦点窗口,返回false
    if (!w.canReceiveKeys()) {
        return false;
    }
    // When switching the app task, we keep the IME window visibility for better
    // transitioning experiences.
    // However, in case IME created a child window or the IME selection dialog without
    // dismissing during the task switching to keep the window focus because IME window has
    // higher window hierarchy, we don't give it focus if the next IME layering target
    // doesn't request IME visible.
    if (w.mIsImWindow && w.isChildWindow() && (mImeLayeringTarget == null
            || !mImeLayeringTarget.isRequestedVisible(ime()))) {
        return false;
    }
    if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null
            && !mImeLayeringTarget.isRequestedVisible(ime())
            && !mImeLayeringTarget.isVisibleRequested()) {
        return false;
    }
    final ActivityRecord activity = w.mActivityRecord;
    // 如果前台没有Activity,则此次WindowState将作为焦点窗口返回
    if (focusedApp == null) {
        ProtoLog.v(WM_DEBUG_FOCUS_LIGHT,
                "findFocusedWindow: focusedApp=null using new focus @ %s", w);
        mTmpWindow = w;
        return true;
    }
    // 如果前台Activity是不可获焦的,则此次WindowState将作为焦点窗口返回
    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).
    // 如果当前WindowState由ActivityRecord管理,且非StartingWindow,
    //则当前台Activity在当前WindowState所属Activity之上时,不存在焦点窗口
    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;
        }
        // 如果Activity当前嵌入到焦点任务中,则
        // 除非 Activity 与 FocusedApp 位于同一 TaskFragment 上,否则无法获得焦点。
        TaskFragment parent = activity.getTaskFragment();
        if (parent != null && parent.isEmbedded()) {
            if (activity.getTask() == focusedApp.getTask()
                    && activity.getTaskFragment() != focusedApp.getTaskFragment()) {
                return false;
            }
        }
    }
    // 不满足以上条件,则此次WindowState将作为焦点窗口返回
    ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "findFocusedWindow: Found new focus @ %s", w);
    mTmpWindow = w;
    return true;
};
  1. 如果WindowState不能接收Input事件,则不能作为焦点窗口;

  2. 如果没有前台Activity,则当前WindowState作为焦点窗口返回;

  3. 如果前台Activity是不可获焦状态,则当前WindowState作为焦点窗口返回;

  4. 如果当前WindowState由ActivityRecord管理,且该WindowState不是Staring Window类型,那么当前台Activity在当前WindowState所属Activity之上时,不存在焦点窗口;

  5. 如果Activity当前嵌入到焦点任务中,则除非 Activity与FocusedApp位于同一TaskFragment上,否则无法获得焦点。

  6. 如果以上条件都不满足,则当前WindowState作为焦点窗口返回;

接下来看一下canReceiveKeys这个函数

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowState.java

public boolean canReceiveKeys(boolean fromUserTouch) {
    if (mActivityRecord != null && mTransitionController.shouldKeepFocus(mActivityRecord)) {
     // 在瞬态启动期间,瞬态隐藏窗口不可见
     // 或在顶部,但保持可聚焦,因此可以接收密钥。
        return true;
    }
    // 可见或处于addWindow()~relayout之间
    final boolean canReceiveKeys = isVisibleRequestedOrAdding()
            // 客户端View可见                     // 退出动画执行完毕
            && (mViewVisibility == View.VISIBLE) && !mRemoveOnExit
            // 没有FLAG_NOT_FOCUSABLE标记
            && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0)
             // mActivityRecord为null或者其可获焦
            && (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
            // 可以接受touch事件
            && (mActivityRecord == null || mActivityRecord.getTask() == null
                    || !mActivityRecord.getTask().getRootTask().shouldIgnoreInput());
    if (!canReceiveKeys) {
        return false;
    }
    // 除非用户故意触摸显示器,否则不允许不受信任的虚拟显示器接收密钥。
    return fromUserTouch || getDisplayContent().isOnTop()
            || getDisplayContent().isTrusted();
}

如果一个WindowState可以接受Input事件,需要同时满足多个条件:

  1. isVisibleRequestedOrAdding()方法为true,表示该WindowState可见或处于添加过程中:

  2. mViewVisibility属性为View.VISIBLE,表示客户端View可见;

  3. mRemoveOnExit为false,表示WindowState的退出动画不存在;

  4. mAttrs.flags中不存在FLAG_NOT_FOCUSABLE标记,该标记如果设置,表示该窗口为不可获焦窗口;

  5. mActivityRecord为null或者mActivityRecord可获焦;

  6. shouldIgnoreInput()方法为false,表示可以接受Touch事件。

isVisibleRequestedOrAdding()方法用来判断该WindowState可见或处于添加过程中:

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowState.java

/**
 * Is this window capable of being visible (policy and content), in a visible part of the
 * hierarchy, and, if an activity window, the activity is visible-requested. Note, this means
 * if the activity is going-away, this will be {@code false} even when the window is visible.
 *
 * The 'adding' part refers to the period of time between IWindowSession.add() and the first
 * relayout() -- which, for activities, is the same as visibleRequested.
 *
 * TODO(b/206005136): This is very similar to isVisibleRequested(). Investigate merging them.
 */
boolean isVisibleRequestedOrAdding() {
    final ActivityRecord atoken = mActivityRecord;
    // Surface已经创建,说明relayout()已经执行
    return (mHasSurface 
            // relayout()未执行,且客户端View可见状态
            || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
            // 如果是子窗口,其副窗口的mHidden属性为false
            && isVisibleByPolicy() && !isParentWindowHidden()
            // mActivityRecord为null,或者其mVisibleRequested为true
            && (atoken == null || atoken.isVisibleRequested())
            // 没有执行退出动画   //且Surface没有进行销毁
            && !mAnimatingExit && !mDestroying;
}

如果以上条件都满足,则返回true。

shouldIgnoreInput() 方法用来判断该WindowState是否能够接收touch事件

boolean shouldIgnoreInput() {
    //是否支持靠背功能 是否是固定窗口下 且不是Display上的焦点根任务
    if (mAtmService.mHasLeanbackFeature && inPinnedWindowingMode()
            && !isFocusedRootTaskOnDisplay()) {
        // 防止画中画根任务接收电视上的输入
        return true;
    }
    return false;
}

如果遇到找不到焦点窗口的情况:比如log发现窗口已经是onresume的状态,但是焦点窗口一直未请求切换到此窗口可以查看如下这条log,主要是打印canReceiveKeys为何false的原因(那个属性不对)canReceiveKeysReason此方法

private final ToBooleanFunction 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 */));
    if (!w.canReceiveKeys()) {
    return false;
    ......
    }        
 }

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowState.java

/** Returns {@code true} if this window desires key events. */
boolean canReceiveKeys() {
    return canReceiveKeys(false /* fromUserTouch */);
}
public String canReceiveKeysReason(boolean fromUserTouch) {
    return "fromTouch= " + fromUserTouch
            + " isVisibleRequestedOrAdding=" + isVisibleRequestedOrAdding()
            + " mViewVisibility=" + mViewVisibility
            + " mRemoveOnExit=" + mRemoveOnExit
            + " flags=" + mAttrs.flags
            + " appWindowsAreFocusable="
            + (mActivityRecord == null || mActivityRecord.windowsAreFocusable(fromUserTouch))
            + " canReceiveTouchInput=" + canReceiveTouchInput()
            + " displayIsOnTop=" + getDisplayContent().isOnTop()
            + " displayIsTrusted=" + getDisplayContent().isTrusted()
            + " transitShouldKeepFocus=" + (mActivityRecord != null
                    && mTransitionController.shouldKeepFocus(mActivityRecord));
     
}

然后我们回到mFindFocusedWindow接口对象中,其他条件就不一一说明,最终从DisplayContent中自顶向下寻找到的第一个满足条件的窗口,将作为新的焦点窗口后,接下来更新Display#mCurrentFocus变量,表示当前焦点窗口。

三、InputWindows的更新

安卓焦点窗口切换,安卓焦点窗口切换方法详解 第3张

找到焦点窗口后,回到DisplayContent的updateFocusedWindowLocked方法中继续往下走 执行到此处会进行InputWindows的更新

代码路径:frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
            int topFocusedDisplayId) {
            //..........详细看上面....
            // 向InputMonitor中设置焦点窗口
        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);
        }
            //.........
    }

 

3.1 InputMonitor#updateInputWindowsLw()

代码路径: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);
    //这里log打印要切到那个窗口        
    // 更新当前焦点窗口
    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();
    // 更新所有inputwindow
    if (updateInputWindows) {
        updateInputWindowsLw(false /*force*/);
    }
}

以上过程伴随日志:

WindowManager: Input focus has changed to Window{a44139a u0 NotificationShade} display=0

void setUpdateInputWindowsNeededLw() {
    mUpdateInputWindowsNeeded = true;
}

3.2.InputMonitor#updateInputWindowsLw()

代码路径:frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

/* Updates the cached window information provided to the input dispatcher. */
void updateInputWindowsLw(boolean force) {
    if (!force && !mUpdateInputWindowsNeeded) {
        return;
    }
    scheduleUpdateInputWindows();
}

在该方法中,只有当强制更新或者mUpdateInputWindowsNeeded值为true时(在setUpdateInputWindowsNeededLw设置为true),才会进行InputWindows的更新

之后将执行scheduleUpdateInputWindows()方法,在这个方法中,会post一个Runnable对象mUpdateInputWindows,在mHandler所在的android.anim线程中执行更新流程:

代码路径:frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

private void scheduleUpdateInputWindows() {
    //检查窗口是否被移除
    if (mDisplayRemoved) {
        return;
    }
    //mUpdateInputWindowsPending只是用来保证post执行不被重复执行,配合锁实现
    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,准备执行下一次同步
            mUpdateInputWindowsPending = false;
            mUpdateInputWindowsNeeded = false;
            //没有display的话return
            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();
            // 在默认显示上添加所有窗口。
            mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag);
        }
    }
}
private final UpdateInputWindows mUpdateInputWindows = new UpdateInputWindows();

接下来执行mUpdateInputForAllWindowsConsumer.updateInputWindows()方法。

3.3.mUpdateInputForAllWindowsConsumer.updateInputWindows()

在该方法中,首先会确认是否存在几类特殊的InputConsumer。InputConsumer用于读取事件,每个窗口对应的客户端都会通过InputConsumer来读取和消费事件,一般情况下,ViewRootImpl在添加窗口过程中,会在注册InputEventReceiver时自动创建InputConsumer对象。此处的特殊InputConsumer则是对一些系统UI显式地进行了创建:

代码路径:frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

private void updateInputWindows(boolean inDrag) {
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
    // 显式创建的特殊InputConsumer对象
    // 用于处理Nav相关input事件
    mPipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP);
    // 用于处理壁纸相关input事件
    mWallpaperInputConsumer = getInputConsumer(INPUT_CONSUMER_WALLPAPER);
    // 用于处理最近的动画输入相关input事件
    mRecentsAnimationInputConsumer = getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
    mAddPipInputConsumerHandle = mPipInputConsumer != null;
    mAddWallpaperInputConsumerHandle = mWallpaperInputConsumer != null;
    mAddRecentsAnimationInputConsumerHandle = mRecentsAnimationInputConsumer != null;
    // 重置mInputTransaction
    resetInputConsumers(mInputTransaction);
    // 如果处于活动状态,则更新最近的输入消费者层
    final ActivityRecord activeRecents = getWeak(mActiveRecentsActivity);
    if (mAddRecentsAnimationInputConsumerHandle && activeRecents != null
            && activeRecents.getSurfaceControl() != null
        WindowContainer layer = getWeak(mActiveRecentsLayerRef);
        layer = layer != null ? layer : activeRecents;
        // Handle edge-case for SUW where windows don't exist yet
        if (layer.getSurfaceControl() != null) {
            final WindowState targetAppMainWindow = activeRecents.findMainWindow();
            if (targetAppMainWindow != null) {
                targetAppMainWindow.getBounds(mTmpRect);
                mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect);
            }
            mRecentsAnimationInputConsumer.show(mInputTransaction, layer);
            mAddRecentsAnimationInputConsumerHandle = false;
        }
    }
    // 遍历窗口更新inputInfo
    mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */);
    //调用到Focus Request
    updateInputFocusRequest(mRecentsAnimationInputConsumer);
    // 将mInputTransaction合并到mPendingTransaction上进行提交
    if (!mUpdateInputWindowsImmediately) {
        mDisplayContent.getPendingTransaction().merge(mInputTransaction);
        mDisplayContent.scheduleAnimation();
    }
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}

然后,接mDisplayContent.forAllWindows 将发起所有WindowState的遍历,mUpdateInputForAllWindowsConsumer本身是一个Consumer接口对象,因此会回调accept()方法对每个WindowState进行处理:

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

boolean forAllWindows(ToBooleanFunction 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  

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowState.java

private boolean applyInOrderWithImeWindows(ToBooleanFunction callback,
        boolean traverseTopToBottom) {
    if (traverseTopToBottom) {
        if (applyImeWindowsIfNeeded(callback, traverseTopToBottom)
                || callback.apply(this)) {
            return true;
        }
    } else {
        if (callback.apply(this)
                || applyImeWindowsIfNeeded(callback, traverseTopToBottom)) {
            return true;
        }
    }
    return false;
}

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

@Override
public boolean apply(WindowState w) {
    mConsumer.accept(w);
    return false;
}

代码路径:frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

@Override
public void accept(WindowState w) {
    // 获取WindowState的InputWindowHandle对象
    //WindowState里保存着InputWindowHandle,每次复用,判断changes,减少同步
    final InputWindowHandleWrapper inputWindowHandle = w.mInputWindowHandle;
    //判断窗口mInputChannelToken是否为空;窗口是否销毁;窗口是否可以接受input事件
    if (w.mInputChannelToken == null || w.mRemoved || !w.canReceiveTouchInput()) {
        if (w.mWinAnimator.hasSurface()) {
            // 确保输入信息无法接收输入事件。可以省略
            // 遮挡检测取决于类型或是否是可信覆盖。
            populateOverlayInputInfo(inputWindowHandle, w);
            setInputWindowInfoIfNeeded(mInputTransaction,
                    w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
            return;
        }
        // 跳过此窗口,因为它不可能接收输入。
        return;
    }
    // 最近任务是否存在
    final RecentsAnimationController recentsAnimationController =
            mService.getRecentsAnimationController();
    final boolean shouldApplyRecentsInputConsumer = recentsAnimationController != null
            && recentsAnimationController.shouldApplyInputConsumer(w.mActivityRecord);
    if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
        if (recentsAnimationController.updateInputConsumerForApp(
                mRecentsAnimationInputConsumer.mWindowHandle)) {
            final DisplayArea targetDA =
                    recentsAnimationController.getTargetAppDisplayArea();
            if (targetDA != null) {
                mRecentsAnimationInputConsumer.reparent(mInputTransaction, targetDA);
                mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 2);
                mAddRecentsAnimationInputConsumerHandle = false;
            }
        }
    }
    // 处理处于PIP模式时的input事件
    if (w.inPinnedWindowingMode()) {
        if (mAddPipInputConsumerHandle) {
            final Task rootTask = w.getTask().getRootTask();
            mPipInputConsumer.mWindowHandle.replaceTouchableRegionWithCrop(
                    rootTask.getSurfaceControl());
            final DisplayArea targetDA = rootTask.getDisplayArea();
            // We set the layer to z=MAX-1 so that it's always on top.
            if (targetDA != null) {
                mPipInputConsumer.layout(mInputTransaction, rootTask.getBounds());
                mPipInputConsumer.reparent(mInputTransaction, targetDA);
                mPipInputConsumer.show(mInputTransaction, MAX_VALUE - 1);
                mAddPipInputConsumerHandle = false;
            }
        }
    }
    // mWallpaperInputConsumer处理壁纸input事件
    if (mAddWallpaperInputConsumerHandle) {
        if (w.mAttrs.type == TYPE_WALLPAPER && w.isVisible()) {
            mWallpaperInputConsumer.mWindowHandle
                    .replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
            // Add the wallpaper input consumer above the first visible wallpaper.
            mWallpaperInputConsumer.show(mInputTransaction, w);
            mAddWallpaperInputConsumerHandle = false;
        }
    }
    // 是否处于拖拽过程中
    if (mInDrag && w.isVisible() && w.getDisplayContent().isDefaultDisplay) {
        mService.mDragDropController.sendDragStartedIfNeededLocked(w);
    }
    // 注册密钥拦截信息
    mService.mKeyInterceptionInfoForToken.put(w.mInputChannelToken,
            w.getKeyInterceptionInfo());
    if (w.mWinAnimator.hasSurface()) {
        // 填充InputWindowHandle
        populateInputWindowHandle(inputWindowHandle, w);
        //提交inputWindowHandle
        setInputWindowInfoIfNeeded(mInputTransaction,
                w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
    }
}

以上方法中:

  1. 首先会对几类特殊InputConsumer进行单独处理;

  2. 然后填充InputWindowHandle对象(populateInputWindowHandle);

  3. 最后将InputWindowHandle对象设置给Transaction对象(setInputWindowInfoIfNeeded),并在事物提交后,由SurfaceFlinger设置给InputDispatcher中。

3.4.InputMonitor#populateInputWindowHandle()

在该方法中,会将WindowState的部分属性填充给inputWindowHandle:

代码路径:frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

@VisibleForTesting
void populateInputWindowHandle(final InputWindowHandleWrapper inputWindowHandle,
        final WindowState w) {
    // Add a window to our list of input windows.
    inputWindowHandle.setInputApplicationHandle(w.mActivityRecord != null
            ? w.mActivityRecord.getInputApplicationHandle(false /* update */) : null);
    inputWindowHandle.setToken(w.mInputChannelToken);
    inputWindowHandle.setDispatchingTimeoutMillis(w.getInputDispatchingTimeoutMillis());
    inputWindowHandle.setTouchOcclusionMode(w.getTouchOcclusionMode());
    inputWindowHandle.setPaused(w.mActivityRecord != null && w.mActivityRecord.paused);
    inputWindowHandle.setWindowToken(w.mClient);
    inputWindowHandle.setName(w.getName());
    // Update layout params flags to force the window to be not touch modal. We do this to
    // restrict the window's touchable region to the task even if it requests touches outside
    // its window bounds. An example is a dialog in primary split should get touches outside its
    // window within the primary task but should not get any touches going to the secondary
    // task.
    int flags = w.mAttrs.flags;
    if (w.mAttrs.isModal()) {
        flags = flags | FLAG_NOT_TOUCH_MODAL;
    }
    inputWindowHandle.setLayoutParamsFlags(flags);
    inputWindowHandle.setInputConfigMasked(
            InputConfigAdapter.getInputConfigFromWindowParams(
                    w.mAttrs.type, flags, w.mAttrs.inputFeatures),
            InputConfigAdapter.getMask());
    final boolean focusable = w.canReceiveKeys()
            && (mDisplayContent.hasOwnFocus() || mDisplayContent.isOnTop());
    inputWindowHandle.setFocusable(focusable);
    final boolean hasWallpaper = mDisplayContent.mWallpaperController.isWallpaperTarget(w)
            && !mService.mPolicy.isKeyguardShowing()
            && w.mAttrs.areWallpaperTouchEventsEnabled();
    inputWindowHandle.setHasWallpaper(hasWallpaper);
    // Surface insets are hardcoded to be the same in all directions
    // and we could probably deprecate the "left/right/top/bottom" concept.
    // we avoid reintroducing this concept by just choosing one of them here.
    inputWindowHandle.setSurfaceInset(w.mAttrs.surfaceInsets.left);
    inputWindowHandle.setScaleFactor(w.mGlobalScale != 1f ? (1f / w.mGlobalScale) : 1f);
    
    boolean useSurfaceBoundsAsTouchRegion = false;
    SurfaceControl touchableRegionCrop = null;
    final Task task = w.getTask();
    if (task != null) {
        if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
            // If the window is in a TaskManaged by a TaskOrganizer then most cropping will
            // be applied using the SurfaceControl hierarchy from the Organizer. This means
            // we need to make sure that these changes in crop are reflected in the input
            // windows, and so ensure this flag is set so that the input crop always reflects
            // the surface hierarchy. However, we only want to set this when the client did
            // not already provide a touchable region, so that we don't ignore the one provided.
            if (w.mTouchableInsets != TOUCHABLE_INSETS_REGION) {
                useSurfaceBoundsAsTouchRegion = true;
            }
            if (w.mAttrs.isModal()) {
                TaskFragment parent = w.getTaskFragment();
                touchableRegionCrop = parent != null ? parent.getSurfaceControl() : null;
            }
        } else if (task.cropWindowsToRootTaskBounds() && !w.inFreeformWindowingMode()) {
            touchableRegionCrop = task.getRootTask().getSurfaceControl();
        }
    }
    inputWindowHandle.setReplaceTouchableRegionWithCrop(useSurfaceBoundsAsTouchRegion);
    inputWindowHandle.setTouchableRegionCrop(touchableRegionCrop);
    if (!useSurfaceBoundsAsTouchRegion) {
        w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs);
        inputWindowHandle.setTouchableRegion(mTmpRegion);
    }
}

填充完毕InputWindowHandle后,调用setInputWindowInfoIfNeeded()方法

下面简单看一下InputWindowHandleWrapper这个类

代码路径:frameworks/base/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java

//InputWindowHandle的包装类
class InputWindowHandleWrapper {
    //所有的信息都设置到 mHandle 变量中
    private final @NonNull InputWindowHandle mHandle;
    boolean isChanged() {
        return mChanged;
    }
    //......
    void setTouchableRegion(Region region) {
        if (mHandle.touchableRegion.equals(region)) {
            return;
        }
        mHandle.touchableRegion.set(region);
        mChanged = true;
    }
    void applyChangesToSurface(@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl sc) {
        t.setInputWindowInfo(sc, mHandle);
        mChanged = false;
    }
    //......
}

代码路径:frameworks/base/core/java/android/view/InputWindowHandle.java

public final class InputWindowHandle {
    
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true, value = {
        InputConfig.DEFAULT,
        InputConfig.NO_INPUT_CHANNEL,
        InputConfig.NOT_FOCUSABLE,
        InputConfig.NOT_TOUCHABLE,
        InputConfig.PREVENT_SPLITTING,
        InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER,
        InputConfig.IS_WALLPAPER,
        InputConfig.PAUSE_DISPATCHING,
        InputConfig.TRUSTED_OVERLAY,
        InputConfig.WATCH_OUTSIDE_TOUCH,
        InputConfig.SLIPPERY,
        InputConfig.DISABLE_USER_ACTIVITY,
        InputConfig.SPY,
        InputConfig.INTERCEPTS_STYLUS,
    })
    
    
    public InputApplicationHandle inputApplicationHandle;
    
    
    // The window name.
    public String name;
    
    public long dispatchingTimeoutMillis;
    
    public int frameLeft;
    public int frameTop;
    public int frameRight;
    public int frameBottom;
    
    
    // Window touchable region.
    public final Region touchableRegion = new Region();
    
    
    public int touchOcclusionMode = TouchOcclusionMode.BLOCK_UNTRUSTED;
}

3.5.InputMonitor#setInputWindowInfoIfNeeded()

回到updateInputWindows()方法的,调用setInputWindowInfoIfNeeded将填充好的InputWindowHandle最终通过通过SurfaceFlinger设置给了InputDispatcher

代码路径:frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

@VisibleForTesting
static void setInputWindowInfoIfNeeded(SurfaceControl.Transaction t, SurfaceControl sc,
        InputWindowHandleWrapper inputWindowHandle) {
    if (DEBUG_INPUT) {
        Slog.d(TAG_WM, "Update InputWindowHandle: " + inputWindowHandle);
    }
    if (inputWindowHandle.isChanged()) {
        inputWindowHandle.applyChangesToSurface(t, sc);
    }
}

代码路径:frameworks/base/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java

void applyChangesToSurface(@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl sc) {
    t.setInputWindowInfo(sc, mHandle);
    mChanged = false;
}

代码路径:frameworks/base/core/java/android/view/SurfaceControl.java

/**
 * @hide
 */
public Transaction setInputWindowInfo(SurfaceControl sc, InputWindowHandle handle) {
    //检查条件
    checkPreconditions(sc);
    nativeSetInputWindowInfo(mNativeObject, sc.mNativeObject, handle);
    return this;
}

代码路径:frameworks/base/core/jni/android_view_SurfaceControl.cpp

static void nativeSetInputWindowInfo(JNIEnv* env, jclass clazz, jlong transactionObj,
        jlong nativeObject, jobject inputWindow) {
    auto transaction = reinterpret_cast(transactionObj);
    sp handle = android_view_InputWindowHandle_getHandle(
            env, inputWindow);
    handle->updateInfo();
    auto ctrl = reinterpret_cast(nativeObject);
    transaction->setInputWindowInfo(ctrl, *handle->getInfo());
}

代码路径:frameworks/native/libs/gui/SurfaceComposerClient.cpp

// native层也有对应的surfacecontrol,刚刚封装的WindowInfo也被传递进来

SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setInputWindowInfo(
        const sp& sc, const WindowInfo& info) {
    //获取surfaceControl对应的layer_state_t(surfaceflinger的一个图层)
    layer_state_t* s = getLayerState(sc);
    if (!s) {
        mStatus = BAD_INDEX;
        return *this;
    }
   // 把对应的WindowInfo数据设置相应的参数给layer_state_t,就是surface里面的layer
    s->windowInfoHandle = new WindowInfoHandle(info);
    //在SurfaceFlinger会按照flag来解析改变数据
    s->what |= layer_state_t::eInputInfoChanged;
    return *this;
}

代码路径:frameworks/native/libs/gui/SurfaceComposerClient.cpp

layer_state_t* SurfaceComposerClient::Transaction::getLayerState(const sp& sc) {
    auto handle = sc->getLayerStateHandle();
    if (mComposerStates.count(handle) == 0) {
        // we don't have it, add an initialized layer_state to our list
        ComposerState s;
        s.state.surface = handle;
        s.state.layerId = sc->getLayerId();
        //用于执行apply时,把所有的layer操作一起同步到底层SurfaceFlinger
        mComposerStates[handle] = s;
    }
    return &(mComposerStates[handle].state);
}

后续统一apply(); BpBn调setClientStateLocked()时eInputInfoChanged更新inputInfo;

3.6 InputMonitor#updateInputFocusRequest

再回到InputMonitor#updateInputWindows中

代码路径:frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

private void updateInputWindows(boolean inDrag) {
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
    // 显式创建的特殊InputConsumer对象
    // 用于处理Nav相关input事件
    mPipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP);
    // 用于处理壁纸相关input事件
    mWallpaperInputConsumer = getInputConsumer(INPUT_CONSUMER_WALLPAPER);
    // 用于处理最近的动画输入相关input事件
    mRecentsAnimationInputConsumer = getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
    mAddPipInputConsumerHandle = mPipInputConsumer != null;
    mAddWallpaperInputConsumerHandle = mWallpaperInputConsumer != null;
    mAddRecentsAnimationInputConsumerHandle = mRecentsAnimationInputConsumer != null;
    // 重置mInputTransaction
    resetInputConsumers(mInputTransaction);
    // 如果处于活动状态,则更新最近的输入消费者层
    final ActivityRecord activeRecents = getWeak(mActiveRecentsActivity);
    if (mAddRecentsAnimationInputConsumerHandle && activeRecents != null
            && activeRecents.getSurfaceControl() != null
        WindowContainer layer = getWeak(mActiveRecentsLayerRef);
        layer = layer != null ? layer : activeRecents;
        // Handle edge-case for SUW where windows don't exist yet
        if (layer.getSurfaceControl() != null) {
            final WindowState targetAppMainWindow = activeRecents.findMainWindow();
            if (targetAppMainWindow != null) {
                targetAppMainWindow.getBounds(mTmpRect);
                mRecentsAnimationInputConsumer.mWindowHandle.touchableRegion.set(mTmpRect);
            }
            mRecentsAnimationInputConsumer.show(mInputTransaction, layer);
            mAddRecentsAnimationInputConsumerHandle = false;
        }
    }
    // 遍历窗口更新inputInfo
    mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */);
    //调用到Focus Request
    updateInputFocusRequest(mRecentsAnimationInputConsumer);
    // 将mInputTransaction合并到mPendingTransaction上进行提交
    if (!mUpdateInputWindowsImmediately) {
        mDisplayContent.getPendingTransaction().merge(mInputTransaction);
        mDisplayContent.scheduleAnimation();
    }
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}

接下先来看一下updateInputFocusRequest是如何打印“Focus request ”这条log的

代码路径:frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

/**
 * Called when the current input focus changes.
 */
private void updateInputFocusRequest(InputConsumerImpl recentsAnimationInputConsumer) {
    final WindowState focus = mDisplayContent.mCurrentFocus;
    //......
    final IBinder focusToken = focus != null ? focus.mInputChannelToken : null;
    if (focusToken == null) {
        if (recentsAnimationInputConsumer != null
                && recentsAnimationInputConsumer.mWindowHandle != null
                && mInputFocus == recentsAnimationInputConsumer.mWindowHandle.token) {
            // Avoid removing input focus from recentsAnimationInputConsumer.
            // When the recents animation input consumer has the input focus,
            // mInputFocus does not match to mDisplayContent.mCurrentFocus. Making it to be
            // a special case, that do not remove the input focus from it when
            // mDisplayContent.mCurrentFocus is null. This special case should be removed
            // once recentAnimationInputConsumer is removed.
            return;
        }
        // 当应用程序获得焦点但其窗口尚未显示时,请从当前窗口中删除输入焦点
        // 这强制输入焦点匹配 mDisplayContent.mCurrentFocus
        // 但是,如果发现更多特殊情况,即输入焦点和 mDisplayContent.mCurrentFocus 预计不匹配,
        //则需要检查如何以及何时撤销焦点的整个逻辑。
        if (mDisplayContent.mFocusedApp != null && mInputFocus != null) {
            ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "App %s is focused,"
                    + " but the window is not ready. Start a transaction to remove focus from"
                    + " the window of non-focused apps.",
                    mDisplayContent.mFocusedApp.getName());
            EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Requesting to set focus to null window",
                    "reason=UpdateInputWindows");
            //从具有输入焦点的当前窗口中移除输入焦点。
            //仅当当前聚焦的应用程序没有响应并且当前聚焦的窗口不属于当前聚焦的应用程序时才应调用。        
            mInputTransaction.removeCurrentInputFocus(mDisplayId);
        }
        mInputFocus = null;
        return;
    }
    //如果当前焦点窗口没有surface或者当前窗口无法聚焦则return
    if (!focus.mWinAnimator.hasSurface() || !focus.mInputWindowHandle.isFocusable()) {
        ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus not requested for window=%s"
                + " because it has no surface or is not focusable.", focus);
        mInputFocus = null;
        return;
    }
    requestFocus(focusToken, focus.getName());
}
//将包装出来的InputChannelToken(focusToken)信息向native层进行同步
private void requestFocus(IBinder focusToken, String windowName) {
    if (focusToken == mInputFocus) {
        return;
    }
    mInputFocus = focusToken;
    //输入焦点请求时间  用于anr的计算
    mInputFocusRequestTimeMillis = SystemClock.uptimeMillis();
    mInputTransaction.setFocusedWindow(mInputFocus, windowName, mDisplayId);
    //在这里打印要请求进入的窗口Focus request 
    EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + windowName,
            "reason=UpdateInputWindows");
    ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", windowName);
}

接着看mInputTransaction.setFocusedWindow 这个方法

代码路径:frameworks/base/core/java/android/view/SurfaceControl.java

/**
 *如果窗口可聚焦,则将焦点设置在由输入 {@code token} 标识的窗口上,否则请求将被丢弃。
 *如果窗口不可见,则请求将排队,直到窗口变得可见或该请求被另一个请求覆盖
 *当前聚焦的窗口将立即失去焦点。
 *这是为了向新获得焦点的窗口发送在完成其第一次绘制时发生的任何焦点调度事件
 * @hide
 */
public Transaction setFocusedWindow(@NonNull IBinder token, String windowName,
        int displayId) {
    nativeSetFocusedWindow(mNativeObject, token, windowName, displayId);
    return this;
}

代码路径:frameworks/base/core/jni/android_view_SurfaceControl.cpp

static void nativeSetFocusedWindow(JNIEnv* env, jclass clazz, jlong transactionObj,
                                   jobject toTokenObj, jstring windowNameJstr, jint displayId) {
    auto transaction = reinterpret_cast(transactionObj);
    if (toTokenObj == NULL) return;
    sp toToken(ibinderForJavaObject(env, toTokenObj));
    FocusRequest request;
    request.token = toToken;
    if (windowNameJstr != NULL) {
        ScopedUtfChars windowName(env, windowNameJstr);
        request.windowName = windowName.c_str();
    }
    request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
    request.displayId = displayId;
    //上面就是一些focusToken的异常检测,没问题,会调用下面的方法
    transaction->setFocusedWindow(request);
}

代码路径:frameworks/native/libs/gui/SurfaceComposerClient.cpp

SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFocusedWindow(
        const FocusRequest& request) {
    mInputWindowCommands.focusRequests.push_back(request);
    return *this;
}
SurfaceComposerClient::Transaction&
SurfaceComposerClient::Transaction::addWindowInfosReportedListener(
        sp windowInfosReportedListener) {
    mInputWindowCommands.windowInfosReportedListeners.insert(windowInfosReportedListener);
    return *this;
}

后续统一apply();BpBn调addInputWindowCommands()添加命令;

3.7 merge

再次回到InputMonitor#updateInputWindows()

代码路径:frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java

private void updateInputWindows(boolean inDrag) {
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
    //.....
    // 遍历窗口更新inputInfo
    mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */);
    //调用到Focus Request
    updateInputFocusRequest(mRecentsAnimationInputConsumer);
    // 将mInputTransaction合并到mPendingTransaction上进行提交
    if (!mUpdateInputWindowsImmediately) {
        mDisplayContent.getPendingTransaction().merge(mInputTransaction);
        mDisplayContent.scheduleAnimation();
    }
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}

调用SurfaceControl.Transaction#merge

代码路径:frameworks/base/core/java/android/view/SurfaceControl.java

/**
 * Merge the other transaction into this transaction, clearing the
 * other transaction as if it had been applied.
 *
 * @param other The transaction to merge in to this one.
 * @return This transaction.
 */
@NonNull
public Transaction merge(@NonNull Transaction other) {
    if (this == other) {
        return this;
    }
    mResizedSurfaces.putAll(other.mResizedSurfaces);
    other.mResizedSurfaces.clear();
    mReparentedSurfaces.putAll(other.mReparentedSurfaces);
    other.mReparentedSurfaces.clear();
    nativeMergeTransaction(mNativeObject, other.mNativeObject);
    return this;
}

之后,当WindowAnimator.java的animate()时发起apply();可以是线程"android.anim"或"binder"线程;

四、apply

安卓焦点窗口切换,安卓焦点窗口切换方法详解 第4张

接animate()的openSurfaceTransaction(),prepareSurfaces(),closeSurfaceTransaction()

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowAnimator.java

private void animate(long frameTimeNs) {
    if (!mInitialized) {
        return;
    }
    // Schedule next frame already such that back-pressure happens continuously.
    scheduleAnimation();
    final RootWindowContainer root = mService.mRoot;
    mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
    mBulkUpdateParams = 0;
    root.mOrientationChangeComplete = true;
    if (DEBUG_WINDOW_TRACE) {
        Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
    }
    ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
    mService.openSurfaceTransaction();
    try {
        // Remove all deferred displays, tasks, and activities.
        root.handleCompleteDeferredRemoval();
        final AccessibilityController accessibilityController =
                mService.mAccessibilityController;
        final int numDisplays = root.getChildCount();
        for (int i = 0; i 

0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码