InputMonitor注意事项

文章只记录自己的点点理解。为了你自己的参考。

1、mInputFocus

WMS.addWindow()-->WMS.finishUpdateFocusedWindowAfterAssignLayersLocked()-->InputMonitor.setInputFocusLw()-->mInputFocus = newWindow;

add一个window的时候会又一次寻找焦点窗体。并把焦点窗体保存在WMS.mCurrentFocus中。这个焦点窗体也会保存到InputMonitor.mInputFocus 中。关键代码:

<span style="font-size:18px;">addWindow(){
            ……
            boolean focusChanged = false;
            if (win.canReceiveKeys()) {
                focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                        false /*updateInputWindows*/);
                if (focusChanged) {
                    imMayMove = false;
                }
            }

            if (imMayMove) {
                moveInputMethodWindowsIfNeededLocked(false);
            }

            assignLayersLocked(displayContent.getWindowList());
            // Don't do layout here, the window must call
            // relayout to be displayed, so we'll do it there.

            if (focusChanged) {
                finishUpdateFocusedWindowAfterAssignLayersLocked(false /*updateInputWindows*/);
            }
            mInputMonitor.updateInputWindowsLw(false /*force*/);
           …………
}
</span>
这几行代码虽短,逻辑也非常easy,可是调用了非常多方法。对于canReceiveKeys()函数:

<span style="font-size:18px;">public final boolean canReceiveKeys() {
        return isVisibleOrAdding()
                && (mViewVisibility == View.VISIBLE)
                && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0);
    }</span>

假设窗体设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE属性,那么该窗体是不能获得焦点的,即canReceiveKeys()会返回false。也就是说焦点窗体是不会更改的。对于悬浮的窗体一般来说是不须要获得焦点的。故一般设置WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE属性,不然会存在一些问题。假设该窗体能够接受key事件(这个key事件有点歧义,并非仅仅接受按键事件),那么就调用updateFocusedWindowLocked()。这个以后再研究。假设焦点窗体发生变更,那么就调用moveInputMethodWindowsIfNeededLocked(false)来移动输入法窗体到合适的位置。

mInputFocus中保存的窗体在调用InputMonitor.updateInputWindowsLw()时终于设置到InputDispatcher.mFocusedWindowHandle中去了。

2、mInputDispatchFrozen

冻结InputDispatcher标志,“ When true, prevents input dispatch from proceeding until set to false again.”。

①WMS.startFreezingDisplayLocked()-->InputMonitor.freezeInputDispatchingLw()-->mInputDispatchFrozen = true; updateInputDispatchModeLw();

②WMS.stopFreezingDisplayLocked()-->InputMonitor.thawInputDispatchingLw()-->mInputDispatchFrozen = false; updateInputDispatchModeLw();-->InputManagerService.setInputDispatchMode()-->InputDispatcher.setInputDispatchMode(bool enabled, bool frozen)-->mDispatchFrozen = frozen;

<span style="font-size:18px;">void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    nsecs_t currentTime = now();

    // Reset the key repeat timer whenever we disallow key events, even if the next event
    // is not a key.  This is to ensure that we abort a key repeat if the device is just coming
    // out of sleep.
    if (!mPolicy->isKeyRepeatEnabled()) {
        resetKeyRepeatLocked();
    }

    // If dispatching is frozen, do not process timeouts or try to deliver any new events.
    if (mDispatchFrozen) {
#if DEBUG_FOCUS
        ALOGD("Dispatch frozen.  Waiting some more.");
#endif
        return;
}
……….
}
</span>
调用dispatchOnceInnerLocked()分发事件时,直接return掉。上面的调用逻辑能够知道冻结/解冻显示屏时,会冻结InputDispatcher的分发流程。

3、mInputDispatchEnabled

一个java层控制InputDispatcher的开关。

①WMS.setEventDispatching()-->InputMonitor.setEventDispatchingLw()

②WMS.performEnableScreen()-->InputMonitor.setEventDispatchingLw()-->mInputDispatchEnabled = enabled;updateInputDispatchModeLw();-->InputManagerService.setInputDispatchMode()-->InputDispatcher.setInputDispatchMode(bool enabled, bool frozen)-->mDispatchEnabled = enabled;

详细是怎么使能InputDispatcher的?

<span style="font-size:18px;">if (!mDispatchEnabled) {
        dropReason = DROP_REASON_DISABLED;
    }
</span>
在dispatchOnceInnerLocked()中会设置dropReason(放弃原因),假设dropReason不为0表示输入事件将被放弃掉,不会分发。


google源代码是这样解释的:“ When true, input dispatch proceeds normally.  Otherwise all events are dropped. Initially false, so that input does not get dispatched until boot is finished at which point the ActivityManager will enable dispatching.”

4、mUpdateInputWindowsNeeded

是否须要更新窗体信息到InputDispatcher中的标志。在非强制性更新窗体信息到InputDispatcher中去时,必须先调用InputMonitor.setUpdateInputWindowsNeededLw()设置该标志。然后再调用InputMonitor.updateInputWindowsLw()。

假设强制性更新窗体信息到InputDispatcher中去时就不必设置该变量了。

5、notifyInputChannelBroken()

InputManager通知WMS输入通道破裂函数。

<span style="font-size:18px;">    public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
        if (inputWindowHandle == null) {
            return;
        }

        synchronized (mService.mWindowMap) {
            WindowState windowState = (WindowState) inputWindowHandle.windowState;
            if (windowState != null) {
                Slog.i(WindowManagerService.TAG, "WINDOW DIED " + windowState);
                mService.removeWindowLocked(windowState.mSession, windowState);
            }
        }
    }
</span>
函数处理逻辑很easy。就是将输入通道破裂的窗体直接调用removeWindowLocked()进行移除。


调用流程:InputDispatcher.startDispatchCycleLocked()--> InputDispatcher.abortBrokenDispatchCycleLocked()--> InputDispatcher.onDispatchCycleBrokenLocked()-->InputDispatcher.doNotifyInputChannelBrokenLockedInterruptible()-->NativeInputManager.notifyInputChannelBroken()-->InputManagerService.notifyInputChannelBroken()-->InputMonitor.notifyInputChannelBroken();

startDispatchCycleLocked()中在publish event不成功,就会走以下的逻辑:

<span style="font-size:18px;">        if (status) {
            if (status == WOULD_BLOCK) {
                if (connection->waitQueue.isEmpty()) {
                    ALOGE("channel '%s' ~ Could not publish event because the pipe is full. "
                            "This is unexpected because the wait queue is empty, so the pipe "
                            "should be empty and we shouldn't have any problems writing an "
                            "event to it, status=%d", connection->getInputChannelName(), status);
                    abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
                } else {
                    // Pipe is full and we are waiting for the app to finish process some events
                    // before sending more events to it.
#if DEBUG_DISPATCH_CYCLE
                    ALOGD("channel '%s' ~ Could not publish event because the pipe is full, "
                            "waiting for the application to catch up",
                            connection->getInputChannelName());
#endif
                    connection->inputPublisherBlocked = true;
                }
            } else {
                ALOGE("channel '%s' ~ Could not publish event due to an unexpected error, "
                        "status=%d", connection->getInputChannelName(), status);
                abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
            }
            return;
        }
</span>
在正常pipe full的情况下是不会调用abortBrokenDispatchCycleLocked()的,其它情况则调用abortBrokenDispatchCycleLocked()来处理。

<span style="font-size:18px;">void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection, bool notify) {
#if DEBUG_DISPATCH_CYCLE
    ALOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s",
            connection->getInputChannelName(), toString(notify));
#endif

    // Clear the dispatch queues.
    drainDispatchQueueLocked(&connection->outboundQueue);
    traceOutboundQueueLengthLocked(connection);
    drainDispatchQueueLocked(&connection->waitQueue);
    traceWaitQueueLengthLocked(connection);

    // The connection appears to be unrecoverably broken.
    // Ignore already broken or zombie connections.
    if (connection->status == Connection::STATUS_NORMAL) {
        connection->status = Connection::STATUS_BROKEN;

        if (notify) {
            // Notify other system components.
            onDispatchCycleBrokenLocked(currentTime, connection);
        }
    }
}
</span>
函数中清理outboundQueue和waitQueue两个队列中的消息。然后调用onDispatchCycleBrokenLocked()-->.....来通知WMS移除相应的窗体。

outboundQueue:Queue of events that need to be published to the connection.

waitQueue:Queue of events that have been published to the connection but that have not yet received a "finished" response from the application.
输入事件仅仅有收到应用回复的finished消息才算真正分发成功。

6、notifyANR()

源代码对这个函数的凝视已经很清楚:

<span style="font-size:18px;">/* Notifies the window manager about an application that is not responding.
     * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
     * 
     * Called by the InputManager.
</span>

调用流程:InputDispatcher.findFocusedWindowTargetsLocked();InputDispatcher.findTouchedWindowTargetsLocked()-->InputDispatcher.handleTargetsNotReadyLocked()--> InputDispatcher.onANRLocked()-->InputDispatcher.doNotifyANRLockedInterruptible()-->NativeInputManager.notifyANR()-->InputManagerService.notifyANR()-->InputMonitor.notifyANR();

在InputDispatcher的findFocusedWindowTargetsLocked()和findTouchedWindowTargetsLocked()函数中存在多种异常情况是目标窗体窗体还没准备好接受事件的,这时须要调用handleTargetsNotReadyLocked()来处理,handleTargetsNotReadyLocked()中会作出一定时间的等待。假设终于超时那么就会调用onANRLocked()报ANR。

InputDispatcher.notifyANR()函数处理也非常easy,终于会调用ActivityManagerNative.getDefault().inputDispatchingTimedOut(windowState.mSession.mPid, aboveSystem, reason);来拉起一个ANR对话框。

7、interceptKeyBeforeQueueing()、interceptKeyBeforeDispatching()

①InputDispatcher.notifyKey()-->NativeInputManager.interceptKeyBeforeQueueing()--> InputManagerService.interceptKeyBeforeQueueing()-->InputMonitor.interceptKeyBeforeQueueing()-->PhoneWindowManager.interceptKeyBeforeQueueing()

②InputDispatcher.dispatchKeyLocked()-->InputDispatcher.doInterceptKeyBeforeDispatchingLockedInterruptible()-->NativeInputManager.interceptKeyBeforeDispatching()--> InputManagerService.interceptKeyBeforeDispatching()-->InputMonitor.interceptKeyBeforeDispatching()-->PhoneWindowManager.interceptKeyBeforeDispatching()

从函数名及调用流程来看,一个是在入队之前调用,一个是分发时调用。

电源键事件走的就是这两个函数中的一个。

8、interceptMotionBeforeQueueingWhenScreenOff()

InputDispatcher.notifyMotion()-->NativeInputManager.interceptMotionBeforeQueueing()--> InputManagerService.interceptMotionBeforeQueueingWhenScreenOff()-->InputMonitor.interceptMotionBeforeQueueingWhenScreenOff()-->PhoneWindowManager.interceptMotionBeforeQueueingWhenScreenOff()

<span style="font-size:18px;">void NativeInputManager::interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
    // Policy:
    // - Ignore untrusted events and pass them along.
    // - No special filtering for injected events required at this time.
    // - Filter normal events based on screen state.
    // - For normal events brighten (but do not wake) the screen if currently dim.
    if ((policyFlags & POLICY_FLAG_TRUSTED) && !(policyFlags & POLICY_FLAG_INJECTED)) {
        if (isScreenOn()) {
            policyFlags |= POLICY_FLAG_PASS_TO_USER;

            if (!isScreenBright()) {
                policyFlags |= POLICY_FLAG_BRIGHT_HERE;
            }
        } else {
            JNIEnv* env = jniEnv();
            jint wmActions = env->CallIntMethod(mServiceObj,
                        gServiceClassInfo.interceptMotionBeforeQueueingWhenScreenOff,
                        policyFlags);
            if (checkAndClearExceptionFromCallback(env,
                    "interceptMotionBeforeQueueingWhenScreenOff")) {
                wmActions = 0;
            }

            policyFlags |= POLICY_FLAG_WOKE_HERE | POLICY_FLAG_BRIGHT_HERE;
            handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
        }
    } else {
        policyFlags |= POLICY_FLAG_PASS_TO_USER;
    }
}</span>
在收到的输入事件来自设备,而不是上层应用注入的事件时,此时假设屏幕是灭屏状态,那么就调用interceptMotionBeforeQueueingWhenScreenOff()函数来处理。

9、pauseDispatchingLw()、resumeDispatchingLw()

这两个函数很easy。就是设置WindowToken.paused。并强制更新到InputDispatcher中去。

<span style="font-size:18px;">    public void pauseDispatchingLw(WindowToken window) {
        if (! window.paused) {
            if (WindowManagerService.DEBUG_INPUT) {
                Slog.v(WindowManagerService.TAG, "Pausing WindowToken " + window);
            }
            
            window.paused = true;
            updateInputWindowsLw(true /*force*/);
        }
    }
    
    public void resumeDispatchingLw(WindowToken window) {
        if (window.paused) {
            if (WindowManagerService.DEBUG_INPUT) {
                Slog.v(WindowManagerService.TAG, "Resuming WindowToken " + window);
            }
            
            window.paused = false;
            updateInputWindowsLw(true /*force*/);
        }
    }
</span>
在InputDispatcher中。findFocusedWindowTargetsLocked()和 findTouchedWindowTargetsLocked()中寻找到的目标窗体假设状态为paused。那么就会调用handleTargetsNotReadyLocked()进行等待。
<span style="font-size:18px;">if (touchedWindow.windowHandle->getInfo()->paused) {
                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                        NULL, touchedWindow.windowHandle, nextWakeupTime,
                        "Waiting because the touched window is paused.");
                goto Unresponsive;
            }
</span>
终点。

版权声明:本文博主原创文章。博客,未经同意不得转载。

原文地址:https://www.cnblogs.com/lcchuguo/p/4799931.html