android input(二) InputDispatcher

android input(一) 初始化及InputReader流程

唤醒InputDispatcher

以Motion事件的分发过程来进行举例

void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
	//检查Motion事件的参数是否有效,对于motion事件,主要时校验触控点的数量与ID是否在合理范围
    if (!validateMotionEvent(args->action, args->actionButton,
                args->pointerCount, args->pointerProperties)) {
        return;
    }
    uint32_t policyFlags = args->policyFlags;
    policyFlags |= POLICY_FLAG_TRUSTED;

    android::base::Timer t;
    //mPolicy是指NativeInputManager对象,这里policyFlags为引用,会修改policyFlags的值
    //如果处理时间大于50ms则,输出警告
    mPolicy->interceptMotionBeforeQueueing(args->displayId, args->eventTime, /*byref*/ policyFlags);
    if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
        ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
                std::to_string(t.duration().count()).c_str());
    }

    bool needWake;
    { // acquire lock
        mLock.lock();
		//Motion事件是否需要交由InputFilter过滤
        if (shouldSendMotionToInputFilterLocked(args)) {
            mLock.unlock();
			//初始化MotionEvent,将NotifyMotionArgs中的参数信息赋值给MotionEvent中的参数
         
            MotionEvent event;
            event.initialize(args->deviceId, args->source, args->displayId,
                    args->action, args->actionButton,
                    args->flags, args->edgeFlags, args->metaState, args->buttonState,
                    args->classification, 0, 0, args->xPrecision, args->yPrecision,
                    args->downTime, args->eventTime,
                    args->pointerCount, args->pointerProperties, args->pointerCoords);

            policyFlags |= POLICY_FLAG_FILTERED;
            //开始过滤,如果返回值为false,就会直接return,这次事件不再进行分发,直接忽略
            if (!mPolicy->filterInputEvent(&event, policyFlags)) {
                return; // event was consumed by the filter
            }
            mLock.lock();
        }
         //创建KeyEntry对象
        MotionEntry* newEntry = new MotionEntry(args->sequenceNum, args->eventTime,
                args->deviceId, args->source, args->displayId, policyFlags,
                args->action, args->actionButton, args->flags,
                args->metaState, args->buttonState, args->classification,
                args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
                args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);
		//将KeyEntry放入队列,返回true,则派发线程处于休眠,需要唤醒
        needWake = enqueueInboundEventLocked(newEntry);
        mLock.unlock();
    } // release lock

    if (needWake) {
        mLooper->wake();
    }
}

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
	//mInboundQueue为空,则表示派发线程处于休眠
    bool needWake = mInboundQueue.isEmpty();
    //将事件放入到队列尾部
    mInboundQueue.enqueueAtTail(entry);
    //事件响应速度优化处理
    switch (entry->type) {
    case EventEntry::TYPE_KEY: {
       ...
        break;
    }
    case EventEntry::TYPE_MOTION: {
     //当前App无响应且用户希望切换到其他应用窗口,则drop该窗口事件,并处理其他窗口事件
        MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
        if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN
                && (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
                && mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
                && mInputTargetWaitApplicationToken != nullptr) {
            ...
            //查询可触摸的窗口
             sp<InputWindowHandle> touchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y);
            ...
        }
        break;
    }
    }

    return needWake;
}

sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId,
        int32_t x, int32_t y, bool addOutsideTargets, bool addPortalWindows) {
   //从前到后浏览窗口,找到触摸窗口和外部目标。
   // mWindowHandles中保存的InputWindowHandler类保存了窗口的InputChannel以及InputWindowInfo结构体
   // InputWindowInfo结构体保存了窗口的各种布局信息,包括可见性、位置、尺寸、flag等。
    const std::vector<sp<InputWindowHandle>> windowHandles = getWindowHandlesLocked(displayId);
    // 递归遍历mWindowHandles中的所有WindowHandle,检查时间坐标是否落在其上
    for (const sp<InputWindowHandle>& windowHandle : windowHandles) {
        const InputWindowInfo* windowInfo = windowHandle->getInfo();
        if (windowInfo->displayId == displayId) {
            int32_t flags = windowInfo->layoutParamsFlags;
            if (windowInfo->visible) {
                if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
                // 如果窗口是focusable或者flag不为FLAG_NOT_FOCUSABLE,则说明该窗口是”可触摸模式“
                    bool isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
                    //如果窗口是”可触摸模式或者坐标点落在窗口之上      
                    if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
                        int32_t portalToDisplayId = windowInfo->portalToDisplayId;
                        if (portalToDisplayId != ADISPLAY_ID_NONE&& portalToDisplayId != displayId) {
                            if (addPortalWindows) {
                                mTempTouchState.addPortalWindow(windowHandle);
                            }
                            return findTouchedWindowAtLocked(
                                    portalToDisplayId, x, y, addOutsideTargets, addPortalWindows);
                        }
                        // Found window.
                        return windowHandle;
                    }
                }
                if (addOutsideTargets && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) {
                //将符合条件的窗口放入TempTouchState中,以便后续处理。
                    mTempTouchState.addOrUpdateWindow(
                            windowHandle, InputTarget::FLAG_DISPATCH_AS_OUTSIDE, BitSet32(0));
                }
            }
        }
    }
    return nullptr;
}

notifyMotion 流程

  1. 检查Motion事件的参数是否有效,其内部会检查触控点的数量pointerCount是否在合理范围内(小于1或者大于16都是不合理的),以及触控点的ID是否在合理范围内(小于0或者大于31都是不合理的)
  2. 调用NativeInputManager.interceptMotionBeforeQueueing,加入队列前执行拦截动作,但并不改变流程,调用链:
    IMS.interceptMotionBeforeQueueing
    InputMonitor.interceptMotionBeforeQueueing (继承IMS.WindowManagerCallbacks)
    PhoneWindowManager.interceptMotionBeforeQueueing,主要是PowerManger.wakeUp
  3. 调用NativeInputManager.filterInputEvent过滤输入事件;当返回值为false则过滤该事件,不再往下分发
    这里会反射到java层IMS.filterInputEvent,然后设置了InputFilter,则返回false,拦截事件,然后调用InputFilter.filterInputEvent,异步调用sendInputEvent, 最终会再调用InputDispatcher.injectInputEvent()
    injectInputEvent函数和notifyKey函数很像,notifyKey函数是正常走按键流程在dispatchReader中调用的函数。这里也会想notifyKey一样,先调用PhoneWindowManager的interceptKeyBeforeQueueing函数,然后根据不同类型的Event,然后创建EventEntry,最后调用了enqueueInboundEventLocked函数
    adb shell input keyevent 就是调用的InputManager.getInstance().injectInputEvent,然后调用到InputDispatcher.injectInputEvent进行事件模拟的
  4. 生成MotionEntry,并调用enqueueInboundEventLocked,将该事件加入到InputDispatcherd的成员变量mInboundQueue
    针对Motion当前App无响应且用户希望切换到其他应用窗口,则drop该窗口事件,并处理其他窗口事件,调用findTouchedWindowAtLocked查询可触摸的窗口
  5. findTouchedWindowAtLocked中的mWindowHandles的赋值过程是由Java层的InputMonitor.setInputWindows(),经过JNI调用后进入InputDispatcher::setInputWindows()方法完成. 进一步说, 就是WMS执行addWindow()过程或许UI改变等场景,都会触发该方法的修改.

notifyMotion由InputReader在其线程循环中调用,因此interceptMotionBeforeQueueing和filterInputEvent都在InputReader的线程中执行

android input(二) InputDispatcher_第1张图片
android input(二) InputDispatcher_第2张图片

分发过程

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        std::scoped_lock _l(mLock);
        mDispatcherIsAlive.notify_all();
        //通过dispatchOnceInnerLocked进行输入事件分发,传出参数nextWakeupTime决定下次派发循环的时间点
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }
        //执行命令队列中的命令
        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;//设置nextWakeupTime为立即开始执行下次线程循环
        }
    } // release lock
    // 计算需要休眠的时间timeoutMillis,并通过pollOnce进入epoll_wait
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}
  1. 通过dispatchOnceInnerLocked进行输入事件分发,传出参数nextWakeupTime决定下次派发循环的时间点
  2. 执行命令队列中的命令,命令是一个符合Command签名的回调函数,可以通过InputDispatcher::postCommandLocked()创建并添加到命令队列mCommandQueue中,InputDispatcher执行命令的过程类似于Handler的工作方式
  3. 计算需要休眠的时间timeoutMillis,并通过pollOnce进入epoll_wait

线程执行Looper->pollOnce,进入epoll_wait等待状态,派发线程的休眠在三种情况下可能被唤醒:

callback:epoll_wait监听的fd由epoll_event发生时唤醒;
timeout:到达nextWakeupTime时间,超时唤醒;
wake:主动调用Looper::wake函数唤醒(由输入事件注入派发对队列中);


void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    ...
    //当分发被冻结,则不再处理超时和分发事件的工作,
    //setInputDispatchMode可以使InputDispatcher在禁用、冻结、正常状态切换
    if (mDispatchFrozen) {
        return;
    }
    //优化app切换延迟,当切换超时,则抢占分发,丢弃其他所有即将要处理的事件
    //如果isAppSwitchDue为true,说明没有及时响应HOME键等操作
    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
    if (mAppSwitchDueTime < *nextWakeupTime) {
        *nextWakeupTime = mAppSwitchDueTime;
    }
    //如果还没有待分发的事件,去mInboundQueue中取出一个事件
    if (! mPendingEvent) {
         //如果mInboundQueue为空,并且没有待分发的事件,就return
        if (mInboundQueue.isEmpty()) {
            ...
        } else {
            //如果mInboundQueue不为空,取队列头部的EventEntry赋值给mPendingEvent
            //之所以用成员变量而不是局部变量保存,是由于此次线程循环有可能不能完成此事件派发 
            mPendingEvent = mInboundQueue.dequeueAtHead();
            traceInboundQueueLengthLocked();
        }
        //重置ANR信息.
        resetANRTimeoutsLocked();
    }
	//检查事件是否需要丢弃,dropReason描述了是否需要被丢弃
    bool done = false;
    DropReason dropReason = DROP_REASON_NOT_DROPPED;
    if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
    	//在事件注入派发时调用interceptMotionBeforeQueueing询问派发测量,倘若派发策略不允许此事件被派发给用户,则丢弃
        dropReason = DROP_REASON_POLICY;
    } else if (!mDispatchEnabled) {
    	//如果InputDispatcher被禁用,通过setInputDispatchMode设置,则此事件也会被丢弃
    	//注意如果被冻结时时不会丢弃事件,而是等解冻后继续派发
        dropReason = DROP_REASON_DISABLED;
    }

    ...
    switch (mPendingEvent->type) {
   ...
    case EventEntry::TYPE_KEY: {
        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
        ...
        done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
        break;
    }

    case EventEntry::TYPE_MOTION: {
        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
        //事件因为home键没有能被及时响应丢弃
        if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
            dropReason = DROP_REASON_APP_SWITCH;
        }
        //事件因为过期丢弃
        if (dropReason == DROP_REASON_NOT_DROPPED
                && isStaleEvent(currentTime, typedEntry)) {
            dropReason = DROP_REASON_STALE;
        }
        //事件因为阻碍了其他窗口获得事件丢弃
        if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
            dropReason = DROP_REASON_BLOCKED;
        }
        //执行dispatchMotionLocked进行Motion事件的派发,如果派发完成,无论成功派发还是事件被丢弃,都会返回true
        //失败则返回false,在下次循环时再次尝试此事件派发
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }
    ...
    if (done) {
    	//如果事件丢弃,为了保证窗口收到的事件仍能保持down/up enter/exit的配对状态,还需要对事件进行补发
        if (dropReason != DROP_REASON_NOT_DROPPED) {
            dropInboundEventLocked(mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;
        //设置mPendingEvent对象为null,使在下次循环时可以处理派发队列中的下一条事件
        releasePendingEventLocked();
        //使得InputDispatcher能够快速处理下一个分发事件
        //因为当派发队列为空时,派发线程可能需要在下次循环中生成重复按键事件,因此不能直接进入休眠
        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately
    }
}
  1. 如果派发队列为空,则回事派发线程陷入无期限休眠状态
  2. InputDispatcher的冻结处理
    如果当前InputDispatcher被冻结,则不进行派发操作,InputDispatcher有三种状态,分别是正常状态、冻结状态和禁用状态,可以通过InputDispatcher的setInputDispatchMode函数来设置
  3. 窗口切换操作处理
    mAppSwitchDueTime ,代表了App最近发生窗口切换操作时(比如按下Home键、挂断电话),该操作事件最迟的分发时间,当事件分发的时间点距离该事件加入mInboundQueue的时间超过500ms,则认为app切换过期,即isAppSwitchDue=true。
    如果mAppSwitchDueTime小于nextWakeupTime(下一次InputDispatcherThread醒来的时间),就将mAppSwitchDueTime赋值给nextWakeupTime,这样当InputDispatcher处理完分发事件后,会第一时间处理窗口切换操作
  4. 取出事件
    如果没有待分发的事件,就从mInboundQueue中取出一个事件,如果mInboundQueue为空,并且没有待分发的事件,就return,如果mInboundQueue不为空,取队列头部的EventEntry赋值给mPendingEvent,mPendingEvent的类型为EventEntry对象指针
  5. 事件丢弃
    dropReason代表了事件丢弃的原因,它的默认值为DROP_REASON_NOT_DROPPED,代表事件不被丢弃。
    根据mPendingEvent的type做区分处理,这里主要截取了对Motion类型的处理。经过过滤,会调用dispatchMotionLocked函数为这个事件寻找合适的窗口
  6. 后续处理
    执行dispatchMotionLocked进行Motion事件的派发,如果派发完成,无论成功派发还是事件被丢弃,都会返回true,否则返回false,在下次循环时再次尝试此事件派发。如果dispatchMotionLocked事件分发成功,则会调用releasePendingEventLocked函数,其内部会将mPendingEvent的值设置为Null,并将mPendingEvent指向的对象内存释放掉。将nextWakeupTime的值设置为LONG_LONG_MIN,这是为了让InputDispatcher能够快速处理下一个分发事件
  7. 派发一个事件至少需要一次线程循环才能完成,因为事件的目标窗口有可能正在处理前一个输入事件,在窗口完成之前的事件的处理病给予反馈之前,InputDIspatcher不会在向此窗口派发新事件。
    事件的派发是串行的,在队首的事件完成派发或者丢弃之前,不会对后续的事件进行派发

事件丢弃DropReason

* frameworks/native/services/inputflinger/InputDispatcher.h
 enum DropReason {
        DROP_REASON_NOT_DROPPED = 0,
        DROP_REASON_POLICY = 1,
        DROP_REASON_APP_SWITCH = 2,
        DROP_REASON_DISABLED = 3,
        DROP_REASON_BLOCKED = 4,
        DROP_REASON_STALE = 5,
    };
  • DROP_REASON_NOT_DROPPED
    当InputDispatcher在将输入事件放入派发队列前想DispatcherPolicy询问此事件派发策略时,DispatcherPolicy会将DROP_REASON_NOT_DROPPED策略去掉,没有这个派发策略的对象会被丢弃。
  • DROP_REASON_POLICY
    某些输入事件具有系统级功能,例如HOME键、电源键、电话接听/挂断等被系统处理,因此DispatcherPolicy不希望这些事件被窗口捕获。
  • DROP_REASON_APP_SWITCH
    dispatchOnceInnerLocked()函数说明了InputDispatcher的事件派发是串行的。因此在前一个事件派发成功并得到目标窗口的反馈之前,后续事件都会被其阻塞。当某个窗口因程序缺陷而无法响应输入事件时,用户可能会尝试使用HOME键退出这个程序。而由于派发的串行性,用户所操作的HOME键在其之前的输入事件成功派发给无响应的应用窗口之前无法获得派发机会,因此在ANR对话框弹出之前的5秒里,用户不得不面对无响应程序。为了解决这个问题,InputDispatcher为HOME键设置了限时派发的要求。当InputDispatcher的enqueueInboundEventLocked()函数发现HOME键被加入派发队列后,便要求HOME键之前的所有输入事件在0.5秒(APP_SWITCH_TIMEOUT常量定义)之前派发完毕,否则这些事件将被丢弃,是的HOME键至少能在0.5秒内得到响应。
  • DROP_REASON_DISABLED
    因为InputDispatcher被禁用而使得事件被丢弃。InputDispatcher::setInputDispatchMode()函数可以是的InputDispatcher在禁用、冻结、正常三种状态之间进行切换。禁用状态会使得所有事件被丢弃,冻结将会使的dispatchOnceInnerLocked()函数直接返回从而停止派发工作。InputDispatcher的这三种状态的切换有Java层的IMS提供接口,由AMS和WMS根据需要进行设置。例如:当手机进入休眠状态时,InputDispatcher会被禁用,而屏幕旋转过程中,InputDispatcher会被暂时冻结。
  • DROP_REASON_BLOCKED
    和APP_SWITCH原因类似,如果是因为一个窗口无法响应输入事件,可用户可能希望在其他窗口上进行点击,以尝试是否能得到响应。因为派发的串行性,这次尝试会以失败而高中。为此,当enqueueInboundEventLocked()发现有窗口正在阻塞派发的进行,并且新入队的触摸事件的目标是另外一个窗口,则将这个新事件保存到mNextUnblockedEvent中。随后的dispatchOnceInnerLocked()会将此事件之前的输入事件全部丢弃,使得用户在其他窗口上进行点击的尝试可以立即得到响应。
  • DROP_REASON_STALE
    在dispatchOnceInnerLocked()函数准备对事件进行派发时,会先检查一下事件所携带的时间戳和当前时间的差距。如果事件过于陈旧(10秒以上,由常量STALE_EVENT_TIMEOUT所指定),则此事件需要被抛弃

Motion事件目标窗口确定

当事件幸运的避开了所有上述丢弃原因之后,才能由InputDispatcher尝试派发。对Motion时间来说,下一步是dispatchMotionLocked(),在这个函数中,InputDispatcher将为事件寻找合适的窗口目标

bool InputDispatcher::dispatchMotionLocked(
        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
    //标记事件已经正式进入派发流程
    if (! entry->dispatchInProgress) {
        entry->dispatchInProgress = true;
    }
    //如果事件是需要丢弃的,则返回true,不会去为该事件寻找合适的窗口
    if (*dropReason != DROP_REASON_NOT_DROPPED) {
        setInjectionResult(entry, *dropReason == DROP_REASON_POLICY
                ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
        return true;
    }
    bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
    //目标窗口信息列表会存储在inputTargets中
    std::vector<InputTarget> inputTargets;
    bool conflictingPointerActions = false;
    int32_t injectionResult;
    if (isPointerEvent) {
        //处理点击形式的事件,比如触摸屏幕
        injectionResult = findTouchedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
    } else {
        //处理非触摸形式的事件,比如轨迹球
        injectionResult = findFocusedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime);
    }
    //输入事件被挂起,说明找到了窗口并且窗口无响应
    if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
        return false;
    }
    setInjectionResult(entry, injectionResult);
     //返回值不为SUCCEEDED,表示无法找到合适窗口,例如窗口未获取焦点,或点击位置没落在任何一个窗口内,此事件将被直接丢弃
    if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
       ...
        return true;
    }
    //分发目标添加到inputTargets列表中
    addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(entry));
	...
    //将事件派发给inputTargets列表中的目标
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}
  • 对于被丢弃的事件直接返回true。
  • 给事件寻找合适的目标窗口,目标窗口分为普通窗口和监听窗口(monitoring),普通窗口通过按点查找与按焦点查找两种方式获得,而监听窗口则无条件监听所有输入事件。普通窗口的查找结果决定了此次线程循环是否可以完成事件派发。
  • 如果成功找到可以接收事件的目标窗口,则调用dispatchEventLocked()函数进行派发。
  • 查找到的派发目标存储在InputTarget结构体中
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
        const MotionEntry* entry, std::vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,bool* outConflictingPointerActions) {
        ...
   if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
		// 从MotionEntry中获取事件坐标点
        int32_t pointerIndex = getMotionEventActionPointerIndex(action);
        int32_t x = int32_t(entry->pointerCoords[pointerIndex].
                getAxisValue(AMOTION_EVENT_AXIS_X));
        int32_t y = int32_t(entry->pointerCoords[pointerIndex].
                getAxisValue(AMOTION_EVENT_AXIS_Y));
        bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
        sp<InputWindowHandle> newTouchedWindowHandle = findTouchedWindowAtLocked(
                displayId, x, y, isDown /*addOutsideTargets*/, true /*addPortalWindows*/);
        ...
        //把选中的窗口保存到TempTouchState中,以便后续处理
        mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
        ...
   }
	...
	//检查mTempTouchState中所有目标窗口是否准备好接受新的输入事件
	for (const TouchedWindow& touchedWindow : mTempTouchState.windows) {
        if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
            //检查窗口是否准备好接收更多的输入
            std::string reason = checkWindowReadyForMoreInputLocked(currentTime,
                    touchedWindow.windowHandle, entry, "touched");
            if (!reason.empty()) {
            	// 如果窗口不能接收新事件,则记录不能接收的原因,并设置nextWakeupTime为5s后,如果5s后
                // 线程仍未将次事件派发成功而进入这个分支,则会开始向Java层通报ANR。
                // 在这里injectionResult被设置为INPUT_EVENT_INJECTION_PENDING
                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                        nullptr, touchedWindow.windowHandle, nextWakeupTime, reason.c_str());
                //因此次查找到的窗口不能接收事件,所以调过后续产生InputTarget的过程
                goto Unresponsive;
            }
        }
    }
    ...
    //如果执行到这里,说明窗口的查找过程一切顺利,设置injectionResult为SUCCEEDED,并将injectionResult放入参数inputTargets中
    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
    //为每一个mTempTouchState中的窗口生成InputTargets
    for (const TouchedWindow& touchedWindow : mTempTouchState.windows) {
        addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
                touchedWindow.pointerIds, inputTargets);
    }

    for (const TouchedMonitor& touchedMonitor : mTempTouchState.gestureMonitors) {
        addMonitoringTargetLocked(touchedMonitor.monitor, touchedMonitor.xOffset,
                touchedMonitor.yOffset, inputTargets);
    }
    //在下一次迭代中,删除外部窗口或悬停触摸窗口
     mTempTouchState.filterNonAsIsTouchWindows();
    ...
}

此函数中出现的mWindowHandles保存了所有窗口的信息,为查找输入窗口提供依据,因此每当窗口形态发生变化是,WMS会通过IMS将所有窗口提交到InputDispatcher,完成mWindowHandles的更新。mWindowHandles中的窗口顺序,索引越小,ZOrder越大,这与WMS中的窗口列表相反

此函数做了三项工作

  1. 根据窗口点击坐标和事件发生坐标选择合适的目标窗口(ZOrder由上至下遍历,ZOrder越靠上,优先权越高),另外如果窗口没有在其LayoutParams.flag中指明FLAG_NOT_TOUCH_MODAL选项,说明是模式窗口,模式窗口将会阻止点击事件派发给其子窗口。
  2. 调用checkWindowReadyForMoreInputLocked()函数检查窗口是否可接收新的点击事件,如果无法接收,说明此窗口有可能发生ANR。handleTargetsNotReadyLocked()做下记录,并将injectionResult设置为PENDING,要求下次派发循环中重试。
  3. 如果找到可以接收新事件的窗口,则调用addWindowTargetLocked()生成InputTarget,并添加到inputTargets列表中。(InputTarget中几乎所有字段可在InputWindowHandle中找到)

事件的发送

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
        EventEntry* eventEntry, const std::vector<InputTarget>& inputTargets) {
    //向mCommandQueue队列添加doPokeUserActivityLockedInterruptible命令
    pokeUserActivityLocked(eventEntry);
    for (const InputTarget& inputTarget : inputTargets) {
    	//根据inputTarget内部的inputChannel来获取Connection的索引
        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
        	//获取保存在mConnectionsByFd容器中的Connection
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            //根据inputTarget,开始事件发送循环
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        }
    }
}
  • 遍历inputTargets列表,获取每一个inputTarget
  • 根据inputTarget内部的inputChannel来获取Connection的索引,再根据这个索引作为Key值来获取mConnectionsByFd容器中的Connection。Connection可以理解为InputDispatcher和目标窗口的连接,其内部包含了连接的状态、InputChannel、InputWindowHandle和事件队列等等。
  • 调用prepareDispatchCycleLocked函数根据当前的inputTarget,开始事件发送循环。最终会通过inputTarget中的inputChannel来和窗口进行进程间通信,最终将Motion事件发送给目标窗口。
    调用流程:
  1. prepareDispatchCycleLocked->enqueueDispatchEntryLocked
    将InputDispatcher中mInboundQueue中的事件取出后, 找到目标window后,封装dispatchEntry加入到connection的outbound队列。
    DispatchEntry附加了一个seq字段,这个字段是事件的序号,窗口对事件的反馈将携带这个序号。其实这个函数使用不同的选项多次调用了enqueueDispatchEntryLocked()。FLAG_DISPATCH_AS_IS表示不修改事件的action类型,而其他的选项则会使得action类型发生变化
  2. startDispatchCycleLocked
    从outboundQueue中取出事件,重新放入waitQueue队列
  3. inputPublisher.publishKeyEvent->mChannel->sendMessage
    InputChannel通过socket向远端的socket发送消息
  • 其中pokeUserActivityLocked(eventEntry)方法调用NativeInputManager::pokeUserActivity,最终会调用到Java层的PowerManagerService.java中的userActivityFromNative()方法. 这也是PMS中唯一的native call方法
void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) {
    int32_t displayId = getTargetDisplayId(eventEntry);
    sp<InputWindowHandle> focusedWindowHandle =
            getValueByKey(mFocusedWindowHandlesByDisplay, displayId);
    ...
    CommandEntry* commandEntry = postCommandLocked(
            & InputDispatcher::doPokeUserActivityLockedInterruptible);
    commandEntry->eventTime = eventEntry->eventTime;
    commandEntry->userActivityEventType = eventType;
}
InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) {
    CommandEntry* commandEntry = new CommandEntry(command);
    //将命令加入mCommandQueue队尾
    mCommandQueue.enqueueAtTail(commandEntry);
    return commandEntry;
}

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection) {
...
	//当Connection状态正常,且outboundQueue不为空
    while (connection->status == Connection::STATUS_NORMAL
            && !connection->outboundQueue.isEmpty()) {
        DispatchEntry* dispatchEntry = connection->outboundQueue.head;
        dispatchEntry->deliveryTime = currentTime;//设置deliveryTime时间
        // Publish the event.
        status_t status;
        EventEntry* eventEntry = dispatchEntry->eventEntry;
        switch (eventEntry->type) {
        case EventEntry::TYPE_KEY: {
           ...
            break;
        }
        case EventEntry::TYPE_MOTION: {
            MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
            ...
            // Publish the motion event.
            status = connection->inputPublisher.publishMotionEvent(...);
            break;
        }
        ...
        }

        // publishKeyEvent失败情况
        if (status) {
            if (status == WOULD_BLOCK) {
                if (connection->waitQueue.isEmpty()) {
                    //pipe已满,但waitQueue为空. 不正常的行为
                    //该方法最终会调用到Java层的IMS.notifyInputChannelBroken().
                    abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
                } else {
                	/// 处于阻塞状态
                    connection->inputPublisherBlocked = true;
                }
            } else {
                abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
            }
            return;
        }
        // Re-enqueue the event on the wait queue.
        connection->outboundQueue.dequeue(dispatchEntry);
        traceOutboundQueueLength(connection);
        connection->waitQueue.enqueueAtTail(dispatchEntry);
        traceWaitQueueLength(connection);
    }
}

总体流程

android input(二) InputDispatcher_第3张图片

  • Motion事件在InputReaderThread线程中的InputReader进行加工,加工完毕后会判断是否要唤醒InputDispatcherThread,如果需要唤醒,会在InputDispatcherThread的线程循环中不断的用InputDispatcher来分发 Motion事件。
  • 将Motion事件交由InputFilter过滤,如果返回值为false,这次Motion事件就会被忽略掉。
  • InputReader对Motion事件加工后的数据结构为NotifyMotionArgs,在InputDispatcher的notifyMotion函数中,用NotifyMotionArgs中的事件参数信息构造一个MotionEntry对象。这个MotionEntry对象会被添加到InputDispatcher的mInboundQueue队列的末尾。
  • 如果mInboundQueue不为空,取出mInboundQueue队列头部的EventEntry赋值给mPendingEvent。
    根据mPendingEvent的值,进行事件丢弃处理。
  • 调用InputDispatcher的findTouchedWindowTargetsLocked函数,在mWindowHandles窗口列表中为Motion事件找到目标窗口,并为该窗口生成inputTarget。
  • enqueueDispatchEntryLocked():生成事件DispatchEntry并加入connection的outbound队列
  • startDispatchCycleLocked():从outboundQueue中取出事件DispatchEntry, 重新放入connection的waitQueue队列
  • InputChannel.sendMessage通过socket方式将消息发送给远程进程目标窗口。

InputChannel

InputChannel就是SocketPair描述符及其操作的封装,SocketPair用来实现在本机内进行进程间的通信,一对SocketPair 通过socketpair()函数创建, 其使用者可以因此而得到两个相互连接的文件描述符。这两个描述符可以通过套接字接口 send()和recv()进行写入和读取,并且向其中一个文件描述符写人的数据,可以从另一个描述符中读取。
配对的两个InputChannel分别保有一个SocketPair的描述符,并分别分配给InputDispatcher与window。因此InputDispatcher向其保有的InputChannel中写入的输入事件,可以由window从自己的InputChannel中读取。并且window可以将事件处理完毕的反馈写入ImputChannel中,InputDispatcher再将反馈进行读取
android input(二) InputDispatcher_第4张图片
ActivityThread.handleResumeActivity->WindowManagerImpl.addView->WindowManagerGlobal.addView()
->ViewRootImpl.setView

* frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
	...
	 if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
    ...
    //通过Binder调用,进入system进程的Session
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
    ...
    if (mInputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                }
   ...
}

* frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq,
            LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState) {
            ...
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
            ...
            final boolean openInputChannels = (outInputChannel != null
                    && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
            if  (openInputChannels) {
                win.openInputChannel(outInputChannel);
            }
            ...
        if (win.canReceiveKeys()) {
        		//新添加window能接收按下操作,则更新聚焦窗口。
                focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                        false /*updateInputWindows*/);
                if (focusChanged) {
                    imMayMove = false;
                }
        }
        ...
        if (focusChanged) {
                displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
                        false /*updateInputWindows*/);
        }
          //将所有的窗口信息更新到InputManagerservice
        displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
        ...
}

  • 创建Java层的InputChannel对象mInputChannel
  • 调用Session.addToDisplay向WMS注册InputChannel信息,进入到WindowManagerService.addWindow
  • 创建WindowInputEventReceiver对象

服务端连接的建立

* frameworks/base/services/core/java/com/android/server/wm/WindowState.java
void openInputChannel(InputChannel outInputChannel) {
        if (mInputChannel != null) {
            throw new IllegalStateException("Window already has an input channel.");
        }
        //根据WindowState的HashCode以及title来生成InputChannel名称
        String name = getName();
        //创建一对InputChannel
        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
        mInputChannel = inputChannels[0];
        mClientChannel = inputChannels[1];
        mInputWindowHandle.token = mClient.asBinder();
        if (outInputChannel != null) {
         	//socket客户端传递给outInputChannel
            mClientChannel.transferTo(outInputChannel);
            mClientChannel.dispose();
            mClientChannel = null;
        } else {
            // If the window died visible, we setup a dummy input channel, so that taps
            // can still detected by input monitor channel, and we can relaunch the app.
            // Create dummy event receiver that simply reports all events as handled.
            mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
        }
        //将Windowstate所保存的InputChannel向IMS进行注册 
        mWmService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder());       
    }
    
* frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public void registerInputChannel(InputChannel inputChannel, IBinder token) {
        if (inputChannel == null) {
            throw new IllegalArgumentException("inputChannel must not be null.");
        }
        if (token == null) {
            token = new Binder();
        }
        //这里直接调用inputChannel.nativeSetToken将java层窗口通过NativeInputChannel设置到native的InputTransport::InputChannel
        inputChannel.setToken(token);
        //实际的注册过程由NI层的NativelnputManager 交给InputDispatcher的同名函数完成注册
        nativeRegisterInputChannel(mPtr, inputChannel, Display.INVALID_DISPLAY);
    }
  1. 通过WindowState.openInputChannel调用InputChannel.openInputChannelPair,然后进入到InputTransport::InputChannel::openInputChannelPair创建的socket pair,将其中的客户端赋值给mInputChannel
  2. 通过IMS.registerInputChannel()将InputChannel注册到IMS中,通过NativeInputManager::registerInputChannel调用mInputManager->getDispatcher()->registerInputChannel进入到InputDispatcher::registerInputChannel
  3. 通过inputChannel.nativeSetToken将java层窗口通过NativeInputChannel设置到native的InputTransport::InputChannel,这里的token为ViewRootImpl中的W窗口类
  4. 通过InputMonitor.updateInputWindowsLw()将所有窗口的信息更新到IMS

在WMS添加窗口时,会创建一对InputChannel
其中一个保存在WindowState中,并注册给IMS,它是服务端。
另外一个则通过传出参数outlnputChannel交给调用者,是客户端。

* frameworks/native/services/inputflinger/InputDispatcher.cpp
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
        int32_t displayId) {
    { // acquire lock
        ...
        //为传入的Inputchannel创建一个Connection对象并对其进行封装 
        sp<Connection> connection = new Connection(inputChannel, false /*monitor*/);
        int fd = inputChannel->getFd();
        ...
        //监听Inputchannel的可读性。mLooper的pollOnce()本质上就是epoll_wait(),因此 
        //Looper对象具有监听文件描述符可读性事件的能力,在此注册Inputchannel可读性事件,
        //并在事件到来时通过handleReceiveCallback()回调进行处理
        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    } // release lock
    // Wake the looper because some connections have changed.
    mLooper->wake();
    return OK;
}

在InputDispatcher中,InputChannel被封装为一个Connection对象。Connection类描述了从ImputDispatcher到目标窗口中的一个连接,其中保存了向窗口发送的事件的状态信息。在Connection中,重要的成员有

  • mlnputPublisher,InputTransport::InputPublisher类的一个对象,它封装InputChannel并直接对其进行写入和读取。另外,它也负责ImputMessage结构体的封装与解析。
  • outboundQueue,用于保存等待通过此Connection进行发送的事件队列。
  • waitQueue,用于保存已经通过此Connection将事件发送给窗口,正在等待窗口反馈
    的事件队列。

registerInputChannel()将InputChannel的可读性事件注册到mLooper中。当来自窗口的反馈到来时,派发线程的mLooper->pollOnce()将会被唤醒,并回调 handleReceiveCallback()进行处理。因此,窗口反馈的到来也会导致派发线程进入下一次派发循环,不过是在handleReceiveCallback()回调完成后。
完成服务端InputChannel的注册之后,InputDispatcher便拥有了向客户端的InputChannel发送InputMessage,以及通过回调handleReceiveCallback()响应来自客户端的反馈的能力。这个过程的基本原理是,先将输入事件放入Connection的outboundQueue队列中,然后再由mInputPublisher依次将队列中的事件封装为InputMessage并写入InputChannel,直到队列为空,或InputChannel的写入缓冲区满。写入的事件将被移存到waitQueue队列里。随后派发线程陷入休眠状态。当窗口在另一端读取事件并发来反馈后,派发线程因InputChannel可读而被唤醒,并在handleReceiveCalback()中通过Connection的mlnputPublisher读取反馈信息,将其与waitQueue中等待反馈的事件进行配对成功后,将事件从waitQueue中移除,完成
事件派发
android input(二) InputDispatcher_第5张图片

窗口端连接的建立及事件的接收

* frameworks/base/core/java/android/view/ViewRootImpl.java
final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }
        ...
}

* frameworks/base/core/jni/android_view_InputEventReceiver.cpp
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
        jobject inputChannelObj, jobject messageQueueObj) {
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    ...
    //获取UI主线程的消息队列
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    ...
	//创建NativeInputEventReceiver对象
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);
    //执行其初始化操作 
    status_t status = receiver->initialize();
    ...
    receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object
    return reinterpret_cast<jlong>(receiver.get());
}

void NativeInputEventReceiver::setFdEvents(int events) {
    if (mFdEvents != events) {
        mFdEvents = events;
         //获取InputChannel的fd 
        int fd = mInputConsumer.getChannel()->getFd();
        if (events) {
        	//通过Looper注册InputChannel的可读性事件 
            mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
        } else {
            mMessageQueue->getLooper()->removeFd(fd);
        }
    }
}

class NativeInputEventReceiver : public LooperCallback {
	...
	InputConsumer mInputConsumer;
    sp<MessageQueue> mMessageQueue;
    ...
    NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env,
        jobject receiverWeak, const sp<InputChannel>& inputChannel,
        const sp<MessageQueue>& messageQueue) :
        mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
        mInputConsumer(inputChannel), mMessageQueue(messageQueue),
        mBatchedInputEventPending(false), mFdEvents(0) {
   ...
	}
}

NativelnputEventReceiver保存了Java层InputEventReceiver对象的引用,并创建了一个InputConsumer类型的对象对InputChannel进行封装。 InputConsumer 与InputPublisher一样,它也封装了InputChannel,负责对其进行写入和读
取操作,同时也负责InputMessage的封装与解析。不过它们的功能正好相反,InputConsumer接收的是输入事件,发送的则是反馈。
通过Looper 监听InputChannel的可读性事件,当有InputMessage可读时,NativelnputEventReceiver的handleEvent() 函数会被Looper 调用,然后调用consumeEvents通过一个循环调用InputConsumer从InputChannel中读取事件,然后回调Java层的onlnputEvent()函数。在Java层完成事件的处理后,便可通过InputConsumer发送处理完毕的反馈给InputDispatcher

android input(二) InputDispatcher_第6张图片

* frameworks/base/core/jni/android_view_InputEventReceiver.cpp
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
    ...
    if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        //发送事件
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }

    if (events & ALOOPER_EVENT_OUTPUT) {
        for (size_t i = 0; i < mFinishQueue.size(); i++) {
            const Finish& finish = mFinishQueue.itemAt(i);
            //将seq与handle两个信息以InputMessage的形式写入InputChannel中 
            if (events & ALOOPER_EVENT_OUTPUT) {
            	status_t status = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled);
            }
            ...
        }
    }
}
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    ...
    bool skipCallbacks = false;
    for (;;) {
    	InputEvent* inputEvent;
    	//通边mInputConsumer的consume()函数从Inputchannel中读取一条InputMessage。 
        //解析为InputEvent后,通过inputEvent参数传出
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent,
                &motionEventType, &touchMoveNum, &flag);
        ...
        //根据事件的类型分别创建KeyEvent与MotlonEvent类型的Java对象 
        switch (inputEvent->getType()) {
            case AINPUT_EVENT_TYPE_KEY:
               ...
                inputEventObj = android_view_KeyEvent_fromNative(env,
                        static_cast<KeyEvent*>(inputEvent));
              ...
              }
              case AINPUT_EVENT_TYPE_MOTION: {
              ...
              }
       }
       //通过JNI回调Java层的InputEventReceiver的dispatchInputEvent()函数                  
       env->CallVoidMethod(receiverObj.get(),
                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
      ...
    }
  • dispatchInputEvent函数首先在字典中保存来自InputDispatcher的事件序列号,以满足发送反馈之需。之后便调用onlnputEvent()函数,交由子类进行输入事件的实际处理工作。如果时output事件则调用mInputConsumer.sendFinishedSignal
  • 事件的反馈动作可由InputEventReceiver.finishlnputEvent()发起,然后进入到NativeInputEventReceiver::finishInputEvent,然后同样调用mInputConsumer.sendFinishedSignal
* frameworks/base/core/jni/android_view_InputEventReceiver.cpp
static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jlong receiverPtr,
        jint seq, jboolean handled) {
    sp<NativeInputEventReceiver> receiver = reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
    status_t status = receiver->finishInputEvent(seq, handled);
    ...
}

status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) {
    status_t status = mInputConsumer.sendFinishedSignal(seq, handled);
    if (status) {
        if (status == WOULD_BLOCK) {
            Finish finish;
            finish.seq = seq;
            finish.handled = handled;
            mFinishQueue.add(finish);
            if (mFinishQueue.size() == 1) {
                setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT);
            }
            return OK;
        }
    }
    return status;
}

当窗口端的InputChannel被写入数 据时,会触发服务端InputChannel可读事件,因此InputDispatcher的派发线程被唤醒并执行 handleReceiveCallback()回调

  • 根据可读的Inputchannel的描述符获取对应的Connection对象
  • 然后在循环中不断地读取尽可能多的反馈信息
  • 调用finishDispatchCycleLocked()函数调用onDispatchCycleFinishedLocked->doDispatchCycleFinishedLockedInterruptible完成对反馈的处理

对于输入事件反馈的处理主要有两个方面

  • 将事件从Connection的waitQueue队列中删除。这个删除动作标志着此事件的派发流程完成,也意味着这个事件经过漫长的加工、传递之旅后生命的结束。
  • 最后调用startDispatchCycleLocked0函数继续尝试发送队列中的下一个事件,又回到发送流程
int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {
    InputDispatcher* d = static_cast<InputDispatcher*>(data);
    { // acquire lock
        std::scoped_lock _l(d->mLock);
        //根据可读的Inputchannel的描述符获取对应的Connection对象
        ssize_t connectionIndex = d->mConnectionsByFd.indexOfKey(fd);
        ...
        bool notify;
        sp<Connection> connection = d->mConnectionsByFd.valueAt(connectionIndex);
        if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
            if (!(events & ALOOPER_EVENT_INPUT)) {
                return 1;
            }
            nsecs_t currentTime = now();
            ...
            for (;;) {
                uint32_t seq;
                bool handled;
                //然后在循环中不断地读取尽可能多的反馈信息 
                status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled);
                if (status) {
                    break;
                }
                //调用finishDispatchCycleLocked()函数完成对反馈的处理 
                d->finishDispatchCycleLocked(currentTime, connection, seq, handled);
                gotOne = true;
            }
            ...
        } else {
           ...
        }
		...
    } // release lock
}

void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(
        CommandEntry* commandEntry) {
    sp<Connection> connection = commandEntry->connection;
    ...
    //从waitQueue中,按照序号取出反馈对应的事件 
    DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq);
    if (dispatchEntry) {
        nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
        //大于2s,则打印信息
         if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
            std::string msg = StringPrintf("Window '%s' spent %0.1fms processing the last input event: ",
                    connection->getWindowName().c_str(), eventDuration * 0.000001f);
            dispatchEntry->eventEntry->appendDescription(msg);
            ALOGI("%s", msg.c_str());
        }
        ...
        if (dispatchEntry == connection->findWaitQueueEntry(seq)) {
        	//将事件从waitQueue中移除 
            connection->waitQueue.dequeue(dispatchEntry);
           ...
        }
         //启动下一次发送循环
        startDispatchCycleLocked(now(), connection);
    }
}
  • InputDispatcher线程调用InputPublisher的publishKeyEvent向UI主线程发送input事件;
  • UI主线程接收到该事件后,调用InputConsumer的consumeEvents来处理该事件, 一路执行到ViewRootImpl.deliverInputEvent()方法;
  • UI主线程经过一系列的InputStage来处理, 当事件分发完成,则会执行finishInputEvent()方法.再进一步调用InputConsumer::sendFinishedSignal 告知InputDispatcher线程该时事件已处理完成.
  • InputDispatcher线程收到该事件后, 执行InputDispatcher::handleReceiveCallback();最终会调用doDispatchCycleFinishedLockedInterruptible()方法 ,将dispatchEntry事件从等待队列(waitQueue)中移除.

android input(二) InputDispatcher_第7张图片

应用端在setView时,会创建InputEventReceiver并传入Looper.myLooper(),即主线程的looper,初始化时获取主线程的MessageQueue,通过在native层looper监听fd事件,如果有按键消息时,会回调到NativeInputEventReceiver::handleEvent,里面会通过反射InputEventReceiver对象的dispatchInputEvent和dispatchMotionEventInfo

ANR处理

findTouchedWindowTargetsLocked和findFocusedWindowTargetsLocked会调用handleTargetsNotReadyLocked对原因进行记录,并安排时间尝试重试派发,或者引发ANR,除非调用了resetANRTimeoutsLocked
主要是以下4个场景,会有机会执行resetANRTimeoutsLocked

  • 解冻屏幕, 系统开/关机的时刻点 (thawInputDispatchingLw, setEventDispatchingLw)
  • wms聚焦app的改变 (WMS.setFocusedApp, WMS.removeAppToken)
  • 设置input filter的过程 (IMS.setInputFilter)
  • 再次分发事件的过程(dispatchOnceInnerLocked)
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
        const EventEntry* entry, std::vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
        ...
        sp<InputWindowHandle> focusedWindowHandle = getValueByKey(mFocusedWindowHandlesByDisplay, displayId);
    sp<InputApplicationHandle> focusedApplicationHandle = getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
    if (focusedWindowHandle == nullptr) {
        if (focusedApplicationHandle != nullptr) {
        //一般此类问题都是Android应用首次启动时会发生此类问题,此时我们应用本身需要检查一下我们的Android应用重写的Application onCreate方法,Android应用的启动界面是否在onCreate onStart方法中是否存在耗时操作。当然不排除系统原因造成的启动慢,直接导致ANR问题发生的情况
            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                    focusedApplicationHandle, nullptr, nextWakeupTime,
                    "Waiting because no window has focus but there is a "
                    "focused application that may eventually add a window "
                    "when it finishes starting up.");
           
         }
    }
    ...
    reason = checkWindowReadyForMoreInputLocked(currentTime,
            focusedWindowHandle, entry, "focused");
    if (!reason.empty()) {
        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                focusedApplicationHandle, focusedWindowHandle, nextWakeupTime, reason.c_str());
        goto Unresponsive;
    }
    ...
}

int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
        const MotionEntry* entry, std::vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
        bool* outConflictingPointerActions) {
        ...
        for (const TouchedWindow& touchedWindow : mTempTouchState.windows) {
        if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
            // Check whether the window is ready for more input.
            std::string reason = checkWindowReadyForMoreInputLocked(currentTime,
                    touchedWindow.windowHandle, entry, "touched");
            if (!reason.empty()) {
                injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                        nullptr, touchedWindow.windowHandle, nextWakeupTime, reason.c_str());
                goto Unresponsive;
            }
        }
    }
    ...
}

td::string InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
        const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
        const char* targetType) {
    // If the window is paused then keep waiting.
    if (windowHandle->getInfo()->paused) {
        return StringPrintf("Waiting because the %s window is paused.", targetType);
    }
    //首先获取窗口的Connection 
    ssize_t connectionIndex = getConnectionIndexLocked(
            getInputChannelLocked(windowHandle->getToken()));
    if (connectionIndex < 0) {
        return ...;
    }

    //InputPublisher被阻塞 
    sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
    if (connection->status != Connection::STATUS_NORMAL) {
        return StringPrintf(...);
    }

    // If the connection is backed up then keep waiting.
    if (connection->inputPublisherBlocked) {
        return StringPrintf(...);
    }

    //对按键事件来说,要求Connection必须处于空闲状态
    if (eventEntry->type == EventEntry::TYPE_KEY) {
        if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
            return StringPrintf(...);
        }
    } else {
      //对Motion事件来说,可以发送事件的条件相对宽松些,只要窗口能在0.5s内发送反馈即可 
        if (!connection->waitQueue.isEmpty()
                && currentTime >= connection->waitQueue.head->deliveryTime
                        + STREAM_AHEAD_EVENT_TIMEOUT) {
            return StringPrintf(...);
        }
    }
    return "";
}

判断窗口是否可以接受事件的依据有两个:ImputPublisher是否被阻塞以及Connection两个队列的状态。

  • InputPublisher的工作是将事件信息写入InputChannel中,如果窗口端因为某种原因迟迟
    未能从InputChannel中将事件读取就会导致SocketPair的写入缓冲区满。
  • Connection两个队列的状态体现了发送循环的状态。如果两个队列至少有一个队列为空,
    则表示Connection正处于发送循环的过程中,否则处于空闲状态。

对按键事件来说,仅当Connection处于空闲状态,也就是窗口已经完成对之前事件的响 应之后才会发送给窗口。因为之前的输入事件有可能会影响焦点窗口,进而影响按键事件的接收者。例如,用户快速地按下了两次BACK键,第一个BACK键将会发送给位于顶端的窗口,这个事件可能会导致窗口关闭,因此第一个BACK键的处理行为决定了第二个BACK应
该发送给哪个窗口。因此按键事件的发送要求窗口完成对所有之前事件的处理。
而Motion事件的条件则相对宽松些,允许Connection处于发送循环的过程中,但是如果等待队列中的第一个事件没能在0.5s获得反馈,则判定窗口处于未响应状态。这是因为Motion事件具有实时性的特点-----用户的意图就是希望输入事件发送给他所看到的窗口,所以不在乎之前事件的处理结果

焦点窗口的更新通过WindowManagerService.addView->WindowManagerService.updateFocusedWindowLocked->RootWindowContainer.updateFocusedWindowLocked->DisplayContent.updateFocusedWindowLocked->DisplayContent.findFocusedWindowIfNeeded

  • 每个DisplayContent都拥有自己的焦点窗口,然而真正的焦点窗口只
    有一个。于是Id越小的DispayContent的焦点窗口具有更高的优先级。设备的主屏幕Id为0,
    因此主屏幕将拥有最高的焦点优先级
  • 寻找焦点窗口的基本原则是沿Z-Order的顺序从上向下遍历窗口,第一个WindowState.canReceiveKeys()返回值为true的窗口拥有焦点
  • 可以得到焦点窗口的选择有如下原则:
    口 DisplayContent的Id值越低,其内部窗口拥有越高的焦点优先级。
    口 窗口在DisplayContent中的显示次序越靠前,其拥有越高的焦点优先级。
    口 焦点窗口必须处于正常显示状态(没有调用removeWindow()),处于可见状态,并且
    没有指定FLAG_NOT_FOCUSABLE选项。
    口 所有位于当前Activity之下的窗口不得获取焦点。若当前Activity以及其上都没有窗
    口 满足上述条件,则此DisplayContent没有焦点窗口
* frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
boolean updateFocusedWindowLockedupdateFocusedWindowLocked(int mode, boolean updateInputWindows) {
        ...
        int topFocusedDisplayId = INVALID_DISPLAY;
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final DisplayContent dc = mChildren.get(i);
            changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);
            ...
        }
       ...
        if (mTopFocusedDisplayId != topFocusedDisplayId) {
            mTopFocusedDisplayId = topFocusedDisplayId;
            mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
            mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
        }
        return changed;
    }
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
        const EventEntry* entry,
        const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t* nextWakeupTime, const char* reason) {
    if (applicationHandle == nullptr && windowHandle == nullptr) {
        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
        	//系统尚未完成启动
            ...
        }
    } else {
    	//如果是第一次发生窗口未响应的情况,则记录下未响应的窗口信息,并设置引发ANR的时间点
        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
        	//获取引发ANR的超时时间 
            nsecs_t timeout;
            if (windowHandle != nullptr) {
            	//如果有目标窗口,则获取由窗口所指定的超时时间 
                timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else if (applicationHandle != nullptr) {
             	//如果没有目标窗口,则从AMS获取超时时间 
                timeout = applicationHandle->getDispatchingTimeout(
                        DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else {
                timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
            }

            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
            //检测到未响应的时间 
            mInputTargetWaitStartTime = currentTime;
            //设置引发ANR的时间 
            mInputTargetWaitTimeoutTime = currentTime + timeout;
            //当引发ANR后将被置true 
            mInputTargetWaitTimeoutExpired = false;
            mInputTargetWaitApplicationToken.clear();
            if (windowHandle != nullptr) {
                mInputTargetWaitApplicationToken = windowHandle->getApplicationToken();
            }
            if (mInputTargetWaitApplicationToken == nullptr && applicationHandle != nullptr) {
                mInputTargetWaitApplicationToken = applicationHandle->getApplicationToken();
            }
        }
    }

    if (mInputTargetWaitTimeoutExpired) {
        return INPUT_EVENT_INJECTION_TIMED_OUT;
    }
	//检查是否引发ANR
    if (currentTime >= mInputTargetWaitTimeoutTime) {
    	//当前时间大于引发ANR的时间后,则引发ANR
        onANRLocked(currentTime, applicationHandle, windowHandle,
                entry->eventTime, mInputTargetWaitStartTime, reason);
        *nextWakeupTime = LONG_LONG_MIN;
        return INPUT_EVENT_INJECTION_PENDING;
    } else {
        //如果尚未到达引发ANR的时间点,设置nextWakeupTime后返回,等待下次再试 
        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
            *nextWakeupTime = mInputTargetWaitTimeoutTime;
        }
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

InputDispatcher从派发队列中获取了一个事件mPendingEvent,并为它查找目标窗口,然后通过checkWindowReadyForMoreInputLocked确定此窗口是否可以接收事件。如果可以则将事件放入窗口的Connection对象的发送队列中并启动发送循环,否则调用handleTargetsNotReadyLocked()计算引发ANR的时间点,然后通过返回INPUT_EVENT
INJECTION_PENDING停止对mPendingEvent的派发工作,并通过设置 nextWakeupTime使派发循环进入休眠状态。休眠的过程有可能因为窗口反馈到来、新输入事件到来或新的窗口信息到来而唤醒,派发线程便重新开始对mPendingEvent的派发过程,进而重新寻找目标窗口,再通过checkWindowReadyForMoreInputLocked()检查目标窗口是否准备好接收事件,如果可以接收事件,则将其提交给Connection进行发送,并重置之前所设置的ANR信息。否则再次进入handleTargetsNotReadyLocked,这时将当前时间与ANR时间进行对比,以决定引发ANR还是再次使派发线程进入休眠

发生ANR调用onANRLocked()的过程会将doNotifyANRLockedInterruptible加入mCommandQueue。 在下一轮InputDispatcher.dispatchOnce的过程中会先执行runCommandsLockedInterruptible()方法,取出 mCommandQueue队列的所有命令逐一执行,调用流程为InputDispatcher::doNotifyANRLockedInterruptible->NativeInputManager::notifyANR->IMS.notifyANR->InputManagerCallback.notifyANR

public long notifyANR(IBinder token, String reason) {
        ...
        synchronized (mService.mGlobalLock) {
            ...
            if (windowState != null) {
                Slog.i(TAG_WM, "Input event dispatching timed out "
                        + "sending to " + windowState.mAttrs.getTitle()
                        + ".  Reason: " + reason);
                int systemAlertLayer = mService.mPolicy.getWindowLayerFromTypeLw(
                        TYPE_APPLICATION_OVERLAY, windowState.mOwnerCanAddInternalSystemWindow);
                aboveSystem = windowState.mBaseLayer > systemAlertLayer;
            } else if (appWindowToken != null) {
                Slog.i(TAG_WM, "Input event dispatching timed out "
                        + "sending to application " + appWindowToken.stringName
                        + ".  Reason: " + reason);
            } else {
                Slog.i(TAG_WM, "Input event dispatching timed out "
                        + ".  Reason: " + reason);
            }
            mService.saveANRStateLocked(appWindowToken, windowState, reason);
        }
        ...
        return 0; // abort dispatching
    }

InputMonitor.notifyANR完成, 当发生ANR时system log中会出现以下信息, 并且TAG=WindowManager:
Input event dispatching timed out xxx. Reason: + reason, 其中xxx取值:

窗口类型: sending to windowState.mAttrs.getTitle()
应用类型: sending to application appWindowToken.stringName
其他类型: 则为空.

ANR reason主要有以下几类:


std::string InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
        const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
        const char* targetType) {
    // If the window is paused then keep waiting.
    if (windowHandle->getInfo()->paused) {
        return StringPrintf("Waiting because the %s window is paused.", targetType);
    }
    // If the window's connection is not registered then keep waiting.
    ssize_t connectionIndex = getConnectionIndexLocked(
            getInputChannelLocked(windowHandle->getToken()));
    if (connectionIndex < 0) {
        return StringPrintf("Waiting because the %s window's input channel is not "
                "registered with the input dispatcher.  The window may be in the process "
                "of being removed.", targetType);
    }
    // If the connection is dead then keep waiting.
    sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
    if (connection->status != Connection::STATUS_NORMAL) {
        return StringPrintf("Waiting because the %s window's input connection is %s."
                "The window may be in the process of being removed.", targetType,
                connection->getStatusLabel());
    }

    // If the connection is backed up then keep waiting.
    if (connection->inputPublisherBlocked) {
        return StringPrintf("Waiting because the %s window's input channel is full.  "
                "Outbound queue length: %d.  Wait queue length: %d.",
                targetType, connection->outboundQueue.count(), connection->waitQueue.count());
    }

    if (eventEntry->type == EventEntry::TYPE_KEY) {
        if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
            return StringPrintf("Waiting to send key event because the %s window has not "
                    "finished processing all of the input events that were previously "
                    "delivered to it.  Outbound queue length: %d.  Wait queue length: %d.",
                    targetType, connection->outboundQueue.count(), connection->waitQueue.count());
        }
    } else {
        if (!connection->waitQueue.isEmpty()
                && currentTime >= connection->waitQueue.head->deliveryTime
                        + STREAM_AHEAD_EVENT_TIMEOUT) {
            return StringPrintf("Waiting to send non-key event because the %s window has not "
                    "finished processing certain input events that were delivered to it over "
                    "%0.1fms ago.  Wait queue length: %d.  Wait queue head age: %0.1fms.",
                    targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f,
                    connection->waitQueue.count(),
                    (currentTime - connection->waitQueue.head->deliveryTime) * 0.000001f);
        }
    }
    return "";
}
  • 无窗口, 有应用:Waiting because no window has focus but there is a focused application that may eventually add a > window when it finishes starting up.

分析:
一般此类问题都是Android应用首次启动时会发生此类问题,此时我们应用本身需要检查一下我们的Android应用重写的Application onCreate方法,Android应用的启动界面是否在onCreate onStart方法中是否存在耗时操作。当然不排除系统原因造成的启动慢,直接导致ANR问题发生的情况
Android应用启动的时候都是先创建Application,创建号Application之后,才会执行应用启动的主Activity,再执行该Activity的onCreate,onStart,onResume方法,然后我们的应用窗口便是在onResume中才去向WindowManager添加注册的。因此在注册添加窗口之前,application或者启动的Activity的生命周期onCreate,onStart的任意方法,做了耗时操作,或者他们加载一起的执行时间过长,都是能够导致无窗口,有应用类型的Input ANR问题发生的

  • 非按键事件,事件等待队列不为空且头事件分发超时500ms:Waiting to send non-key event because the [targetType] window has not finished processing certain input events that were delivered to it over 500ms ago. Wait queue length: [waitQueue长度]. Wait queue head age: [等待时长].

分析:
这种情况一般时应用在处理touch事件超时引起的

  1. 比如在使用Handler向UI主线程的looper messagequeue post一个runnable callback,这个runnable里面便是执行的耗时超过5s,则会抛出异常,因为应用端的消息接收处理时在主线的一次Looper循环中读取并分发事件,处理完成后反馈到InputDIspatch,如果主线程的Looper耗时,事件超过5s没处理,则会引起anr
  2. 或者应用在处理touch事件耗时超过5s,则也会抛出anr
  3. CPU占用过大,引起事件处理不及时,比如启动多个线程同时并发执行

07-18 13:20:19.853 1948 2127 I WindowManager: Input event dispatching timed out sending to com.example.anrtest/com.example.anrtest.MainActivity. Reason: Waiting to send non-key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago. Wait queue length: 5. Wait queue head age: 8503.9ms

  • 按键事件,输出队列或事件等待队列不为空:Waiting to send key event because the [targetType] window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: [outboundQueue长度]. Wait queue length: [waitQueue长度].

分析:
这种情况一般时在应用处理key事件超时引起的,比如应用重载onKeyPreIme方法处理耗时,则会引起anr
07-18 13:41:07.925 1948 2127 I WindowManager: Input event dispatching timed out sending to com.example.anrtest/com.example.anrtest.MainActivity. Reason: Waiting to send key event because the focused window has not finished processing all of the input events that were previously delivered to it. Outbound queue length: 0. Wait queue length: 2.

下面四种情况比较少见

  • 窗口暂停: Waiting because the [targetType] window is paused.
  • 窗口未连接,窗口所在的进程可能正在被移除: Waiting because the [targetType] window’s input channel is not registered with the input dispatcher. The window may be in the process of being removed.
  • 窗口连接已死亡:Waiting because the [targetType] window’s input connection is [Connection.Status]. The window may be in the process of being removed.
  • 窗口连接已满:Waiting because the [targetType] window’s input channel is full. Outbound queue length: [outboundQueue长度]. Wait queue length: [waitQueue长度].

targetType: 取值为”focused”或者”touched”
Connection.Status: 取值为”NORMAL”,”BROKEN”,”ZOMBIE

错误:

E InputChannel-JNI: Error 24 dup channel fd 74
我们可以在系统源码中找到它的定义 ERRNO_VALUE(EMFILE, 24);指的就是我们的应用进程fd泄漏,越过上限值
进入应用进程/proc/1948 查看limits文件
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size 0 unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 21523 21523 processes
Max open files 31744 32768 files
这里Max open files 31744就是系统对当前进程fd打开数量的限制
cd 到fd目录,然后ls -l | wc -l 查看进程创建了多少fd

你可能感兴趣的:(技术总结)