企业网站群建设的原因,西安免费信息推广平台,wordpress网易云,网站 永久关停 请示Android Input事件机制 Android系统是由事件驱动的#xff0c;而Input是最常见的事件之一#xff0c;用户的点击、滑动、长按等操作#xff0c;都属于Input事件驱动#xff0c;其中的核心就是InputReader和InputDispatcher。InputReader和InputDispatcher是跑在system_serv…Android Input事件机制 Android系统是由事件驱动的而Input是最常见的事件之一用户的点击、滑动、长按等操作都属于Input事件驱动其中的核心就是InputReader和InputDispatcher。InputReader和InputDispatcher是跑在system_server进程中的两个Native循环线程负责读取和分发Input事件。整体处理流程大致如下 触摸屏会按照屏幕硬件的触控采样率周期每隔几秒扫描一次如果有触控时间就会上报到对应设备驱动系统封装了一个叫EventHub的对象它利用INotify和epoll机制监听/dev/input目录下的Input设备驱动节点通过EventHug的getEvents接口就可以监听并获取到Input事件 InputReader负责从EventHub里面把Input事件读取出来然后交给InputDispatcher进行事件分发 InputDispatcher在拿到InputReader获取的事件之后对事件进行包装后寻找并分发到目标窗口 InboundQueue队列iq中放着InputDispatcher从InputReader中拿到的Input事件 OutboundQueue队列oq里面放的是即将要指派给各个目标窗口App的事件 WaitQueue队列wq里面记录的是已经派发给App但是App还在处理没有返回处理成功的事件 PendingInputEventQueueaq中记录的是应用需要处理的Input事件这里可以看到Input事件已经传递到应用进程 deliverInputEvent标识App UI Thread被Input事件唤醒 InputResponse标识Input事件区域这里可以看到一个Input_Down事件 若干个Input_Move 一个Input_Up事件的处理阶段都被算到了这里 App响应处理Input事件内部会在其界面View数中逐层分发和处理。 结合Systrace分析
从上面的系统机制的分析可以看出整个Input触控时间的分发与处理主要涉及到两个进程一个是system_server系统进程另一个是当前焦点窗口所属的Setting应用进程。
system_server进程的处理过程 当用户手指在Setting应用界面滑动时系统system_server进程中的Native线程InputReader会从EventHub中读取利用Linux的epoll机制监听到的屏幕驱动上报的Input触控事件然后唤醒另一条Native线程InputDIspatcher负责进行事件的进一步分发处理。 InputDispatcher被唤醒后会先将事件放到InboundQueue队列iq中然后找到具体处理此Input事件的应用目标窗口并将Input事件放入对应的应用目标窗口的OutboundQueue队列oq中等待进一步通过SocketPair双工信道发送Input事件到应用目标窗口中。 最后当事件发送给具体的应用目标窗口后会将事件移动到WaitQueue队列wq中一直等待收到目标应用处理Input事件完成后反馈后再从队列中移除如果5秒钟没有收到目标应用窗口处理完成此次Input事件的反馈就会报该应该ANR异常事件。以上整个过程在Android系统AOSP源码中都加有相应的Systrace tag如下图Systrace所示 应用进程的处理过程 当Input触控时间通过socket传递到Setting应用进程这边后会唤醒应用的UI线程在ViewRootImpl#deliverInputEvent的流程中进行Input事件的具体分发与处理。具体的处理流程 先交给之前在添加应用PhoneWindow窗口时的ViewRootImpl#setView流程中创建的多个不同类型的InputUsage中依次进行处理比如对输入法处理逻辑的封装ImeInputUsage某些key类型的Input事件会由它先交给输入法进程处理完成后再交给应用窗口的InputUsage处理整个处理流程是按照责任链的设计模式进行 然后会交给负责应用窗口Input事件分发处理的ViewPostImeInputUsage中具体处理这里面会从View布局树的根节点DecorView开始遍历整个View树上的每一个子View或ViewGroup控件执行事件的分发、拦截、处理的逻辑 最后触控时间处理完成后会调用finishInputEvent结束应用对触控事件处理逻辑这里面会通过JNI调用到Native层InputConsumer的sendFinishedSignal函数中通过socket消息通知系统框架中的InputDIspatcher该Input事件处理完成触发从wq队列中及时移除待处理事件以免报ANR异常。 一次滑动过程的触控交互的InputResponse区域中一般会包含一个Input的ACTION_DOWN事件 多个ACTION_MOVE事件 一个ACTION_UP事件Setting应用界面中的相关View控件在收到多个ACTION_MOVE触控事件后经过判断用户手指滑动行为一般会调用View#invalidate等相关接口触发UI线程的绘制上帧更新画面的操作。 Android Input初始化及InputReader流程
概述
当输入设备可用时Linux内核会在/dev/input/下创建对应名为event0~n或其他名称的设备节点。而当输入设备不可用时则会将对应的节点删除。
Android输入系统的工作原理概括来说就是监控/dev/input/下的所有设备节点当某个节点有数据可读时将数据读出并进行一系列的翻译加工然后再所有的窗户中寻找合适的事件接收者并派发给它。
getevent与sendevent
getevent与sendevent两个工具可以从设备节点中直接读取输入事件或写入输入事件。
Getevent 由于getevent不会对事件数据做任何加工因此其输出的内容是由内核提供的最原始的事件输出是十六进制的 adb shell getevent [-选项] [device_path]。 adb shell getevent –t 查看当前按下按键的值值0x01表示按下0x00则表示抬起。 按下返回 [1556162527.777123] /dev/input/event6: 0001 009e 00000001 [1556162527.777123] /dev/input/event6: 0000 0000 00000000 送开返回 [1556162530.504152] /dev/input/event6: 0003 0030 00000000 [1556162530.504152] /dev/input/event6: 0003 0032 00000000 [1556162530.504152] /dev/input/event6: 0000 0002 00000000 [1556162530.504152] /dev/input/event6: 0000 0000 00000000 [1556162530.525527] /dev/input/event6: 0001 009e 00000000 [1556162530.525527] /dev/input/event6: 0000 0000 00000000 事件类型0001事件代码009e以及事件的值00000001。 Sendevent
实现模拟用户输入的功能sendevent的参数为十进制。 sendevent 节点路径 类型代码 值 adb shell sendevent /dev/input/event0 1 116 1 #按下电源键 adb shell sendevent /dev/input/event0 1 116 0 #抬起电源键 adb shell input keyevent 4 # 返回按键
Android系统Input事件处理流程 内核将原始事件写入到设备节点中InputReader不断地通过EventHub将原始事件取出来并翻译加工成Android输入事件然后交给InputDIspatcher。InputDIspatcher根据WMS提供的窗口信息将事件交给合适的窗口。窗口的ViewRootImpl对象再沿着控件树将事件派发给感兴趣的控件。控件对其收到的事件做出响应更新自己的画面、执行特定的动作。 Linux内核接收输入设备的中断并将原始事件的数据写入到设备节点中。 设备节点作为内核的IMS的桥梁它将原始事件的数据暴露给用户控件以便IMS可以从中读取事件。 InputManagerService一个Android系统服务它分为Java层和Native层两个部分。Java层负责与WMS的通信。而Native层则是InputReader和InputDIspatcher两个输入系统关键组件的运行容器。 EventHub直接访问所有的设备节点。并且正如其名字所描述的它通过一个名为getEvents()函数将所有输入系统相关的待处理的底层事件返回给使用者。这些事件包括原始输入事件、设备节点的增删等。 InputReader是IMS中的关键组件之一。它运行于一个独立的线程中负责管理输入设备的列表与配置以及进行输入事件的加工处理。它通过其线程循环不断地通过getEvent()函数从EventHub中将事件取出并进行处理。对于设备节点的增删事件它会更新输入设备列表于配置。对于原始输入事件InputReader对其进行翻译、组装、封装为包含了更多信息、更具可读性的输入事件然后交给InputDIspatcher进行派发。 InputReaderPolicy它为InputReader的事件加工处理提供一些策略配置例如键盘布局信息等。 InputDispatcher是IMS中另一个关键组件。它也运行于一个独立的线程中。InputDIspatcher中保管了来自WMS的所有窗口的信息其收到来自InputReader的输入事件后会在其保管的窗口中寻找合适的窗口并将事件派发给此窗口。 InputDIspatcherPolicy它为InputDIspatcher的派发过程提供策略控制。例如截取某些特定的输入事件来作特殊用途或者阻止将某些事件派发给目标窗口。一个典型的例子就是HOME键被InputDIspatcherPolicy截取到PhoneWindowManager中进行处理并阻止窗口收到HOME键按下的事件。 WMS虽然不是输入系统中的一员但是它却对InputDIspatcher的正常工作起到了至关重要的作用。当新建窗口时WMS为新窗口和IMS创建了事件传递所用的通道。另外WMS还将所有窗口的信息包括窗口的可点击区域焦点窗口等信息实时地更新到IMS的InputDIspatcher中使得InputDIspatcher可以正确的将事件派发到指定的窗口。 ViewRootImpl对于某些窗口如壁纸窗口、SurfaceView的窗口来说窗口即是输入事件派发的终点。而对于其他的如Activity、对话框等使用了Android控件系统的窗口来说输入事件的终点是控件View。ViewRootImpl将窗口所收到的输入事件沿着控件树将事件派发给感兴趣的控件。 初始化 InputManagerService初始化 这段代码是Android系统中InputManagerService的启动过程。首先在SystemServer服务的startOtherServices()方法中创建并启动了InputManagerService实例。然后在InputManagerService的构造函数中初始化了Native层和Java层的通信并将Java层的InputManagerService对象和消息队列的Looper传递给Native层。最后在nativeInit方法中创建了一个NativeInputManager对象这个对象是Java层和Native层之间的桥梁并将这个对象的指针返回给Java层的InputManagerService保存在mPtr成员变量中。 具体来说startOtherServices()方法中 创建了InputManagerService实例。 调用了InputManagerService的setWindowManagerCallbacks()方法将WindowManager的回调传递给InputManagerService。 调用了InputManagerService的start()方法启动了InputManagerService。 在InputManagerService的构造函数中 保存了上下文信息。 创建了一个名为InputManagerHandler的Handler用于处理输入事件。 调用了native方法nativeInit()传入了上下文对象、InputManagerService对象和消息队列的Looper。 将InputManagerService注册为InputManagerInternal服务的本地服务。 在nativeInit()方法中 从Java层的消息队列对象中获取了消息队列。 创建了一个NativeInputManager对象这个对象是Java层和Native层之间的桥梁。 将NativeInputManager对象的指针转换为jlong类型并返回给Java层的InputManagerService保存在mPtr成员变量中。 这样Java层的InputManagerService就可以通过mPtr成员变量与Native层的NativeInputManager进行通信了。这个过程是Android系统启动过程中的一部分用于初始化和管理输入事件包括触摸、按键等输入事件的处理。 * frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() {...traceBeginAndSlog(StartInputManagerService);inputManager new InputManagerService(context);traceEnd();...traceBeginAndSlog(StartInputManager);inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());inputManager.start();traceEnd();...
}* frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public InputManagerService(Context context) {this.mContext context;this.mHandler new InputManagerHandler(DisplayThread.get().getLooper());...mPtr nativeInit(this, mContext, mHandler.getLooper().getQueue());...LocalServices.addService(InputManagerInternal.class, new LocalService());
}* frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,jobject serviceObj, jobject contextObj, jobject messageQueueObj) {spMessageQueue messageQueue android_os_MessageQueue_getMessageQueue(env, messageQueueObj);.../* 新建了一个NativeInputManager对象NativeInputManager此对象将是Native层组件与Java层IMS进行通信的桥梁 */NativeInputManager* im new NativeInputManager(contextObj, serviceObj, messageQueue-getLooper());im-incStrong(0);//返回了NativeInputManager对象的指针给Java层的IMSIMS将其保存在mPtr成员变量中return reinterpret_castjlong(im);
}
在nativeInit函数中将Java层的MessageQueue转换为native层的MessageQueue然后再取出Looper用于NativeInputManager的初始化。NativeInputManager是Java层与Native层互相通信的桥梁它实现了InputReaderPolicyInterface与InputDispatcherPolicyInterface两个接口通过JNI回调Java层的IMS由它完成决策。
NativeInputManager的初始化
这个过程做了以下事情
将Java层的InputManagerService转换为native层的InputManagerService存储在mServiceObj中 -创建InputManager。 这段代码是Android系统中InputManager的初始化过程。在InputManager的构造函数中首先创建了InputDispatcher和InputClassifier对象这两个对象分别负责分发输入事件和输入事件的分类。然后创建了InputReader对象这个对象负责从设备读取输入事件。最后创建了InputReaderThread和InputDispatcherThread两个线程分别用于处理输入事件的读取和分发。 具体来说 InputManager的构造函数中 创建了InputDispatcher对象用于分发输入事件。 创建了InputClassifier对象用于对输入事件进行分类。 调用createInputReader()方法创建了InputReader对象这个对象负责从设备读取输入事件。 调用initialize()方法创建了InputReaderThread和InputDispatcherThread两个线程。 createInputReader()方法中 创建了InputReader对象这个对象负责从设备读取输入事件。 initialize()方法中 创建了InputReaderThread对象这个线程用于处理输入事件的读取。 创建了InputDispatcherThread对象这个线程用于处理输入事件的分发。 这样InputManager就可以通过这两个线程来处理输入事件了。 这个过程是Android系统启动过程中的一部分用于初始化和管理输入事件包括触摸、按键等输入事件的处理。 * frameworks/native/services/inputflinger/InputManager.cpp
InputManager::InputManager(const spInputReaderPolicyInterface readerPolicy,const spInputDispatcherPolicyInterface dispatcherPolicy) {mDispatcher new InputDispatcher(dispatcherPolicy);mClassifier new InputClassifier(mDispatcher);mReader createInputReader(readerPolicy, mClassifier);initialize();
}spInputReaderInterface createInputReader(const spInputReaderPolicyInterface policy,const spInputListenerInterface listener) {return new InputReader(new EventHub(), policy, listener);
}void InputManager::initialize() {mReaderThread new InputReaderThread(mReader);mDispatcherThread new InputDispatcherThread(mDispatcher);
}
InputManager的初始化
InputManager的构造函数也比较简洁它创建了四个对象分别为IMS的核心参与者InputReader与InputDispatcher以及它们所在的线程InputReaderThread与InputDispatcherThread。注意InputManager的构造函数的参数readerPolicy与dispatcherPolicy它们都是NativeInputManager。
InputDispatcher会创建自己线程的Looper以及设置根据传入的dispatchPolicy设置分发规则。InputReader则会将传入的InputDispatcher封装为监听对象存起来并创建一个EventHub。 启动 system_server执行InputManagerService.start()函数以启动IMS在start()方法中做了以下事情 调用nativeStart方法其实就是调用InputManager的start()方法。 将InputManagerService交给WatchDog监控。 注册触控点速度、显示触控的观察者并注册广播监控它们。 主动调用updateXXX方法更新初始化。 InputManager的start()启动InputDispatcherThread和InputReaderThread开始监听。 当两个线程启动后InputReader在其线程循环中不断地从EventHub中抽取原始输入事件进行加工处理后将加工所得的事件放入InputDispatcher的派发发队列中。InputDispatcher则在其线程循环中将派发队列中的事件取出查找合适的窗口将事件写入到窗口的事件接收管道中。窗口事件接收线程的Looper从管道中将事件取出交由事件处理函数进行事件响应。 这段代码是Android系统中InputManagerService的启动过程。在Java层的start()方法中调用了native方法nativeStart()传入了mPtr成员变量这个变量是Java层和Native层之间的桥梁。 在Native层的InputManager的start()方法中首先启动了InputDispatcherThread线程这个线程负责分发输入事件。然后启动了InputReaderThread线程这个线程负责从设备读取输入事件。 具体来说 InputManagerService的start()方法中 调用了native方法nativeStart()传入了mPtr成员变量这个变量是Java层和Native层之间的桥梁。 InputManager的start()方法中 调用mDispatcherThread-run()方法启动了InputDispatcherThread线程这个线程负责分发输入事件。 调用mReaderThread-run()方法启动了InputReaderThread线程这个线程负责从设备读取输入事件。 这样InputManager就可以通过这两个线程来处理输入事件了。 这个过程是Android系统启动过程中的一部分用于初始化和管理输入事件包括触摸、按键等输入事件的处理。 * frameworks/base/services/core/java/com/android/server/input/InputManagerService.javapublic void start() {Slog.i(TAG, Starting input manager);nativeStart(mPtr);...updatePointerSpeedFromSettings();updateShowTouchesFromSettings();updateAccessibilityLargePointerFromSettings();
}* frameworks/native/services/inputflinger/InputManager.cpp
status_t InputManager::start() {status_t result mDispatcherThread-run(InputDispatcher, PRIORITY_URGENT_DISPLAY);...result mReaderThread-run(InputReader, PRIORITY_URGENT_DISPLAY);...
}
InputReaderThread 启动后循环执行mReader-loopOnce()loopOnce中会调用mEventHub-getEvents读取事件读取的结果存储在参数mEventBuffer中返回值表示事件的个数当EventHub中无事件可以抽取时此函数的调用将会阻塞直到事件到来或者超时。 读取到了事件就会调用processEventsLocked处理事件对于设备事件此函数对根据设备的可用性加载或移除设备对应的配置信息。对于原始输入事件则在进行转译、封装与加工后将结果暂存到mQueuedListener中处理完成后调用getInputDevicesLocked获取输入设备信息。 调用mPolicy-notifyInputDevicesChanged函数利用InputManagerService的代理通过Handler发送MSG_DELIVER_INPUT_DEVICES_CHANGED消息通知输入设备发生了变化。 最后调用mQueuedListener-flush(),将事件队列中的所有事件交给在InputReader中注册过的InputDIspatcher。 注意 C层的Thread类与Java层的Thread类有着一个显著的不同。C层Thread类内建了线程循环threadLoop()就是一次循环而已只要返回值为truethreadLoop()将会不断地被内建的循环调用。这也是InputReader.loopOnce()函数名称的由来。而Java层Thread类的run()函数则是整个线程的全部一旦其退出线程也便完结。 这段代码是Android系统中InputReader的循环处理过程。在Native层的InputReaderThread的threadLoop()方法中调用了InputReader的loopOnce()方法这个方法会循环读取输入事件。 在InputReader的loopOnce()方法中首先从EventHub中读取输入事件然后处理这些事件。如果输入设备发生了变化就通知InputReaderPolicy。最后调用mQueuedListener的flush()方法将处理过的输入事件发送给监听者。 具体来说 InputReaderThread的threadLoop()方法中 调用了mReader的loopOnce()方法这个方法会循环读取输入事件。 InputReader的loopOnce()方法中 从EventHub中读取输入事件。 处理这些事件。 如果输入设备发生了变化就通知InputReaderPolicy。 调用mQueuedListener的flush()方法将处理过的输入事件发送给监听者。 这样InputReader就可以通过这个循环来处理输入事件了。 这个过程是Android系统启动过程中的一部分用于初始化和管理输入事件包括触摸、按键等输入事件的处理。 * frameworks/native/services/inputflinger/InputReaderBase.cpp
bool InputReaderThread::threadLoop() {mReader-loopOnce();return true;
}* frameworks/native/services/inputflinger/InputReader.cpp
void InputReader::loopOnce() {...size_t count mEventHub-getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);{ // acquire lockAutoMutex _l(mLock);mReaderIsAliveCondition.broadcast();if (count) {processEventsLocked(mEventBuffer, count);}...if (oldGeneration ! mGeneration) {inputDevicesChanged true;getInputDevicesLocked(inputDevices);}} // release lock// Send out a message that the describes the changed input devices.if (inputDevicesChanged) {mPolicy-notifyInputDevicesChanged(inputDevices);}mQueuedListener-flush();
}
EventHub
InputReader在其线程循环中的第一个工作便是从EventHub中读取一批未处理的事件。 EventHub的直译是事件集线器顾名思义它将所有的输入事件通过一个接口getEvents()将从多个输入设备节点中读取的事件交给InputReader是输入系统最底层的一个组件。 这段代码是Android系统中EventHub的初始化过程。在EventHub的构造函数中首先创建了一个epoll对象和inotify对象用于监听设备节点的增删事件。然后将设备节点的路径/dev/input作为监听对象添加到inotify对象中当此文件夹下的设备节点发生创建与删除事件时都可以通过inotify对象读取事件的详细信息。接着将inotify对象和名为wakeFds的匿名管道用于唤醒InputReader线程作为epoll的监控对象并注册到epoll对象中。 具体来说 EventHub的构造函数中 创建了一个epoll对象。 创建了一个inotify对象用于监听设备节点的增删事件。 将设备节点的路径/dev/input作为监听对象添加到inotify对象中。 创建了一个名为wakeFds的匿名管道并将管道读取端的描述符的可读事件注册到epoll对象中。 在epoll对象中注册了inotify对象和wakeFds管道用于监听设备节点的增删事件和唤醒InputReader线程。 这样EventHub就可以通过epoll和inotify来监听设备节点的变化并及时处理这些事件了。 这个过程是Android系统启动过程中的一部分用于初始化和管理输入事件包括触摸、按键等输入事件的处理。 * frameworks/native/services/inputflinger/EventHub.cpp
EventHub::EventHub(void) :mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),mOpeningDevices(nullptr), mClosingDevices(nullptr),mNeedToSendFinishedDeviceScan(false),mNeedToReopenDevices(false), mNeedToScanDevices(true),mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);//创建一个epoll对象mEpollFd epoll_create1(EPOLL_CLOEXEC);LOG_ALWAYS_FATAL_IF(mEpollFd 0, Could not create epoll instance: %s, strerror(errno));//创建一个inotify对象。这个inotify对象将被用来监听设备节点的增删事件mINotifyFd inotify_init();//将存储设备节点的路径/dev/input作为监听对象添加到inotify对象中。当此文件夹下的设备节点//发生创建与删除事件时都可以通过mINotifyFd读取事件的详细信息mInputWd inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);...struct epoll_event eventItem;memset(eventItem, 0, sizeof(eventItem));eventItem.events EPOLLIN;eventItem.data.fd mINotifyFd;//将mINotifyFd作为epoll的一个监控对象。当inotify事件到来时epoll_wait()将//立刻返回EventHub便可从mINotifyFd中读取设备节点的增删信息并作相应处理int result epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, eventItem);LOG_ALWAYS_FATAL_IF(result ! 0, Could not add INotify to epoll instance. errno%d, errno);/*创建了一个名为wakeFds的匿名管道并将管道读取端的描述符的可读事件注册到epoll对象中。因为InputReader在执行getEvents()时会因无事件而导致其线程阻塞在epoll_wait()的调用里然而有时希望能够立刻唤醒InputReader线程使其处理一些请求。此时只需向wakeFds管道的写入端写入任意数据此时读取端有数据可读使得epoll_wait()得以返回从而达到唤醒InputReader线程的目的*/int wakeFds[2];result pipe(wakeFds);LOG_ALWAYS_FATAL_IF(result ! 0, Could not create wake pipe. errno%d, errno);mWakeReadPipeFd wakeFds[0];mWakeWritePipeFd wakeFds[1];...eventItem.data.fd mWakeReadPipeFd;result epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, eventItem);...
}
INotify
INotify是一个Linux内核所提供的一个文件系统变化通知机制。它可以为应用程序监控文件系统的变化如文件的创建、删除、读写等。INotify机制有两个基本对象分别为inotify对象与watch对象都使用文件描述符表示。 inotify对象对应了一个队列应用程序可以向inotify对象添加多个监听。当被监听的事件发生时可以通过read()函数从inotify对象中将事件信息读取出来。Inotify对象可以通过inotify_init创建。 watch对象则用来描述文件系统的变化事件的监听。它是一个二元组包括监听目标和事件掩码两个元素。监听目标是文件系统的一个路径可以是文件也可以是文件夹。而事件掩码则表示了需要需要监听的事件类型掩码中的每一位代表一种事件。可以监听的事件种类很多其中就包括文件的创建(IN_CREATE)与删除(IN_DELETE)。以下代码即可将一个用于监听输入设备节点的创建与删除的watch对象添加到inotify对象中
int wd inotify_add_watch (inotifyFd, “/dev/input”,IN_CREATE | IN_DELETE);
完成上述watch对象的添加后当/dev/input/下的设备节点发生创建与删除操作时都会将相应的事件信息写入到inotifyFd所描述的inotify对象中此时可以通过read()函数从inotifyFd描述符中将事件信息读取出来。
事件信息使用结构体inotify_event进行描述
struct inotify_event {__s32 wd; /* 事件对应的Watch对象的描述符 */__u32 mask; /* 事件类型例如文件被删除此处值为IN_DELETE */__u32 cookie;__u32 len; /* name字段的长度 */char name[0]; /* 可变长的字段用于存储产生此事件的文件路径*/
}; 当有监听事件发生时可以通过如下方式将一个或多个未读取的事件信息读取出来 size_t len read (inotifyFd, events_buf,BUF_LEN); 其中events_buf是inotify_event的数组指针能够读取的事件数量由取决于数组的长度。成功读取事件信息后便可根据inotify_event结构体的字段判断事件类型以及产生事件的文件路径了。 INotify机制的使用过程 通过inotify_init()创建一个inotify对象。 通过inotify_add_watch将一个或多个监听添加到inotify对象中。 通过read()函数从inotify对象中读取监听事件。当没有新事件发生时inotify对象中无任何可读数据。 通过INotify机制避免了轮询文件系统的麻烦但是还有一个问题INotify机制并不是通过回调的方式通知事件而需要使用者主动从INotify对象中进行事件读取这里借助Linux的Epoll机制。
Epoll
Epoll可以使用一次等待监听多个描述符的可读/可写状态。等待返回时携带了可读的描述符或自定义的数据使用者可以据此读取所需的数据后可以再次进入等待。因此不需要为每个描述符创建独立的线程进行阻塞读取避免了资源浪费的同时又可以获得较快的响应速度。 epoll_create(int max_fds)创建一个epoll对象的描述符之后对epoll的操作均使用这个描述符完成。max_fds参数表示了此epoll对象可以监听的描述符的最大数量。 int epoll_create1(int flag); 当flag是0时表示和epoll_create函数完全一样不需要size的提示了 当flag EPOLL_CLOEXEC创建的epfd会设置FD_CLOEXEC 当flag EPOLL_NONBLOCK创建的epfd会设置为非阻塞 FD_CLOEXEC是fd的一个标识说明用来设置文件close-on-exec状态的。当close-on-exec状态为0时调用exec时fd不会被关闭状态非零时则会被关闭这样做可以防止fd泄露给执行exec后的进程 epoll_ctl (int epfd, int op,int fd, struct epoll_event *event)用于管理注册事件的函数。这个函数可以增加/删除/修改事件的注册。 op表示了何种操作包括EPOLL_CTL_ADD/DEL/MOD三种分别表示增加/删除/修改注册事件。 int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout)用于等待事件的到来。当此函数返回时events数组参数中将会包含产生事件的文件描述符。函数返回值表示获取了多少个事件。 Epoll机制的使用过程 创建epoll对象 Int epfd epoll_create(MAX_FDS)。 填充epoll_event结构体以描述监控事件。 通过epoll_ctl()函数将此描述符与epoll_event结构体注册进epoll对象重复这个步骤可以将多个文件描述符的多种事件监听注册到epoll对象中。 使用epoll_wait()函数等待事件会使调用者陷入等待状态直到其注册的事件之一发生之后才会返回并且携带了刚刚发生的事件的详细信息在如getEvents中调用。 EventHub的创建过程 创建mEpollFd用于监听是否有数据有无事件可读。创建mINotifyFd将它注册到DEVICE_PATH这里路径就是/dev/input节点并将它交给内核用于监听该设备节点的增删数据事件。那么只要有数据增删的事件到来epoll_wait()就会返回使得EventHub能收到来自系统的通知并获取事件的详细信息。调用epoll_ctl函数将mEpollFd和mINotifyFd注册到epoll中。定义int wakeFd[2]作为事件传输管道的读写两端并将读端注册到epoll中让mEpollFd监听。 EventHub 是 Android 系统中负责处理输入事件的类它位于 frameworks/native/services/inputflinger 目录下。这个类的主要职责是监听和管理 /dev/input 目录下的所有输入设备并将这些设备的事件转换为 Android 系统可以理解的事件格式。 在 getEvents 方法中它首先检查是否需要扫描设备如果需要它会调用 scanDevicesLocked 方法来遍历 /dev/input 目录下的所有设备打开这些设备并存储到 Device 结构体中同时将这些设备的文件描述符的可读事件注册到 Epoll 中。当设备的输入事件到来时Epoll 会在 getEvents 函数的调用中产生一条 epoll 事件。 然后它遍历 mClosingDevices 链表为每一个已卸载的设备生成 DEVICE_REMOVED 事件。 接着它通过 Epoll 事件的 data 字段确定此事件表示了 mINotifyFd 可读即 Epoll 事件的处理。如果 mPendingINotify 事件待处理它会调用 readNotifyLocked 函数来读取并处理存储在 mINotifyFd 中的 INotify 事件。 这个过程会一直重复直到满足某个条件例如超时或者缓冲区满。 这个过程的关键点在于 scanDevicesLocked 和 readNotifyLocked 方法它们分别负责扫描设备和处理 INotify 事件。这两个方法的具体实现细节没有在提供的代码片段中展示但它们应该是用来打开设备、注册 Epoll 事件、处理设备卸载和 INotify 事件的。 请注意这是一个简化的解释实际的实现可能会更复杂包括错误处理、线程同步、设备状态管理等。 * frameworks/native/services/inputflinger/EventHub.cpp
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {...for (;;) {...if (mNeedToScanDevices) {mNeedToScanDevices false;//遍历/dev/input下所有可用的输入设备打开并存储到Device结构体//将设备节点的描述符的可读事件注册到Epoll中,当此设备的输入事件到来时Epoll会在getEvents()函数的调用中产生一条epoll事件scanDevicesLocked();mNeedToSendFinishedDeviceScan true;}//遍历mClosingDevices链表为每一个已卸载的设备生成DEVICE_REMOVED事件while (mClosingDevices) {...}// 通过Epoll事件的data字段确定此事件表示了mINotifyFd可读及Epoll事件的处理while (mPendingEventIndex mPendingEventCount) {}//如果INotify事件待处理if (mPendingINotify mPendingEventIndex mPendingEventCount) {mPendingINotify false;//调用readNotifyLocked()函数读取并处理存储在mINotifyFd中的INotify事件readNotifyLocked();deviceChanged true;}}
}
getEvents()通过Epoll事件的data.u32字段在mDevices列表中查找已加载的设备并从设备的文件描述符中读取原始输入事件列表。从文件描述符中读取的原始输入事件存储在input_event结构体中这个结构体的四个字段存储了事件的事件戳、类型、代码与值四个元素。然后逐一将input_event的数据转存到RawEvent中并保存至buffer以返回给调用者。 InputReader
InputReader是在InputReaderThread中启动的InputReaderThread和InputDispatcherThread的定义是类似的也是继承了Thread并定义了threadLoop纯虚函数。如果处理的事件为键盘输入事件则调用时序图如下所示。 原始事件的入口函数是ProcessEventsLocked根据事件类型区分原始输入事件还是设备增删事件对于原始输入事件EventHub会讲属于同意输入设备的原始输入事件放在一起因此使用processEventsForDeviceLocked同时处理来自同意输入设备的一批事件。 InputReader 是 Android 系统中负责处理输入事件的类它位于 frameworks/native/services/inputflinger 目录下。这个类的主要职责是接收和处理来自 EventHub 的原始输入事件。 在 processEventsLocked 方法中它遍历所有的原始事件。对于每一条原始事件根据事件类型它会调用 processEventsForDeviceLocked 方法来处理。如果事件类型是原始输入事件那么它会调用 InputDevice 的 process 方法来处理。如果事件类型是设备增删事件那么它会处理设备增删事件。 在 processEventsForDeviceLocked 方法中它首先检查设备是否存在如果设备被忽略那么它会忽略这条事件。如果设备存在那么它会调用 InputDevice 的 process 方法来处理事件。 这个过程的关键点在于 InputDevice 的 process 方法它是用来处理输入事件的。这个方法的具体实现细节没有在提供的代码片段中展示但它是用来处理输入事件的。 请注意这是一个简化的解释实际的实现可能会更复杂包括错误处理、设备状态管理等。 * frameworks/native/services/inputflinger/InputReader.cpp
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {for (const RawEvent* rawEvent rawEvents; count;) {int32_t type rawEvent-type;size_t batchSize 1;//根据事件类型区分原始输入事件还是设备增删事件if (type EventHubInterface::FIRST_SYNTHETIC_EVENT) {int32_t deviceId rawEvent-deviceId;...//数据事件的处理batchSize表示属于此设备的输入事件个数processEventsForDeviceLocked(deviceId, rawEvent, batchSize);} else {//处理设备增删事件...}count - batchSize;rawEvent batchSize;}
}void InputReader::processEventsForDeviceLocked(int32_t deviceId,const RawEvent* rawEvents, size_t count) {//InputReader保存了mDevices字典以设备ID为键值存储了一系列的InputDevice对象ssize_t deviceIndex mDevices.indexOfKey(deviceId);if (deviceIndex 0) {ALOGW(Discarding event for unknown deviceId %d., deviceId);return;}InputDevice* device mDevices.valueAt(deviceIndex);if (device-isIgnored()) {//ALOGD(Discarding event for ignored deviceId %d., deviceId);return;}//调用InputDevice的process处理事件device-process(rawEvents, count);
}
processEventsForDeviceLocked中InputReader保存了mDevices字典在processEventsLocked的处理设备增删阶段初始化然后调用InputDevice::process处理事件。
InputDevice
InputReader的InputDevice存储输入设备的信息与EventHub一样InputDevice描述了一个输入设备并且以设备ID为键保存在mDevices中InputReader::InputDevice和EventHub::Device结构体类似也保存了设备的ID、厂商信息、设备所属类别。只是InputDevice多了一个InputMapper列表。
InputMapper
InputMapper是InputReader中实际进行原始输入事件加工的场所有一系列子类分别用于加工不同类型的原始输入事件。这样使得InputDevice不需要知道哪一个InputMapper可以处理一个原始输入事件只须将一个事件逐一交给每一个InputMapper尝试处理如果InputMapper可以接受这个事件则处理否则什么都不做。 InputReader 类是 Android 系统中负责管理输入设备和处理输入事件的类它位于 frameworks/native/services/inputflinger 目录下。 在 addDeviceLocked 方法中它首先检查是否已经添加了相同的设备如果已经添加了那么它会忽略这条设备添加事件。然后它获取设备标识符、设备类和设备控制器编号并调用 createDeviceLocked 方法来创建一个新的 InputDevice 对象。 在 createDeviceLocked 方法中它创建一个新的 InputDevice 对象并根据设备类添加相应的 InputMapper。例如如果设备是键盘类设备它会添加 KeyboardInputMapper如果设备是触摸屏设备它会添加 MultiTouchInputMapper 或 SingleTouchInputMapper。 这个过程的关键点在于 InputDevice 的创建和 InputMapper 的添加。InputDevice 是用来管理设备和处理事件的而 InputMapper 是用来处理特定类型的输入事件的。 请注意这是一个简化的解释实际的实现可能会更复杂包括错误处理、设备状态管理等。 void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {ssize_t deviceIndex mDevices.indexOfKey(deviceId);if (deviceIndex 0) {//已添加的相同设备则不再添加ALOGW(Ignoring spurious device added event for deviceId %d., deviceId);return;}InputDeviceIdentifier identifier mEventHub-getDeviceIdentifier(deviceId);uint32_t classes mEventHub-getDeviceClasses(deviceId);int32_t controllerNumber mEventHub-getDeviceControllerNumber(deviceId);InputDevice* device createDeviceLocked(deviceId, controllerNumber, identifier, classes);device-configure(when, mConfig, 0);device-reset(when);
...mDevices.add(deviceId, device);...
}InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,const InputDeviceIdentifier identifier, uint32_t classes) {InputDevice* device new InputDevice(mContext, deviceId, bumpGenerationLocked(),controllerNumber, identifier, classes);...//添加键盘类设备InputMapperif (keyboardSource ! 0) {device-addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));}...//添加触摸屏设备InputMapperif (classes INPUT_DEVICE_CLASS_TOUCH_MT) {device-addMapper(new MultiTouchInputMapper(device));} else if (classes INPUT_DEVICE_CLASS_TOUCH) {device-addMapper(new SingleTouchInputMapper(device));}...
}
InputDevice::process首先会遍历InputDevice中的所有的事件真正加工原始输入事件的是InputMapper对象由于原始输入事件的类型很多因此在InputMapper有很多子类用于加工不同的原始输入事件比如KeyboardInputMapper用于处理键盘输入事件TouchInputMapper用于处理触摸输入事件。 InputDevice 类是 Android 系统中负责处理输入事件的类它位于 frameworks/native/services/inputflinger 目录下。 在 process 方法中它遍历所有的原始事件。如果 mDropUntilNextSync 的值为 true那么它会忽略当前事件。如果 mDropUntilNextSync 的值为 false那么它会遍历所有的 InputMapper并调用它们的 process 方法来处理事件。 这个过程的关键点在于 InputMapper 的 process 方法它是用来处理输入事件的。这个方法的具体实现细节没有在提供的代码片段中展示但它是用来处理输入事件的。 请注意这是一个简化的解释实际的实现可能会更复杂包括错误处理、设备状态管理等。 void InputDevice::process(const RawEvent* rawEvents, size_t count) {for (const RawEvent* rawEvent rawEvents; count ! 0; rawEvent) {...//mDropUntilNextSync的值默认为false如果设备的输入事件缓冲区溢出这个值会置为true。if (mDropUntilNextSync) {}else{for (InputMapper* mapper : mMappers) {mapper-process(rawEvent);}}...--count;}
} KeyboardInputMapper 是 Android 系统中负责处理键盘输入事件的类它位于 frameworks/native/services/inputflinger 目录下。 在 process 方法中它根据事件类型来处理键盘事件。当事件类型为 EV_KEY 时它会调用 processKey 方法来处理。在 processKey 方法中它会根据扫描码和使用码来获取按键码、元状态和策略标志。 如果按键被按下它会根据屏幕的方向对按键的虚拟键值进行旋转转换并检查是否应该丢弃虚拟键。如果应该丢弃它会取消触摸并返回。然后它会生成一个 KeyDown 结构体并将其添加到集合中。 如果按键被抬起它会从集合中移除对应的 KeyDown 对象。 最后它会生成一个 NotifyKeyArgs 对象并调用 getListener 方法的 notifyKey 方法来通知按键事件。 在 mapKey 方法中它首先获取设备然后检查键字符映射。如果键字符映射存在它会尝试根据扫描码和使用码来映射按键码。 这个过程的关键点在于键字符映射和按键码的映射以及按键事件的处理。 请注意这是一个简化的解释实际的实现可能会更复杂包括错误处理、设备状态管理等。 void KeyboardInputMapper::process(const RawEvent* rawEvent) {switch (rawEvent-type) {case EV_KEY: {//表示为Keyboard事件int32_t scanCode rawEvent-code;int32_t usageCode mCurrentHidUsage;mCurrentHidUsage 0;//排除对鼠标按键的处理if (isKeyboardOrGamepadKey(scanCode)) {processKey(rawEvent-when, rawEvent-value ! 0, scanCode, usageCode);}break;}...}
}void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,int32_t usageCode) {int32_t keyCode;int32_t keyMetaState;uint32_t policyFlags;if (getEventHub()-mapKey(getDeviceId(), scanCode, usageCode, mMetaState,keyCode, keyMetaState, policyFlags)) {keyCode AKEYCODE_UNKNOWN;keyMetaState mMetaState;policyFlags 0;}if (down) {// 当按下时首先需要根据屏幕的方向对按键的虚拟键值进行旋转转换if (mParameters.orientationAware) {keyCode rotateKeyCode(keyCode, getOrientation());}// KeyboardInputMapper维护了一个mKeyDowns集合ssize_t keyDownIndex findKeyDown(scanCode);if (keyDownIndex 0) {//对于重复按下的按键需要确保后续的处理过程中的虚拟键值与第一次按下时的一致以免重复过程中屏幕的方向的变化导致虚拟键值的变化使得后续InputDispatch无法正常识别重复按键的动作keyCode mKeyDowns[keyDownIndex].keyCode;} else {//生成keydown结构体并添加到集合中if ((policyFlags POLICY_FLAG_VIRTUAL) mContext-shouldDropVirtualKey(when,getDevice(), keyCode, scanCode)) {return;}if (policyFlags POLICY_FLAG_GESTURE) {mDevice-cancelTouch(when);}KeyDown keyDown;keyDown.keyCode keyCode;keyDown.scanCode scanCode;mKeyDowns.push_back(keyDown);}mDownTime when;} else {ssize_t keyDownIndex findKeyDown(scanCode);//对于抬起的按键则将对应的keydown对象从集合中移除if (keyDownIndex 0) {keyCode mKeyDowns[keyDownIndex].keyCode;mKeyDowns.erase(mKeyDowns.begin() (size_t)keyDownIndex);} else {return;}}...NotifyKeyArgs args(mContext-getNextSequenceNum(), when, getDeviceId(), mSource,getDisplayId(), policyFlags, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);getListener()-notifyKey(args);
}status_t EventHub::mapKey(int32_t deviceId,int32_t scanCode, int32_t usageCode, int32_t metaState,int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {AutoMutex _l(mLock);Device* device getDeviceLocked(deviceId);status_t status NAME_NOT_FOUND;if (device) {// Check the key character map first.spKeyCharacterMap kcm device-getKeyCharacterMap();if (kcm ! nullptr) {if (!kcm-mapKey(scanCode, usageCode, outKeycode)) {*outFlags 0;status NO_ERROR;}}
} EventHub::openDeviceLocked会为此设备加载键盘布局配置文件键盘布局配置文件的路径位于/system/usr/keylayout下的.lk文件。 processKey函数会将加工后的键盘输入事件封装为NotifyKeyArgs将NotifyKeyArgs通知给InputListenerInterface。 mArgsQueue的数据类型为VectorNotifyArgs*将该key事件压人该vector中。 InputReader::loopOnce的最后调用mQueuedListener-flush()遍历整个mArgsQueue数组。 InputDispatcher继承了InputDispatcherInterface而InputDispatcherInterface继承了InputListenerInterface因此实际上是调用了InputDispatcher的notifyKey函数将NotifyKeyArgs交给InputDispatcher处理。 QueuedInputListener 类是 Android 系统中负责接收和处理输入事件的类它位于 frameworks/native/services/inputflinger 目录下。 在 notifyKey 方法中它将接收到的按键事件参数添加到队列中。 在 flush 方法中它遍历队列中的所有事件调用每个事件的 notify 方法来处理事件然后删除这些事件。最后它清空队列。 这个过程的关键点在于事件的接收和处理以及事件队列的管理。 请注意这是一个简化的解释实际的实现可能会更复杂包括错误处理、事件状态管理等。 * frameworks/native/services/inputflinger/InputListener.cpp
void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {mArgsQueue.push_back(new NotifyKeyArgs(*args));
}void QueuedInputListener::flush() {size_t count mArgsQueue.size();for (size_t i 0; i count; i) {NotifyArgs* args mArgsQueue[i];args-notify(mInnerListener);delete args;}mArgsQueue.clear();
}
Android Input初始化及InputDIspatcher流程
唤醒InputDIspatcher
以Motion事件的分化过来进行举例。 InputDispatcher 类是 Android 系统中负责分发输入事件的类它位于 frameworks/native/services/inputflinger 目录下。 在 notifyMotion 方法中它首先检查 Motion 事件的参数是否有效然后通过 interceptMotionBeforeQueueing 方法来校验 Motion 事件的触点数量和 ID 是否在合理范围内。如果 Motion 事件需要通过输入过滤器它会创建一个 MotionEntry 对象并将其添加到入队列中。如果事件被过滤器消费它会返回而不进行分发。 在 enqueueInboundEventLocked 方法中它将事件添加到入队列中并根据事件类型进行优化处理。如果当前应用无响应且用户希望切换到其他应用窗口它会查找触摸窗口。 在 findTouchedWindowAtLocked 方法中它遍历所有的窗口检查坐标是否落在窗口之上。如果窗口是可触摸的它会返回该窗口。 这个过程的关键点在于事件的分发、过滤和窗口的查找。 请注意这是一个简化的解释实际的实现可能会更复杂包括错误处理、事件状态管理等。 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 lockmLock.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 lockif (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_castMotionEntry*(entry);if (motionEntry-action AMOTION_EVENT_ACTION_DOWN (motionEntry-source AINPUT_SOURCE_CLASS_POINTER) mInputTargetWaitCause INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY mInputTargetWaitApplicationToken ! nullptr) {...//查询可触摸的窗口spInputWindowHandle touchedWindowHandle findTouchedWindowAtLocked(displayId, x, y);...}break;}}return needWake;
}spInputWindowHandle InputDispatcher::findTouchedWindowAtLocked(int32_t displayId,int32_t x, int32_t y, bool addOutsideTargets, bool addPortalWindows) {//从前到后浏览窗口找到触摸窗口和外部目标。// mWindowHandles中保存的InputWindowHandler类保存了窗口的InputChannel以及InputWindowInfo结构体// InputWindowInfo结构体保存了窗口的各种布局信息包括可见性、位置、尺寸、flag等。const std::vectorspInputWindowHandle windowHandles getWindowHandlesLocked(displayId);// 递归遍历mWindowHandles中的所有WindowHandle检查时间坐标是否落在其上for (const spInputWindowHandle 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流程 检查Motion事件的参数是否有效其内部会检查触控点的数量pointerCount是否在合理范围内小于1或者大于16都是不合理的以及触控点的ID是否在合理范围内小于0或者大于31都是不合理的。 调用NativeInputManager.interceptMotionBeforeQueueing加入队列前执行拦截动作但并不改变流程调用链 IMS.interceptMotionBeforeQueueing
InputMonitor.interceptMotionBeforeQueueing (继承IMS.WindowManagerCallbacks)
PhoneWindowManager.interceptMotionBeforeQueueing主要是PowerManger.wakeUp 调用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进行事件模拟的。 生成MotionEntry并调用enqueueInboundEventLocked将该事件加入到InputDispatcherd的成员变量mInboundQueue。针对Motion当前App无响应且用户希望切换到其他应用窗口则drop该窗口事件并处理其他窗口事件调用findTouchedWindowAtLocked查询可触摸的窗口。 findTouchedWindowAtLocked中的mWindowHandles的赋值过程是由Java层的InputMonitor.setInputWindows(),经过JNI调用后进入InputDispatcher::setInputWindows()方法完成。 进一步说, 就是WMS执行addWindow()过程或许UI改变等场景,都会触发该方法的修改。 notifyMotion由InputReader在其线程循环中调用因此interceptMotionBeforeQueueing和filterInputEvent都在InputReader的线程中执行。 分发过程 这段代码是Android系统中用于处理输入事件的分发逻辑。InputDispatcherThread类中的threadLoop方法会调用InputDispatcher类的dispatchOnce方法进行一次输入事件的分发。 在dispatchOnce方法中首先会获取一个锁然后调用dispatchOnceInnerLocked方法进行输入事件的分发这个方法的返回值会决定下次分发循环的时间点。如果命令队列为空那么会立即进行分发。如果命令队列中有命令那么会立即开始执行下次线程循环。 然后计算需要休眠的时间timeoutMillis并通过mLooper-pollOnce方法进入休眠直到有事件发生或者时间到。 这个方法的主要作用是处理输入事件保证用户输入的及时响应。 注意这里的LONG_LONG_MAX和LONG_LONG_MIN是C中的最大和最小值nsecs_t是一个64位的整数用于表示时间戳。haveCommandsLocked和runCommandsLockedInterruptible是两个私有方法用于检查是否有待处理的命令和执行命令具体实现并未给出。 这个代码片段没有错误处理和异常处理实际使用时需要添加。 bool InputDispatcherThread::threadLoop() {mDispatcher-dispatchOnce();return true;
}void InputDispatcher::dispatchOnce() {nsecs_t nextWakeupTime LONG_LONG_MAX;{ // 获取锁std::scoped_lock _l(mLock);mDispatcherIsAlive.notify_all();// 通过dispatchOnceInnerLocked进行输入事件分发传出参数nextWakeupTime决定下次派发循环的时间点if (!haveCommandsLocked()) {dispatchOnceInnerLocked(nextWakeupTime);}// 执行命令队列中的命令if (runCommandsLockedInterruptible()) {nextWakeupTime LONG_LONG_MIN;// 设置nextWakeupTime为立即开始执行下次线程循环}} // 释放锁// 计算需要休眠的时间timeoutMillis并通过pollOnce进入epoll_waitnsecs_t currentTime now();int timeoutMillis toMillisecondTimeoutDelay(currentTime, nextWakeupTime);mLooper-pollOnce(timeoutMillis);
} 这段代码中dispatchOnce方法的主要逻辑是 获取锁检查是否有待处理的命令如果没有则进行分发如果有则立即开始执行下次线程循环。 计算需要休眠的时间并通过pollOnce方法进入休眠直到有事件发生或者时间到。 释放锁。 这个过程会一直重复直到线程被终止。 bool InputDispatcherThread::threadLoop() {mDispatcher-dispatchOnce();return true;
}void InputDispatcher::dispatchOnce() {nsecs_t nextWakeupTime LONG_LONG_MAX;{ // acquire lockstd::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_waitnsecs_t currentTime now();int timeoutMillis toMillisecondTimeoutDelay(currentTime, nextWakeupTime);mLooper-pollOnce(timeoutMillis);
} 通过dispatchOnceInnerLocked进行输入事件分发传出参数nextWakeupTime决定下次派发循环的时间点。 执行命令队列中的命令命令是一个符合Command签名的回调函数可以通过InputDispatcher::postCommandLocked()创建并添加到命令队列mCommandQueue中InputDispatcher执行命令的过程类似于Handler的工作方式。 计算需要休眠的时间timeoutMillis并通过pollOnce进入epoll_wait。 线程执行Looper-pollOnce进入epoll_wait等待状态派发线程的休眠在三种情况下可能被唤醒 callbackepoll_wait监听的fd由epoll_event发生时唤醒 timeout到达nextWakeupTime时间超时唤醒 wake主动调用Looper::wake函数唤醒由输入事件注入派发队列中 这段代码是Android系统中用于处理输入事件的分发逻辑的实现。InputDispatcher::dispatchOnceInnerLocked方法是在dispatchOnce方法中被调用的用于处理一个输入事件。 在dispatchOnceInnerLocked方法中首先检查是否需要冻结输入分发。如果需要则直接返回不再处理任何事件。然后检查是否需要进行应用切换如果需要则更新nextWakeupTime为应用切换的时间。 如果当前没有待分发的事件则从mInboundQueue队列中取出头部的事件并重置ANRApplication Not Responding超时。 接着检查事件是否需要被丢弃丢弃的原因可能是因为事件的派发策略不允许或者因为InputDispatcher被禁用。 对于不同类型的输入事件有不同的处理逻辑。对于按键事件如果事件因为应用切换、过期或者被其他窗口阻碍而丢弃则进行相应的处理。对于触摸事件如果事件因为阻碍其他窗口获得事件而丢弃则进行相应的处理。 如果事件被丢弃为了保证窗口收到的事件仍能保持down/up enter/exit的配对状态需要对事件进行补发。然后释放mPendingEvent对象使得InputDispatcher能够快速处理下一个分发事件。 这个过程会一直重复直到线程被终止。 注意如果mPendingEvent对象为空则可以处理分发队列中的下一条事件。 这个方法的主要逻辑是 检查是否需要冻结输入分发如果需要则直接返回。 检查是否需要进行应用切换如果需要则更新nextWakeupTime。 如果当前没有待分发的事件则从mInboundQueue队列中取出头部的事件。 检查事件是否需要丢弃如果需要则进行相应的处理。 根据事件类型进行相应的处理。 如果事件被丢弃则进行相应的处理并释放mPendingEvent对象使得InputDispatcher能够快速处理下一个分发事件。 这个过程会一直重复直到线程被终止。 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为空并且没有待分发的事件就returnif (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_castKeyEntry*(mPendingEvent);...done dispatchKeyLocked(currentTime, typedEntry, dropReason, nextWakeupTime);break;}case EventEntry::TYPE_MOTION: {MotionEntry* typedEntry static_castMotionEntry*(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}
} 如果派发队列为空则回事派发线程陷入无期限休眠状态。 InputDispatcher的冻结处理如果当前InputDispatcher被冻结则不进行派发操作InputDispatcher有三种状态分别是正常状态、冻结状态和禁用状态可以通过InputDispatcher的setInputDispatchMode函数来设置。 窗口切换操作处理mAppSwitchDueTime 代表了App最近发生窗口切换操作时比如按下Home键、挂断电话该操作事件最迟的分发时间当事件分发的时间点距离该事件加入mInboundQueue的时间超过500ms,则认为app切换过期,即isAppSwitchDuetrue。如果mAppSwitchDueTime小于nextWakeupTime下一次InputDispatcherThread醒来的时间就将mAppSwitchDueTime赋值给nextWakeupTime这样当InputDispatcher处理完分发事件后会第一时间处理窗口切换操作。 取出事件如果没有待分发的事件就从mInboundQueue中取出一个事件如果mInboundQueue为空并且没有待分发的事件就return如果mInboundQueue不为空取队列头部的EventEntry赋值给mPendingEventmPendingEvent的类型为EventEntry对象指针。 事件丢弃dropReason代表了事件丢弃的原因它的默认值为DROP_REASON_NOT_DROPPED代表事件不被丢弃。根据mPendingEvent的type做区分处理这里主要截取了对Motion类型的处理。经过过滤会调用dispatchMotionLocked函数为这个事件寻找合适的窗口。 后续处理执行dispatchMotionLocked进行Motion事件的派发如果派发完成无论成功派发还是事件被丢弃都会返回true否则返回false在下次循环时再次尝试此事件派发。如果dispatchMotionLocked事件分发成功则会调用releasePendingEventLocked函数其内部会将mPendingEvent的值设置为Null并将mPendingEvent指向的对象内存释放掉。将nextWakeupTime的值设置为LONG_LONG_MIN这是为了让InputDispatcher能够快速处理下一个分发事件。 派发一个事件至少需要一次线程循环才能完成因为事件的目标窗口有可能正在处理前一个输入事件在窗口完成之前的事件的处理病给予反馈之前InputDIspatcher不会在向此窗口派发新事件。事件的派发是串行的在队首的事件完成派发或者丢弃之前不会对后续的事件进行派发 事件丢弃DropReason 这段代码定义了一个枚举类型DropReason用于表示输入事件在分发过程中被丢弃的原因。 DROP_REASON_NOT_DROPPED表示事件没有被丢弃。 DROP_REASON_POLICY表示事件因为输入策略而被丢弃通常是因为事件类型不被允许被分发给用户。 DROP_REASON_APP_SWITCH表示事件因为应用切换而被丢弃通常是因为用户快速点击HOME键等操作系统需要立即切换到桌面应用。 DROP_REASON_DISABLED表示事件因为InputDispatcher被禁用而被丢弃。 DROP_REASON_BLOCKED表示事件因为阻碍了其他窗口获得事件而被丢弃可能是由于其他窗口的事件优先级更高。 DROP_REASON_STALE表示事件因为过期而被丢弃可能是由于事件太旧不再需要处理。 这个枚举类型用于在输入事件分发过程中记录和处理事件被丢弃的原因以便于后续进行相应的处理和优化。 * 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将为事件寻找合适的窗口目标。 这段代码是Android系统中用于处理触摸事件的分发逻辑的实现。InputDispatcher::dispatchMotionLocked方法是在dispatchOnceInnerLocked方法中被调用的用于处理一个触摸事件。 在dispatchMotionLocked方法中首先标记事件已经正式进入派发流程。然后如果事件是需要丢弃的则直接返回true不会去为该事件寻找合适的窗口。 接着根据事件的类型调用不同的方法来寻找合适的窗口。对于触摸事件调用findTouchedWindowTargetsLocked方法对于非触摸事件如轨迹球事件调用findFocusedWindowTargetsLocked方法。 如果输入事件被挂起说明找到了窗口并且窗口无响应则返回false。否则将事件派发给inputTargets列表中的目标。 这个过程会一直重复直到线程被终止。 这个方法的主要逻辑是 标记事件已经正式进入派发流程。 如果事件是需要丢弃的则直接返回。 根据事件的类型调用不同的方法来寻找合适的窗口。 如果找到了合适的窗口则将事件派发给这些窗口。 如果事件被挂起说明找到了窗口并且窗口无响应则返回false。 这个过程会一直重复直到线程被终止。 注意这个方法中injectionResult的值决定了事件是否被成功派发如果injectionResult不是INPUT_EVENT_INJECTION_SUCCEEDED则表示无法找到合适窗口事件将被直接丢弃。 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::vectorInputTarget 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结构体中。 这段代码是Android系统中InputDispatcher的一部分主要功能是处理触摸事件的分发。以下是对这段代码的中文解释 首先如果当前是一个新的手势或者是一个分割动作并且动作是POINTER_DOWN那么从MotionEntry中获取事件坐标点。 然后使用这些坐标点在当前显示区域中找到被触摸的窗口。 找到的窗口会被添加到临时触摸状态mTempTouchState中以便后续处理。 接着检查临时触摸状态中的所有目标窗口是否准备好接受新的输入事件。 如果窗口不能接收新事件则记录不能接收的原因并设置下次唤醒时间为5秒后如果5秒后线程仍未将此事件派发成功则会开始向Java层通报ANR。 如果窗口的查找过程一切顺利那么设置注入结果为SUCCEEDED并为每一个临时触摸状态中的窗口生成InputTargets。 最后在下一次迭代中删除外部窗口或悬停触摸窗口。 注意这段代码中的InputDispatcher类是一个处理触摸事件的分发器MotionEntry是一个存储触摸事件的结构体InputTarget是一个存储目标窗口的结构体InputWindowHandle是一个存储窗口信息的结构体。 这段代码的主要功能是找到被触摸的窗口并检查这些窗口是否准备好接受新的输入事件。如果窗口不能接收新事件那么会设置下次唤醒时间为5秒后如果5秒后线程仍未将此事件派发成功则会开始向Java层通报ANR。如果窗口的查找过程一切顺利那么会设置注入结果为SUCCEEDED并为每一个临时触摸状态中的窗口生成InputTargets。 这段代码的主要逻辑是 如果当前是一个新的手势或者是一个分割动作并且动作是POINTER_DOWN那么获取事件坐标点。 使用这些坐标点在当前显示区域中找到被触摸的窗口。 找到的窗口会被添加到临时触摸状态中以便后续处理。 检查临时触摸状态中的所有目标窗口是否准备好接受新的输入事件。 如果窗口不能接收新事件则记录不能接收的原因并设置下次唤醒时间为5秒后。 如果窗口的查找过程一切顺利那么设置注入结果为SUCCEEDED并为每一个临时触摸状态中的窗口生成InputTargets。 最后在下一次迭代中删除外部窗口或悬停触摸窗口。 注意这段代码中的InputDispatcher类是一个处理触摸事件的分发器MotionEntry是一个存储触摸事件的结构体InputTarget是一个存储目标窗口的结构体InputWindowHandle是一个存储窗口信息的结构体。 int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,const MotionEntry* entry, std::vectorInputTarget 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;spInputWindowHandle 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_PENDINGinjectionResult handleTargetsNotReadyLocked(currentTime, entry,nullptr, touchedWindow.windowHandle, nextWakeupTime, reason.c_str());//因此次查找到的窗口不能接收事件所以调过后续产生InputTarget的过程goto Unresponsive;}}}...//如果执行到这里说明窗口的查找过程一切顺利设置injectionResult为SUCCEEDED,并将injectionResult放入参数inputTargets中injectionResult INPUT_EVENT_INJECTION_SUCCEEDED;//为每一个mTempTouchState中的窗口生成InputTargetsfor (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中的窗口列表相反。 此函数做了三项工作 根据窗口点击坐标和事件发生坐标选择合适的目标窗口ZOrder由上至下遍历ZOrder越靠上优先权越高另外如果窗口没有在其LayoutParams.flag中指明FLAG_NOT_TOUCH_MODAL选项说明是模式窗口模式窗口将会阻止点击事件派发给其子窗口。 调用checkWindowReadyForMoreInputLocked()函数检查窗口是否可接收新的点击事件如果无法接收说明此窗口有可能发生ANR。handleTargetsNotReadyLocked()做下记录并将injectionResult设置为PENDING要求下次派发循环中重试。 如果找到可以接收新事件的窗口则调用addWindowTargetLocked()生成InputTarget并添加到inputTargets列表中。InputTarget中几乎所有字段可在InputWindowHandle中找到。 事件的发送 这段代码是Android系统中InputDispatcher的一部分主要功能是分发事件到相应的输入连接。以下是对这段代码的中文解释 首先向mCommandQueue队列添加doPokeUserActivityLockedInterruptible命令。 然后遍历inputTargets对于每一个inputTarget通过inputTarget内部的inputChannel获取Connection的索引。 如果获取到的索引大于等于0那么就从mConnectionsByFd容器中获取对应的Connection。 接着根据inputTarget开始事件发送循环。 注意这段代码中的InputDispatcher类是一个处理触摸事件的分发器EventEntry是一个存储事件的结构体InputTarget是一个存储目标连接的结构体。 这段代码的主要逻辑是 向mCommandQueue队列添加doPokeUserActivityLockedInterruptible命令。 遍历inputTargets对于每一个inputTarget通过inputTarget内部的inputChannel获取Connection的索引。 如果获取到的索引大于等于0那么就从mConnectionsByFd容器中获取对应的Connection。 接着根据inputTarget开始事件发送循环。 注意这段代码中的InputDispatcher类是一个处理触摸事件的分发器EventEntry是一个存储事件的结构体InputTarget是一个存储目标连接的结构体。 void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,EventEntry* eventEntry, const std::vectorInputTarget inputTargets) {//向mCommandQueue队列添加doPokeUserActivityLockedInterruptible命令pokeUserActivityLocked(eventEntry);for (const InputTarget inputTarget : inputTargets) {//根据inputTarget内部的inputChannel来获取Connection的索引ssize_t connectionIndex getConnectionIndexLocked(inputTarget.inputChannel);if (connectionIndex 0) {//获取保存在mConnectionsByFd容器中的ConnectionspConnection 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事件发送给目标窗口。调用流程 prepareDispatchCycleLocked-enqueueDispatchEntryLocked。将InputDispatcher中mInboundQueue中的事件取出后, 找到目标window后,封装dispatchEntry加入到connection的outbound队列。DispatchEntry附加了一个seq字段这个字段是事件的序号窗口对事件的反馈将携带这个序号。其实这个函数使用不同的选项多次调用了enqueueDispatchEntryLocked()。FLAG_DISPATCH_AS_IS表示不修改事件的action类型而其他的选项则会使得action类型发生变化 startDispatchCycleLocked。从outboundQueue中取出事件,重新放入waitQueue队列。 inputPublisher.publishKeyEvent-mChannel-sendMessage。InputChannel通过socket向远端的socket发送消息。 其中pokeUserActivityLocked(eventEntry)方法调用NativeInputManager::pokeUserActivity最终会调用到Java层的PowerManagerService.java中的userActivityFromNative()方法 这也是PMS中唯一的native call方法 这段代码是Android系统中InputDispatcher的一部分主要功能是处理用户活动并分发事件到相应的连接。以下是对这段代码的中文解释 InputDispatcher::pokeUserActivityLocked方法中首先获取当前显示区域的焦点窗口然后创建一个命令并将其添加到命令队列中。 InputDispatcher::postCommandLocked方法中创建一个新的命令条目并将其添加到命令队列中。 InputDispatcher::startDispatchCycleLocked方法中当连接状态正常且出站队列不为空时开始事件分发循环。 对于每一个事件根据事件类型进行不同的处理。对于按键事件直接发布事件对于触摸事件调用publishMotionEvent方法发布事件。 如果发布事件失败根据失败的原因进行不同的处理。如果是因为管道已满但等待队列为空则调用Java层的IMS.notifyInputChannelBroken()方法如果处于阻塞状态则将连接的inputPublisherBlocked标志设置为true。 如果发布事件失败将事件从出站队列中移除并将其重新添加到等待队列中。 注意这段代码中的InputDispatcher类是一个处理触摸事件的分发器CommandEntry是一个存储命令的结构体Connection是一个存储连接信息的结构体。 这段代码的主要逻辑是 在InputDispatcher::pokeUserActivityLocked方法中获取当前显示区域的焦点窗口然后创建一个命令并将其添加到命令队列中。 在InputDispatcher::postCommandLocked方法中创建一个新的命令条目并将其添加到命令队列中。 在InputDispatcher::startDispatchCycleLocked方法中当连接状态正常且出站队列不为空时开始事件分发循环。 对于每一个事件根据事件类型进行不同的处理。对于按键事件直接发布事件对于触摸事件调用publishMotionEvent方法发布事件。 如果发布事件失败根据失败的原因进行不同的处理。如果是因为管道已满但等待队列为空则调用Java层的IMS.notifyInputChannelBroken()方法如果处于阻塞状态则将连接的inputPublisherBlocked标志设置为true。 如果发布事件失败将事件从出站队列中移除并将其重新添加到等待队列中。 注意这段代码中的InputDispatcher类是一个处理触摸事件的分发器CommandEntry是一个存储命令的结构体Connection是一个存储连接信息的结构体。 void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) {int32_t displayId getTargetDisplayId(eventEntry);spInputWindowHandle 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 spConnection 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_castMotionEntry*(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);}
} 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再将反馈进行读取。 ActivityThread.handleResumeActivity-WindowManagerImpl.addView-WindowManagerGlobal.addView() -ViewRootImpl.setView 这段代码是Android系统中窗口管理的一部分主要涉及到ViewRootImpl和WindowManagerService两个类。 在ViewRootImpl的setView方法中如果当前窗口的输入特性没有设置INPUT_FEATURE_NO_INPUT_CHANNEL那么会创建一个InputChannel对象并通过Binder调用进入system进程的Session。如果mInputChannel不为空并且mInputQueueCallback不为空则会创建一个InputQueue对象并调用mInputQueueCallback的onInputQueueCreated方法。 在WindowManagerService的addWindow方法中如果新添加的窗口能接收按键操作会更新聚焦窗口。然后通过InputMonitor对象更新所有的窗口信息。 具体来说InputChannel是Android系统中用于进程间通信的通道InputQueue是用于处理输入事件的队列WindowState代表一个窗口的状态WindowManagerService负责管理所有的窗口。 在ViewRootImpl的setView方法中如果当前窗口的输入特性没有设置INPUT_FEATURE_NO_INPUT_CHANNEL那么会创建一个InputChannel对象。然后通过Binder调用进入system进程的Session如果mInputChannel不为空并且mInputQueueCallback不为空则会创建一个InputQueue对象并调用mInputQueueCallback的onInputQueueCreated方法。 在WindowManagerService的addWindow方法中如果新添加的窗口能接收按键操作会更新聚焦窗口。然后通过InputMonitor对象更新所有的窗口信息。 总的来说这段代码的主要功能是创建和管理窗口的输入通道和输入队列以及更新窗口的焦点状态。 * 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进程的Sessionres 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*/);}//将所有的窗口信息更新到InputManagerservicedisplayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);...
} 创建Java层的InputChannel对象mInputChannel。 调用Session.addToDisplay向WMS注册InputChannel信息进入到WindowManagerService.addWindow。 创建WindowInputEventReceiver对象。 服务端连接的建立 这段代码是Android系统中窗口管理的一部分主要涉及到WindowState和InputManagerService两个类。 在WindowState的openInputChannel方法中如果WindowState已经有一个输入通道则会抛出异常。然后根据WindowState的哈希值和标题生成输入通道的名称创建一对输入通道并将其中一个分配给mInputChannel另一个分配给mClientChannel。如果outInputChannel不为空则将mClientChannel传递给outInputChannel并将其销毁。否则如果窗口可见会创建一个死窗口事件接收器以确保输入事件仍然可以被输入监视器通道检测到并可以重新启动应用程序。最后将WindowState保存的InputChannel注册到InputManagerService。 在InputManagerService的registerInputChannel方法中如果输入通道为空则会抛出异常。然后使用Binder将token设置为输入通道的token。实际的注册过程由NativeInputManager交给InputDispatcher的同名函数完成。 总的来说这段代码的主要功能是创建和管理窗口的输入通道并将它们注册到InputManagerService。 在WindowState的openInputChannel方法中如果WindowState已经有一个输入通道则会抛出异常。然后根据WindowState的哈希值和标题生成输入通道的名称创建一对输入通道并将其中一个分配给mInputChannel另一个分配给mClientChannel。如果outInputChannel不为空则将mClientChannel传递给outInputChannel并将其销毁。否则如果窗口可见会创建一个死窗口事件接收器以确保输入事件仍然可以被输入监视器通道检测到并可以重新启动应用程序。最后将WindowState保存的InputChannel注册到InputManagerService。 在InputManagerService的registerInputChannel方法中如果输入通道为空则会抛出异常。然后使用Binder将token设置为输入通道的token。实际的注册过程由NativeInputManager交给InputDispatcher的同名函数完成。 总的来说这段代码的主要功能是创建和管理窗口的输入通道并将它们注册到InputManagerService。 * 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();//创建一对InputChannelInputChannel[] inputChannels InputChannel.openInputChannelPair(name);mInputChannel inputChannels[0];mClientChannel inputChannels[1];mInputWindowHandle.token mClient.asBinder();if (outInputChannel ! null) {//socket客户端传递给outInputChannelmClientChannel.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::InputChannelinputChannel.setToken(token);//实际的注册过程由NI层的NativelnputManager 交给InputDispatcher的同名函数完成注册nativeRegisterInputChannel(mPtr, inputChannel, Display.INVALID_DISPLAY);} 通过WindowState.openInputChannel调用InputChannel.openInputChannelPair然后进入到InputTransport::InputChannel::openInputChannelPair创建的socket pair将其中的客户端赋值给mInputChannel。 通过IMS.registerInputChannel()将InputChannel注册到IMS中通过NativeInputManager::registerInputChannel调用mInputManager-getDispatcher()-registerInputChannel进入到InputDispatcher::registerInputChannel。 通过inputChannel.nativeSetToken将java层窗口通过NativeInputChannel设置到native的InputTransport::InputChannel这里的token为ViewRootImpl中的W窗口类。 通过InputMonitor.updateInputWindowsLw()将所有窗口的信息更新到IMS。 在WMS添加窗口时会创建一对InputChannel。 其中一个保存在WindowState中并注册给IMS它是服务端。 另外一个则通过传出参数outlnputChannel交给调用者是客户端。 这段代码是Android系统中输入管理器的一部分主要涉及到InputDispatcher类。 在InputDispatcher的registerInputChannel方法中如果输入通道为空则会抛出异常。然后使用Binder将token设置为输入通道的token。实际的注册过程由NativeInputManager交给InputDispatcher的同名函数完成。 总的来说这段代码的主要功能是创建和管理窗口的输入通道并将它们注册到InputManagerService。 在InputDispatcher的registerInputChannel方法中为传入的InputChannel创建一个Connection对象并对其进行封装。然后监听InputChannel的可读性。mLooper的pollOnce()本质上就是epoll_wait()因此Looper对象具有监听文件描述符可读性事件的能力在此注册InputChannel可读性事件并在事件到来时通过handleReceiveCallback()回调进行处理。 总的来说这段代码的主要功能是监听并处理输入通道的事件并将它们注册到InputDispatcher。 * frameworks/native/services/inputflinger/InputDispatcher.cpp
status_t InputDispatcher::registerInputChannel(const spInputChannel inputChannel,int32_t displayId) {{ // acquire lock...//为传入的Inputchannel创建一个Connection对象并对其进行封装 spConnection 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;
} atcher中InputChannel被封装为一个Connection对象。Connection类描述了从ImputDispatcher到目标窗口中的一个连接其中保存了向窗口发送的事件的状态信息。在Connection中重要的成员有 mlnputPublisherInputTransport::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系统中输入事件接收器的一部分主要涉及到ViewRootImpl、NativeInputEventReceiver和InputConsumer类。 在ViewRootImpl的WindowInputEventReceiver类中它继承自InputEventReceiver用于接收和处理窗口的输入事件。在构造函数中它接收一个InputChannel和Looper并调用父类的构造函数进行初始化。 在android_view_InputEventReceiver.cpp中有一个nativeInit方法它接收一个Java层的WindowInputEventReceiver对象、一个InputChannel对象和一个MessageQueue对象。这个方法中它获取InputChannel和MessageQueue然后创建一个NativeInputEventReceiver对象并执行其初始化操作。最后它保留了对这个对象的引用。 在NativeInputEventReceiver类中它有一个setFdEvents方法用于设置文件描述符的事件。如果事件发生了变化它会获取InputChannel的文件描述符并根据事件类型通过Looper注册InputChannel的可读性事件。如果事件为0它会从Looper中移除文件描述符。 NativeInputEventReceiver的构造函数中它接收一个JNI环境、一个弱引用的Java对象、一个InputChannel和一个MessageQueue。在构造函数中它创建了一个InputConsumer对象并保存了MessageQueue。 总的来说这段代码的主要功能是创建和管理输入事件接收器并处理输入事件。 在ViewRootImpl的WindowInputEventReceiver类中它继承自InputEventReceiver用于接收和处理窗口的输入事件。在构造函数中它接收一个InputChannel和Looper并调用父类的构造函数进行初始化。 在android_view_InputEventReceiver.cpp中有一个nativeInit方法它接收一个Java层的WindowInputEventReceiver对象、一个InputChannel对象和一个MessageQueue对象。这个方法中它获取InputChannel和MessageQueue然后创建一个NativeInputEventReceiver对象并执行其初始化操作。最后它保留了对这个对象的引用。 在NativeInputEventReceiver类中它有一个setFdEvents方法用于设置文件描述符的事件。如果事件发生了变化它会获取InputChannel的文件描述符并根据事件类型通过Looper注册InputChannel的可读性事件。如果事件为0它会从Looper中移除文件描述符。 NativeInputEventReceiver的构造函数中它接收一个JNI环境、一个弱引用的Java对象、一个InputChannel和一个MessageQueue。在构造函数中它创建了一个InputConsumer对象并保存了MessageQueue。 总的来说这段代码的主要功能是创建和管理输入事件接收器并处理输入事件。 * 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) {spInputChannel inputChannel android_view_InputChannel_getInputChannel(env,inputChannelObj);...//获取UI主线程的消息队列spMessageQueue messageQueue android_os_MessageQueue_getMessageQueue(env, messageQueueObj);...//创建NativeInputEventReceiver对象spNativeInputEventReceiver receiver new NativeInputEventReceiver(env,receiverWeak, inputChannel, messageQueue);//执行其初始化操作 status_t status receiver-initialize();...receiver-incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the objectreturn reinterpret_castjlong(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;spMessageQueue mMessageQueue;...NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env,jobject receiverWeak, const spInputChannel inputChannel,const spMessageQueue 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系统中处理输入事件的核心部分主要分为两个部分NativeInputEventReceiver的handleEvent和consumeEvents函数。 NativeInputEventReceiver::handleEvent函数 这个函数主要处理从InputChannel接收到的输入事件。如果事件类型是ALOOPER_EVENT_INPUT那么会调用consumeEvents函数处理事件。如果事件类型是ALOOPER_EVENT_OUTPUT那么会将mFinishQueue中的事件发送给InputChannel。 NativeInputEventReceiver::consumeEvents函数 这个函数从InputChannel中读取输入事件并根据事件类型创建KeyEvent或MotionEvent对象然后通过JNI回调Java层的InputEventReceiver的dispatchInputEvent函数。 这段代码的主要流程如下 从InputChannel中读取输入事件并解析为InputEvent对象。 根据事件类型创建KeyEvent或MotionEvent对象。 通过JNI回调Java层的InputEventReceiver的dispatchInputEvent函数将事件分发给Java层处理。 注意 ALOOPER_EVENT_INPUT表示有新的输入事件到达。 ALOOPER_EVENT_OUTPUT表示有新的输入事件需要发送。 consumeEvents函数从InputChannel中读取输入事件并处理。 dispatchInputEvent函数将输入事件分发给Java层处理。 这个代码是Android系统处理输入事件的核心部分它负责将输入事件从Native层转换为Java层并分发给Java层的InputEventReceiver处理。 * 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_castKeyEvent*(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。 这段代码是Android系统中用于处理输入事件的C代码主要分为两个部分nativeFinishInputEvent和NativeInputEventReceiver::finishInputEvent。 nativeFinishInputEvent函数是Java层的JNI接口用于通知NativeInputEventReceiver对象完成一个输入事件的处理。它接收一个NativeInputEventReceiver对象的指针序列号seq和处理结果handled作为参数然后调用NativeInputEventReceiver对象的finishInputEvent方法。 NativeInputEventReceiver::finishInputEvent方法是NativeInputEventReceiver类的方法它接收一个序列号seq和处理结果handled作为参数然后调用mInputConsumer对象的sendFinishedSignal方法。这个方法的返回值是一个status_t类型的状态码表示操作的结果。如果返回值非零表示操作失败可能是由于WOULD_BLOCK错误。 如果返回值是WOULD_BLOCK那么会将当前的序列号和处理结果添加到一个名为mFinishQueue的队列中并设置文件描述符的事件类型为ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT。 如果返回值是OK那么直接返回OK。 总的来说这段代码的主要功能是处理输入事件的完成信号如果处理过程中遇到WOULD_BLOCK错误那么会将这个错误记录下来等待后续处理。 * frameworks/base/core/jni/android_view_InputEventReceiver.cpp
static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jlong receiverPtr,jint seq, jboolean handled) {spNativeInputEventReceiver receiver reinterpret_castNativeInputEventReceiver*(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函数继续尝试发送队列中的下一个事件又回到发送流程。 这段代码是Android系统中InputDispatcher的实现主要用于处理输入事件的分发。 handleReceiveCallback函数是处理接收回调的函数它接收一个文件描述符fd、事件类型events和数据data作为参数。首先它通过文件描述符获取对应的Connection对象然后根据事件类型如果是输入事件则不断地从Connection对象的inputPublisher中接收反馈信息并调用finishDispatchCycleLocked函数处理反馈。如果处理时间超过2s则打印信息。 doDispatchCycleFinishedLockedInterruptible函数是处理分发周期完成的函数它接收一个命令条目commandEntry作为参数。首先它从waitQueue中按照序号取出反馈对应的事件如果事件处理时间超过2s则打印信息。然后将事件从waitQueue中移除并启动下一次发送循环。 注意这两个函数都在一个锁的保护下执行以保证线程安全。 这段代码的主要逻辑是接收输入事件处理反馈并管理事件的分发。如果事件处理时间过长则打印警告信息。 int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {InputDispatcher* d static_castInputDispatcher*(data);{ // acquire lockstd::scoped_lock _l(d-mLock);//根据可读的Inputchannel的描述符获取对应的Connection对象ssize_t connectionIndex d-mConnectionsByFd.indexOfKey(fd);...bool notify;spConnection 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) {spConnection 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)中移除。 应用端在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)。 这段代码是Android系统中InputDispatcher类的部分实现主要用于处理输入事件的分发。 findFocusedWindowTargetsLocked 方法 首先尝试从mFocusedWindowHandlesByDisplay中获取当前焦点的窗口句柄如果获取失败则检查mFocusedApplicationHandlesByDisplay中是否有应用正在启动如果没有则返回一个提示信息。 如果获取到窗口句柄则检查该窗口是否准备好接收更多输入。如果窗口未准备好则返回一个提示信息。 findTouchedWindowTargetsLocked 方法 遍历mTempTouchState.windows检查每个窗口是否准备好接收更多输入。如果窗口未准备好则返回一个提示信息。 checkWindowReadyForMoreInputLocked 方法 如果窗口被暂停则返回一个提示信息。 获取窗口的Connection索引如果索引无效则返回一个提示信息。 如果Connection的状态不是正常状态则返回一个提示信息。 如果Connection的inputPublisher被阻塞则返回一个提示信息。 对于按键事件要求Connection必须处于空闲状态如果Connection的出站队列或等待队列不为空则返回一个提示信息。 对于Motion事件只要窗口能在0.5s内发送反馈即可如果等待队列不为空且当前时间大于等待队列头部事件的交付时间加上0.5s则返回一个提示信息。 如果以上条件都不满足则返回空字符串表示窗口已经准备好接收更多输入。 以上代码中InputDispatcher类中的方法主要用于检查窗口是否准备好接收更多输入如果窗口未准备好则返回一个提示信息。如果窗口被暂停、Connection状态异常、inputPublisher被阻塞、Connection的出站队列或等待队列不为空或者对于Motion事件等待队列不为空且当前时间大于等待队列头部事件的交付时间加上0.5s则认为窗口未准备好。 注意以上代码中的StringPrintf函数用于格式化字符串goto Unresponsive用于跳转到标记为Unresponsive的标签处sp是Smart Pointer的缩写用于智能指针std::vector是C标准模板库中的动态数组std::string是字符串类型nsecs_t是64位无符号整型用于表示纳秒级的时间戳。 int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,const EventEntry* entry, std::vectorInputTarget inputTargets, nsecs_t* nextWakeupTime) {...spInputWindowHandle focusedWindowHandle getValueByKey(mFocusedWindowHandlesByDisplay, displayId);spInputApplicationHandle 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::vectorInputTarget 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 spInputWindowHandle 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被阻塞 spConnection 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没有焦点窗口。 这段代码是Android系统窗口管理服务的一部分主要用于更新并管理焦点窗口。 这段代码的主要功能是遍历所有的子窗口内容DisplayContent并调用它们的updateFocusedWindowLocked方法来更新焦点窗口。这个方法的参数包括模式mode和是否更新输入窗口updateInputWindows。 在遍历过程中它会记录下当前遍历到的所有显示器的顶级焦点窗口的显示IDtopFocusedDisplayId。 如果当前的顶级焦点显示ID与之前记录的顶级焦点显示ID不同那么就更新顶级焦点显示ID并通过WindowManagerService的InputManager和Policy设置新的顶级焦点显示ID。 最后返回一个布尔值changed表示是否有任何改变。 注意INVALID_DISPLAY是一个无效的显示ID通常定义为-1。 这个方法的主要目的是确保所有的显示器都有正确的焦点窗口并且当焦点窗口发生变化时能够及时更新。 这段代码使用了面向对象的编程思想将每个显示器的焦点窗口更新操作封装在了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;} 这段代码是Android系统中InputDispatcher的handleTargetsNotReadyLocked方法的实现主要功能是处理目标应用或窗口未准备好接收输入事件的情况。 首先检查应用和窗口是否都为空。如果都为空说明系统尚未完成启动。 如果应用或窗口不为空则获取引发ANRApplication Not Responding的超时时间。如果目标窗口存在则获取窗口指定的超时时间如果目标窗口不存在则从AMSActivity Manager Service获取超时时间。 设置引发ANR的时间点并设置引发ANR的时间并将mInputTargetWaitTimeoutExpired设置为false。 获取应用或窗口的token如果两者都为空则获取应用的token。 检查是否已经到达引发ANR的时间点。如果到达则调用onANRLocked方法并设置nextWakeupTime为最小值返回INPUT_EVENT_INJECTION_TIMED_OUT。 如果没有到达引发ANR的时间点则检查mInputTargetWaitTimeoutTime是否小于当前的nextWakeupTime如果是则更新nextWakeupTime返回INPUT_EVENT_INJECTION_PENDING。 这段代码的主要目的是在应用或窗口未准备好接收输入事件时处理超时和ANR的情况。 int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,const EventEntry* entry,const spInputApplicationHandle applicationHandle,const spInputWindowHandle 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;}//检查是否引发ANRif (currentTime mInputTargetWaitTimeoutTime) {//当前时间大于引发ANR的时间后则引发ANRonANRLocked(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。 这段代码是Android窗口管理器Window Manager中的一个方法用于处理应用程序无响应ANR的情况。当应用程序在一定时间内无法响应用户的输入事件时就会触发这个方法。 这个方法的参数是 IBinder token应用程序窗口的token用于标识应用程序窗口。 String reason超时的原因例如Input dispatching timed out。 方法的主要步骤如下 首先它获取全局锁mService.mGlobalLock这是一个同步锁用于保证在同一时间只有一个线程可以访问共享资源。 然后它检查windowState是否不为空。如果不为空说明超时的原因是与窗口状态相关。 如果windowState不为空它会记录一条日志信息说明输入事件分发超时并发送给windowState.mAttrs.getTitle()。 接着它获取系统层级systemAlertLayer这个层级用于决定窗口的显示位置。 如果windowState.mBaseLayer大于systemAlertLayer那么窗口就会显示在系统警告之上。 如果appWindowToken不为空说明超时的原因是与应用程序窗口相关。它会记录一条日志信息说明输入事件分发超时并发送给appWindowToken.stringName。 如果appWindowToken和windowState都为空说明超时的原因不明。它会记录一条日志信息说明输入事件分发超时。 最后它调用mService.saveANRStateLocked(appWindowToken, windowState, reason)方法保存ANR状态。 方法返回0表示中止分发事件。 这个方法的主要目的是记录ANR事件并保存相关的信息以便后续处理。 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中会出现以下信息, 并且TAGWindowManager: Input event dispatching timed out xxx. Reason: reason, 其中xxx取值: 窗口类型: sending to windowState.mAttrs.getTitle()。 应用类型: sending to application appWindowToken.stringName。 其他类型: 则为空。 ANR reason主要有以下几类 这段代码是Android系统中的InputDispatcher类的一部分用于检查一个特定的窗口window是否准备好接收更多的输入事件。InputDispatcher是Android系统中负责管理输入事件的分发和处理的组件。 代码的主要功能如下 首先它会检查窗口是否被暂停paused。如果窗口被暂停那么它将等待直到窗口被激活。 接下来它会检查窗口的输入通道是否已经注册到输入分发器中。如果没有那么它将等待直到窗口的输入通道被注册。 然后它会检查连接的状态是否正常。如果连接状态异常那么它将等待直到连接状态恢复正常。 接着它会检查连接是否被阻塞。如果连接被阻塞那么它将等待直到连接不被阻塞。 如果当前事件是按键事件TYPE_KEY它会检查连接的出站队列outboundQueue和等待队列waitQueue是否为空。如果不为空那么它将等待直到窗口处理完之前发送的所有输入事件。 如果当前事件不是按键事件它会检查等待队列是否不为空并且当前时间是否超过了等待队列头部事件的交付时间加上一个预设的超时时间STREAM_AHEAD_EVENT_TIMEOUT。如果是那么它将等待直到窗口处理完某些之前发送的输入事件。 如果以上所有条件都不满足函数将返回一个空字符串表示窗口已经准备好接收更多的输入。 这段代码的主要目的是确保输入事件能够被正确、有效地分发到正确的窗口并且避免在窗口处理输入事件时出现阻塞或延迟。 注意这段代码中的StringPrintf函数用于格式化字符串getConnectionIndexLocked和getInputChannelLocked函数可能是InputDispatcher类中的其他方法用于获取连接索引和输入通道sp和wp是Android的智能指针类型用于管理对象的生命周期。 这段代码是Android系统输入事件分发流程中的一个关键部分它确保了输入事件的正确分发和处理。 std::string InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,const spInputWindowHandle 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 windows connection is not registered then keep waiting.ssize_t connectionIndex getConnectionIndexLocked(getInputChannelLocked(windowHandle-getToken()));if (connectionIndex 0) {return StringPrintf(Waiting because the %s windows 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.spConnection connection mConnectionsByFd.valueAt(connectionIndex);if (connection-status ! Connection::STATUS_NORMAL) {return StringPrintf(Waiting because the %s windows 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 windows 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的onCreateonStartonResume方法然后我们的应用窗口便是在onResume中才去向WindowManager添加注册的。因此在注册添加窗口之前application或者启动的Activity的生命周期onCreateonStart的任意方法做了耗时操作或者他们加载一起的执行时间过长都是能够导致无窗口有应用类型的Input ANR问题发生的。 非按键事件事件等待队列不为空且头事件分发超时500msWaiting 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事件超时引起的。 比如在使用Handler向UI主线程的looper messagequeue post一个runnable callback这个runnable里面便是执行的耗时超过5s则会抛出异常因为应用端的消息接收处理时在主线的一次Looper循环中读取并分发事件处理完成后反馈到InputDIspatch如果主线程的Looper耗时事件超过5s没处理则会引起anr。 或者应用在处理touch事件耗时超过5s则也会抛出anr。 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。