Android_InputManagerService详解

本文基于Android7.0源码

1 Android输入系统

在人类现在所使用的所有终端设备,他们都有各自的输入系统,例如电脑的键盘,鼠标,触屏,麦克,他们只是设备的硬件实施者,我们也可以称为输入设备(InputDevice)。

而此问题要探讨的是Android输入系统,我们上面定义了输入设备,那么接下来我们定义系统。

是谁去获取,解释,传递这些多元化的信号呢?

又是谁拿到这些信号后让电脑做出相应的反馈动作?

这需要一个完整的系统做去做准确,快捷的处理动作,那么这个处理所有输入设备信号的人,就是Android输入系统,此人任劳任怨,听起来肯定不是一个革命家,而恰好是因为他的存在,我们的操作系统才得以和人类交互,完成我们的一切命令。

从上面问题是我对操作系统的理解,此般抽象,应该都很容易理解,而我们这篇文章的重点是Android系统InputManagerService,光从名字看,他是我们Android系统Service大家庭的一员,从现在开始,我们进入正题

1.1 Android输入系统原理和架构简介

当输入设备可用时,Linux内核会在/dev/input/下创建对应的名为event0~n或其他名称的设备节点。而当输入设备不可用时,则会将对应的节点删除。

在用户空间可以通过ioctl的方式从这些设备节点中获取其对应的输入设备的类型、厂商、描述等信息。

当用户操作输入设备时,Linux内核接收到相应的硬件中断,然后将中断加工成原始的输入事件数据并写入其对应的设备节点中,在用户空间可以通过read()函数将事件数据读出。

Android输入系统的工作原理概括来说,就是监控/dev/input/下的所有设备节点,当某个节点有数据可读时,将数据读出并进行一系列的翻译加工,然后在所有的窗口中寻找合适的事件接收者,并派发给它。

如果需要查看输入设备信息,请查看:Android模拟输入操作的实现与原理

通过上面介绍我们得知,输入系统始于Linux内核/dev/input/event0~n,终于WMS管理的相应窗口。

最初的事件是Linux内核捕获收集设备输入信号,生成并且保存成event0~n,而最终则是KeyEvent或者MotionEvent。

因此Android输入系统的主要职责为,①读取设备节点的原始事件,②封装加工事件,分发到特定窗口

此过程由InputManagerService为核心的多个参与者共同完成

输入系统的总体流程与参与者

上图描述了Android输入系统的流程与参与者的关系,每一个基础参与者的详解如下:

  • Linux内核

接受输入设备的中断,并将原始事件的数据写入到设备节点中。
设备节点,作为内核与IMS的桥梁,它将原始事件的数据暴露给用户空间,以便IMS可以从中读取事件。

  • InputManagerService

一个Android系统服务,它分为Java层和Native层两部分。Java层负责与WMS的通信。而Native层则是InputReader和InputDispatcher两个输入系统关键组件的运行容器。

  • EventHub

直接访问所有的设备节点。并且正如其名字所描述的,它通过一个名为getEvents()的函数将所有输入系统相关的待处理的底层事件返回给使用者。这些事件包括原始输入事件、设备节点的增删等。

  • InputReader

InputReader是IMS中的关键组件之一。它运行于一个独立的线程中,负责管理输入设备的列表与配置,以及进行输入事件的加工处理。它通过其线程循环不断地通过getEvents()函数从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将窗口所接收到的输入事件沿着控件树将事件派发给感兴趣的控件。
简单来说,内核将原始事件写入到设备节点中,InputReader不断地通过EventHub将原始事件取出来并翻译加工成Android输入事件,然后交给InputDispatcher。InputDispatcher根据WMS提供的窗口信息将事件交给合适的窗口。窗口的ViewRootImpl对象再沿着控件树将事件派发给感兴趣的控件。控件对其收到的事件作出响应,更新自己的画面、执行特定的动作。所有这些参与者以IMS为核心,构建了Android庞大而复杂的输入体系。

1.2 InputManagerServer启动

已经了解IMS的作用和架构,那么接下来分析IMS是如何被系统启动并运行的。

1.2.1 SystemServer中启动IMS

作为一个系统Service,IMS在SystemServer中的ServerThread线程中启动。

\frameworks\base\services\java\com\android\server\SystemServer.java->ServerThread.run()->startOtherServices()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Starts a miscellaneous grab bag of stuff that has yet to be refactored
* and organized.
*/
private void startOtherServices() {
......
//定义InputManagerService对象
InputManagerService inputManager = null;
......
/**IMS的创建**/
//创建InputManagerService对象
inputManager = new InputManagerService(context);
......
//调用WindowManagerService.main(),将inputManager作为对象传入,使得WMS持有IMS对象
wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
//将inputManager添加到ServiceManager中,以便其他程序访问
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
......
//给IMS设置WMS的事件回调监听
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
/**IMS的启动**/
//启动IMS服务
inputManager.start();
......
// 设置有线耳机变化监听
inputManager.setWiredAccessoryCallbacks(
new WiredAccessoryManager(context, inputManager));
}

由上面代码可以看出IMS被创建,并且启动IMS。

接下来从两方面入手进行IMS的启动流程解析:

①IMS创建 ②IMS启动

1.2.2 IMS的创建

\frameworks\base\services\core\java\com\android\server\input\InputManagerService.java->InputManagerService()

1
2
3
4
5
6
7
8
9
10
public InputManagerService(Context context) {
this.mContext = context;
//创建InputManagerHandler,将在WMS中使用
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
......
//nativeInit初始化native模块
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

LocalServices.addService(InputManagerInternal.class, new LocalService());
}

IMS的构造函数中主要调用nativeInit()

\frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp->nativeInit()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
//创建一个消息队列,用于native组件和Java层IMS通信
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}

//创建NativeInputManager对象,负责native组件和Java层的通信
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast<jlong>(im);
}

NativeInputManager被创建,它主要负责native组件和Java层的通信,那么接下来我们了解一下NativeInputManager

\frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp->NativeInputManager

1
2
3
4
5
6
// --- NativeInputManager ---

class NativeInputManager : public virtual RefBase,
public virtual InputReaderPolicyInterface,
public virtual InputDispatcherPolicyInterface,
public virtual PointerControllerPolicyInterface {

上面是NativeInputManager类的定义,其实现了InputReaderPolicyInterface和InputDispatcherPolicyInterface接口,那么我们可以推断出Android输入系统的主要参与者InputReaderPolicy和InputDispatcherPolicy的接口是NativeInputManager来实现,只提供了接口实现,而不是具体实现。

接下来了解NativeInputManager的构造函数的实现

\frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp->NativeInputManager::NativeInputManager()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
NativeInputManager::NativeInputManager(jobject contextObj,
jobject serviceObj, const sp<Looper>& looper) :
mLooper(looper), mInteractive(true) {
JNIEnv* env = jniEnv();

mContextObj = env->NewGlobalRef(contextObj);
mServiceObj = env->NewGlobalRef(serviceObj);

{
AutoMutex _l(mLock);
mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
}
mInteractive = true;
//创建EventHub对象,又一个Android输入系统的主要参与者出现了
sp<EventHub> eventHub = new EventHub();
//创建Native层InputManager对象
mInputManager = new InputManager(eventHub, this, this);
}

有上面NativeInputManager构造函数的实现可以了解,NativeInputManager主要创建了EventHub和InputManager两个重要对象,EventHub在上面有简单简绍,接下来我们了解InputManager

\frameworks\native\services\inputflinger\InputManager.cpp->InputManager::InputManager()

1
2
3
4
5
6
7
8
9
10
11
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
//创建InputDispatcher对象
mDispatcher = new InputDispatcher(dispatcherPolicy);
//创建InputReader对象
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
//调用初始化方法
initialize();
}

\frameworks\native\services\inputflinger\InputManager.cpp->InputManager::initialize()

1
2
3
4
5
6
void InputManager::initialize() {
//创建InputReader的运行线程
mReaderThread = new InputReaderThread(mReader);
//创建InputDispatcher的运行线程
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

InputManager的构造中主要创建了4个对象,其中InputDispatcher和InputReader是IMS的主要参与者,InputDispatcher构造参数dispatcherPolicy,InputReader构造参数readerPolicy都是NativeInputManager对象。

由以上的各种构造代码流程梳理中我们来总结下IMS的整体结构和功能。

  • Java组件:

1.Java层InputManagerService主要为ReaderPolicy和DispatcherPolicy提供具体实现

2.Java层InputManagerService与WMS系统服务进行交互

  • Native组件:
    1.NativeInputManager位于IMS的jni层,主要负责Native层和Java层的通信,同时为InputReader和InputDispatcher提供了策略请求接口。将策略请求转发给Java层,有Java层IMS进行具体实现和操作。

2.InputManager是InputReader和InputDispatcher的拥有者,它分别为InputReader和InputDispatcher创建提供了运行线程

1.2.3 IMS的启动

通过上面一系列构造,SystemServer中创建了InputManagerService对象,那么接下来调用start()启动InputManagerService。

\frameworks\base\services\core\java\com\android\server\input\InputManagerService.java->start()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void start() {
Slog.i(TAG, "Starting input manager");
//启动本地Service
nativeStart(mPtr);

// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);

registerPointerSpeedSettingObserver();
registerShowTouchesSettingObserver();
registerAccessibilityLargePointerSettingObserver();

mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updatePointerSpeedFromSettings();
updateShowTouchesFromSettings();
updateAccessibilityLargePointerFromSettings();
}
}, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);

updatePointerSpeedFromSettings();
updateShowTouchesFromSettings();
updateAccessibilityLargePointerFromSettings();
}

\frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp->nativeStart()

1
2
3
4
5
6
7
8
9
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
//获取NativeInputManager对象
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
//调用NativeInputManager的启动方法
status_t result = im->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}

\frameworks\native\services\inputflinger\InputManager.cpp->InputManager::start()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
status_t InputManager::start() {
//运行DispatcherThread线程
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputDispatcher thread due to error %d.", result);
return result;
}

//运行ReaderThread线程
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
if (result) {
ALOGE("Could not start InputReader thread due to error %d.", result);

mDispatcherThread->requestExit();
return result;
}

return OK;
}

随着两个主要线程的启动IMS进入工作状态。当InputManager构造DispatcherThread和ReaderThread现成的时候并未立即启动,而是万事俱备,start()做了东风。

当两个线程启动后,InputReader在其线程中不断的从EventHub中抽去原始输入事件,进行加工处理后放入InputDispatcher的派发队列中,

InputDispatcher在其线程中将派发队列中的事件去除,查找合适窗口,将事件写入到窗口的事件接收管道中。

窗口事件接收线程中Looper从管道中将事件取出,交由事件处理函数进行事件响应。整个过程有3个线程首尾相接。通过层层交接,将事件交付给事件处理函数。

1
设备节点-Reader->派发队列-Dispatcher->窗口管道------Looper->事件处理者

2 IMS运行流程和工作原理分析

通过上面流程可以将IMS流程划分成3部分:

1.设备节点-Reader->派发队列

2.派发队列-Dispatcher->窗口管道

3.窗口管道-Looper->事件处理者

接下来,从上面三个部分进行分别解析,从而呈现整个IMS的运行流程

在学习这三个流程之前,需要一些基础知识,请查看:Android_INotify与EpollAndroid输入系统之EventHub*

2.1 设备节点-Reader->派发队列

InputReader被InputManager创建并运行于InputReaderThread线程中。InputReaderThread继承自C++的Thread类,Thread类中提供了一个threadLoop()的纯虚函数,当线程开始运行时,将会在内部线程循环中不断调用threadLoop(),知道此函数返回false,则退出循环,结束线程运行。

2.1.1 InputReader运行流程

\frameworks\native\services\inputflinger\InputReader.cpp->InputReaderThread::threadLoop()

1
2
3
4
5
bool InputReaderThread::threadLoop() {
//执行InputReader的loopOnce()函数
mReader->loopOnce();
return true;
}

InputReaderThread实现了threadLoop()方法,其中调用了InputReader的loopOnce()方法,由C++特性可知threadLoop()会不断循环调用,接下来重点分析loopOnce()方法。

\frameworks\native\services\inputflinger\InputReader.cpp->InputReader::loopOnce()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void InputReader::loopOnce() {
......
//通过EventHub对象获取事件列表(保存在mEventBuffer中),并返回事件个数
//当EventHub中无事件可以抽取时,此函数的调用将会阻塞直到事件到来或者超时
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

{ // acquire lock
......

if (count) {
//如果事件个数大于0,则对时间进行加工处理
processEventsLocked(mEventBuffer, count);
}

......
} // release lock

// 如果输入设备改变,发出一条消息,描述改变的输入设备。
if (inputDevicesChanged) {
mPolicy->notifyInputDevicesChanged(inputDevices);
}

// 将队列中获取的事件刷新到mQueuedListener
// 这必须发生在锁之外,因为监听器可能会回调到InputReader的方法中
// 例如getScanCodeState,或者在另一个线程上被阻塞,同样等待获取InputReader锁,从而导致死锁
// 监听器实际上是InputDispatcher,它调用进入窗口管理器,这偶尔会调用到InputReader
// 通过调用flush()函数将事件交付给InputDispatcher
mQueuedListener->flush();
}

InputReader的loopOnce()方法中主要做了三件事:

1.从EventHub中获取未处理的所有事件,这些事件分为两类,一类是从设备节点中读取的原始输入事件,另一类则是输入设备可用性变化事件,简称为设备事件。

2.调用processEventsLocked()对事件进行处理,对于设备事件,此函数对根据设备的可用性加载或移除设备对应的配置信息。对于原始输入事件,则在进行转译、封装与加工后将结果暂存到mQueuedListener中。

3.调用mQueuedListener.flush()将所有暂存的输入事件一次性地交付给InputDispatcher。

2.1.2 原始事件封装

原始事件入口是processEventsLocked(),那么从processEventsLocked函数开始分析。

\frameworks\native\services\inputflinger\InputReader.cpp->InputReader::processEventsLocked()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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;
while (batchSize < count) {
if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
|| rawEvent[batchSize].deviceId != deviceId) {
break;
}
batchSize += 1;
}
//处理属于某一设备的一批事件,batchSize表示属于此设备的输入事件个数
processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
} else {
//处理设备增删事件
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED:
addDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::DEVICE_REMOVED:
removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
break;
case EventHubInterface::FINISHED_DEVICE_SCAN:
handleConfigurationChangedLocked(rawEvent->when);
break;
default:
ALOG_ASSERT(false); // can't happen
break;
}
}
count -= batchSize;
rawEvent += batchSize;
}
}

processEventsLocked()函数分别处理原始输入事件和设备增删事件,暂不讨论设备增删,对于原始事件EventHub会将同一输入设备的所有原始事件放在一起,交给processEventsForDeviceLocked()函数进行批量处理。

\frameworks\native\services\inputflinger\InputReader.cpp->InputReader::processEventsForDeviceLocked()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void InputReader::processEventsForDeviceLocked(int32_t deviceId,
const RawEvent* rawEvents, size_t count) {
//InputReader中保存了一个mDevices字典,这个字典以deviceId为键存储了一系列InputDevice对象
ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
if (deviceIndex < 0) {
return;
}

InputDevice* device = mDevices.valueAt(deviceIndex);
if (device->isIgnored()) {
return;
}

//对这批事件进行处理
device->process(rawEvents, count);
}

上面代码中出现了InputDevice类,并且调用了此类的process()函数进行原始事件批处理

\frameworks\native\services\inputflinger\InputReader.cpp->InputDevice::process()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
size_t numMappers = mMappers.size();
for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
if (mDropUntilNextSync) {//处理事件同步错误
if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
mDropUntilNextSync = false;
} else {
}
} else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
mDropUntilNextSync = true;
reset(rawEvent->when);
} else {
//出现了InputMapper对象列表,将事件传入了InputMapper对象中进行处理
for (size_t i = 0; i < numMappers; i++) {
InputMapper* mapper = mMappers[i];
mapper->process(rawEvent);
}
}
}
}

processEventsLocked()函数将属于同一设备的输入事件列表交由processEventsForDeviceLocked()处理,processEventsForDeviceLocked()再将列表交给InputDevice::process(),InputDevice::process()将事件逐个交给每一个InputMapper的process()进行处理。

在InputReader中也有一个类InputDevice来存储输入设备信息,和EventHub一样,InputDevice描述了一个输入设备,并以设备Id为键保存在mDevices字典中。InputDevice类和EventHub的Debice结构体类似,也保存了设备Id、厂商信息以及设备所属的类别。他们之间重要的差别是,InputDevice相对Device多了一个InputMapper列表。

InputMapper是InputReader中原始事件的实际加工场所,它有一系列子类,分别用于加工不同类型的原始输入事件。InputDevice的process函数使用InputMapper的方式是一个简化了的职责连设计模式。InputDevice不需知道哪一个InputMapper可以处理一个原始输入事件,只需将一个事件逐个交给每一个InputMapper尝试处理,如果能处理则处理,不能处理则什么都不做。

2.1.3 InputDevice与InputMapper

InputDevice用来表示一个输入设备,其增删操作与EventHub的设备增删有关。具体操作见InputReader::processEventsLocked()函数。

InputDevice创建

InputDevice的创建过程主体,就是从EventHub中读取InputReader所感兴趣的设备信息,然后根据这些信息创建InputDevice对象。

\frameworks\native\services\inputflinger\InputReader.cpp->InputReader::addDeviceLocked()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
if (deviceIndex >= 0) {
return;
}
//从EventHub中获取厂商信息和设备类别
InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
uint32_t classes = mEventHub->getDeviceClasses(deviceId);
int32_t controllerNumber = mEventHub->getDeviceControllerNumber(deviceId);

//创建一个InputDevice对象
InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
//使用InputReader中保存的策略配置信息对新建的device进行配置,并通过reset()进行设备重置
device->configure(when, &mConfig, 0);
device->reset(when);

......

//将设备放入mDevices字典中
mDevices.add(deviceId, device);
bumpGenerationLocked();

if (device->getClasses() & INPUT_DEVICE_CLASS_EXTERNAL_STYLUS) {
notifyExternalStylusPresenceChanged();
}
}

\frameworks\native\services\inputflinger\InputReader.cpp->InputReader::createDeviceLocked()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
const InputDeviceIdentifier& identifier, uint32_t classes) {
//根据设备Id、厂商信息以及设备类型创建一个InputDevice对象,InputDevice构造保存了这些信息,无其他处理
InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
controllerNumber, identifier, classes);

......
//后续一系列代码根据设备类型为设备添加特定的InputMapper
// Switch-like devices.
if (classes & INPUT_DEVICE_CLASS_SWITCH) {
device->addMapper(new SwitchInputMapper(device));
}

......

return device;
}

如上代码清除的描述了InputDevice的创建过程,其保存了设备Id、厂商信息、以及设备类型,并根据设备类型向InputDevice中添加了一系列各种类型的InputMapper。InputReader还会是用其当前的策略配置对InputDevice进行配置,使其事件处理过程中能够根据IMS或应用程序的需求进行调配。

InputMapper分配

由上面可知,InputMapper完成了原始输入事件的加工处理,因此InputMapper的分配依据至关重要

从createDeviceLocked()函数实现可知,InputMapper的分配依据是设备类型,设备类型信息来自EventHub的Device结构体。在Android输入系统之EventHub一文中提到过Device结构体中保存的设备类别的设置依据来自从设备节点读取的事件位掩码。

Device结构体的事件位掩码描述了4种类型的输入事件

1.EV_KEY 按键事件类型

上报者:键盘、鼠标、手柄、手写板等一切拥有按钮的设备(包括手机实体按键)。

Device结构体中,对应的事件位掩码keyBitmask描述了设备可产生的按键事件集合。按键事件全集包括,字符按键、方向键、控制键、鼠标键、游戏按键等。

2.EV_ABS 绝对坐标事件类型

上报者:触控板,触摸屏。事件位掩码absBitmask描述了设备可以上报的事件的维度(ABS_X, ABS_Y, ABS_Z),以及是否支持多点触控事件。

3.EV_REL 相对坐标事件类型

此类事件描述了事件在空间中相对于上次事件的偏移量。鼠标、轨迹球等基于游戏指针的设备可以上报此类事件。事件位掩码relBitmask描述了设备可以上报的事件的维度信息(REL_X, REL_Y, REL_Z)。

4.EV_SW 开关事件类型

此类事件描述了若干固定状态之间的切换。手机上的静音模式开关按钮、模式切换拨盘等设备可以上报此类事件。事件位掩码swBitmask表示了设备可切换的状态列表。

另外还有两个事件位掩码描述了设备的反馈能力。ledBitmask描述了设备是否支持光反馈,ffBitmask描述了设备是否支持力反馈。对于这类设备,EventHub提供了接口setLedState()与vibrate()来启动光反馈和力反馈。

通过事件位掩码确定了设备可以上报的事件类型后,便可以据此确定设备类型了,Android在EventHub.h中定义了12种设备类型。

\frameworks\native\services\inputflinger\EventHub.h->enum{}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
* Input device classes.
*/
enum {
/* 鼠标按键以外的EV_KEY类型事件的设备,例如键盘,机身按键 */
INPUT_DEVICE_CLASS_KEYBOARD = 0x00000001,

/* 字符按键设备,例如键盘,此类型设备必定同时属于KEYBOARD */
INPUT_DEVICE_CLASS_ALPHAKEY = 0x00000002,

/* 可以上报EV_ABS类型事件的设备,如触摸板,触控板 */
INPUT_DEVICE_CLASS_TOUCH = 0x00000004,

/* 上报EV_REL类型事件,并且可以上报BRN_MOUSE子类的EV_KEY事件的设备属于此类,例如鼠标和轨迹球 */
INPUT_DEVICE_CLASS_CURSOR = 0x00000008,

/* 可以上报EV_ABS类型事件,并且其事件位掩码指示其支持多点事件的设备属于此类,例如多点触摸屏,此类设备同时属于TOUCH类型 */
INPUT_DEVICE_CLASS_TOUCH_MT = 0x00000010,

/* 方向键,例如键盘、手机导航键等,这种类型同事也属于KEYBOARD */
INPUT_DEVICE_CLASS_DPAD = 0x00000020,

/* 游戏按键的设备,如游戏手柄,同事属于KEYBOARD */
INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040,

/* EV_SW类型事件的设备 */
INPUT_DEVICE_CLASS_SWITCH = 0x00000080,

/* 属于CAMEPAD类型,并属于TOUCH类型的设备 */
INPUT_DEVICE_CLASS_JOYSTICK = 0x00000100,

/* 支持力反馈类型的设备 */
INPUT_DEVICE_CLASS_VIBRATOR = 0x00000200,

/* The input device has a microphone. */
INPUT_DEVICE_CLASS_MIC = 0x00000400,

/* The input device is an external stylus (has data we want to fuse with touch data). */
INPUT_DEVICE_CLASS_EXTERNAL_STYLUS = 0x00000800,

/* The input device has a rotary encoder */
INPUT_DEVICE_CLASS_ROTARY_ENCODER = 0x00001000,

/* 虚拟设备 */
INPUT_DEVICE_CLASS_VIRTUAL = 0x40000000,

/* 外部设备,即非內建设备 */
INPUT_DEVICE_CLASS_EXTERNAL = 0x80000000,
};

确定了设备类型后,createDeviceLocked()函数为InputDevice分配InputMapper,下面是事件类型到InputMapper的分配过程图(前缀相同所以省略所有前缀)。

事件类型到InputMapper的分配过程

分析到InputMapper就先告一段落。后面有时间再补充

2.1.4 各类型事件的加工处理

2.1.5 原始事件封装

2.1.6 原始事件封装

2.1.7 InputReader总结

在代码中可以看出事件的读取操作是由EventHub完成的,而InputReader的主要职责是:原始输入事件的整合,变换,和Android输入事件的生成。

原始输入事件结构简单,信息量少,可用性差,而且其所携带的事件信息往往与硬件实现有关。因此有必要将原始输入事件转换成信息更丰富、可用性更佳的高级事件,这也就是InputReader存在于输入系统的意义所在。

InputMapperReader流程图

2.2 派发队列-Dispatcher->窗口管道

通过上面解析可知,当事件进入InputDispatcher时,输入事件已被InputReader加工成为三种类型:Key事件,Motion事件,Switch事件。

那么接下来,看看InputDispatcher中事件的派发流程和各类型事件的特点。在事件派发过程中InputDispatcherPolicy即将开始工作。

InputDispatcher实现了InputListenerInterface,在InputReader中mQueuedListener->flush(),将事件以NotifyXXXArgs结构体的形式交给InputDispatcher。

在InputDispatcher中又是怎样处理接收到的事件的呢?

2.2.1 派发队列的定义

首先我们找到派发队列,然后根据派发队列的操作,找出派发队列的数据变化,即注入事件。

派发队列的定义

\frameworks\native\services\inputflinger\InputDispatcher.h

1
2
//定义派发队列
Queue<EventEntry> mInboundQueue;

2.2.2 Key事件注入派发队列流程

找到了派发队列对象,具体的key事件从InputReader中传递过来后,又是怎样注入到队列中的呢?

\frameworks\native\services\inputflinger\InputDispatcher.cpp->InputDispatcher::notifyKey()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
//验证参数有效性,对于KeyEvent来说,主要验证key事件信息合法性,(即:必须有Down和Up两个操作之一)
if (!validateKeyEvent(args->action)) {
return;
}

//获取事件的派发策略
uint32_t policyFlags = args->policyFlags;
int32_t flags = args->flags;
int32_t metaState = args->metaState;
//根据policyFlag修改此事件的metaState
if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {
policyFlags |= POLICY_FLAG_VIRTUAL;
flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
}
if (policyFlags & POLICY_FLAG_FUNCTION) {
metaState |= AMETA_FUNCTION_ON;
}
//从InputReader过来的事件都是TRUSTED
policyFlags |= POLICY_FLAG_TRUSTED;

//获取keyCode,并做一系列过滤处理
int32_t keyCode = args->keyCode;
if (metaState & AMETA_META_ON && args->action == AKEY_EVENT_ACTION_DOWN) {
int32_t newKeyCode = AKEYCODE_UNKNOWN;
if (keyCode == AKEYCODE_DEL) {
newKeyCode = AKEYCODE_BACK;
} else if (keyCode == AKEYCODE_ENTER) {
newKeyCode = AKEYCODE_HOME;
}
if (newKeyCode != AKEYCODE_UNKNOWN) {
AutoMutex _l(mLock);
struct KeyReplacement replacement = {keyCode, args->deviceId};
mReplacedKeys.add(replacement, newKeyCode);
keyCode = newKeyCode;
metaState &= ~AMETA_META_ON;
}
} else if (args->action == AKEY_EVENT_ACTION_UP) {
// In order to maintain a consistent stream of up and down events, check to see if the key
// going up is one we've replaced in a down event and haven't yet replaced in an up event,
// even if the modifier was released between the down and the up events.
AutoMutex _l(mLock);
struct KeyReplacement replacement = {keyCode, args->deviceId};
ssize_t index = mReplacedKeys.indexOfKey(replacement);
if (index >= 0) {
keyCode = mReplacedKeys.valueAt(index);
mReplacedKeys.removeItemsAt(index);
metaState &= ~AMETA_META_ON;
}
}

//向DispatcherPolicy询问key事件的派发策略。和Motion中的interceptMotionBeforeQueueing()不同
//获取key派发策略的函数需要KeyEvent作为参数
KeyEvent event;
event.initialize(args->deviceId, args->source, args->action,
flags, keyCode, args->scanCode, metaState, 0,
args->downTime, args->eventTime);

mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);

bool needWake;
{ // acquire lock
mLock.lock();

if (shouldSendKeyToInputFilterLocked(args)) {
mLock.unlock();

//进入队列前使用InputFilter进行一次过滤,当过滤结果为false,该事件则被忽略
policyFlags |= POLICY_FLAG_FILTERED;//该标记表示,已经做过过滤处理
if (!mPolicy->filterInputEvent(&event, policyFlags)) {
return; // event was consumed by the filter
}

mLock.lock();
}

int32_t repeatCount = 0;
//使用NotifyKeyArgs中的信息构造一个KeyEvent对象,像MotionEntry一样注入事件队列
KeyEntry* newEntry = new KeyEntry(args->eventTime,
args->deviceId, args->source, policyFlags,
args->action, flags, keyCode, args->scanCode,
metaState, repeatCount, args->downTime);
//通过enqueueInboundEventLocked()函数将KeyEvent对象注入派发队列,返回值needWake表示派发线程是否处于休眠状态。
needWake = enqueueInboundEventLocked(newEntry);
mLock.unlock();
} // release lock
//唤醒派发线程
if (needWake) {
mLooper->wake();
}
}

2.2.3 Motion事件注入派发队列流程

notifyKey()函数中首先获取派发策略,然后验证,过滤,封装,最后将该事件注入到派发队列中。那么接下来我们看一下notifyMotion()

\frameworks\native\services\inputflinger\InputDispatcher.cpp->InputDispatcher::notifyMotion()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
......
//和KeyEvent类似,首先验证参数有效性,对Motion事件来说,主要是为了验证触控点的数量和Id是否在合理范围
if (!validateMotionEvent(args->action, args->actionButton,
args->pointerCount, args->pointerProperties)) {
return;
}

//获取派发策略
uint32_t policyFlags = args->policyFlags;
policyFlags |= POLICY_FLAG_TRUSTED;//同样从InputReader过来的事件都是TRUSTED
mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags);'

bool needWake;
{ // acquire lock
mLock.lock();
//把事件交给InputFilter进行过滤
if (shouldSendMotionToInputFilterLocked(args)) {
mLock.unlock();

MotionEvent event;
//使用NotifyMotionArgs参数中保存的事件信息,构造一个MotionEvent对象
event.initialize(args->deviceId, args->source, args->action, args->actionButton,
args->flags, args->edgeFlags, args->metaState, args->buttonState,
0, 0, args->xPrecision, args->yPrecision,
args->downTime, args->eventTime,
args->pointerCount, args->pointerProperties, args->pointerCoords);

policyFlags |= POLICY_FLAG_FILTERED;//标记此事件已被过滤
//有DispatcherPolicy启动过滤动作,当结果为false,此事件被忽略
if (!mPolicy->filterInputEvent(&event, policyFlags)) {
return; // event was consumed by the filter
}

mLock.lock();
}

// 使用NotifyMotionArgs参数中的时间信息构造一个MotionEntry,并通过enqueueInboundEventLocked()函数将其注入派发队列
MotionEntry* newEntry = new MotionEntry(args->eventTime,
args->deviceId, args->source, policyFlags,
args->action, args->actionButton, args->flags,
args->metaState, args->buttonState,
args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
args->displayId,
args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);

needWake = enqueueInboundEventLocked(newEntry);
mLock.unlock();
} // release lock

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

上面分别解析了Key和Motion事件注入队列的流程,发现以下特点

1.向DispatcherPolicy询问key事件的派发策略和Motion中的interceptMotionBeforeQueueing()不同获取key派发策略的函数需要KeyEvent作为参数

2.两种事件都是由DispatcherPolicy启动过滤动作

接下来了解具体的注入函数

2.2.4 事件注入派发队列整体流程解析

\frameworks\native\services\inputflinger\InputDispatcher.cpp->enqueueInboundEventLocked()

1
2
3
4
5
6
7
8
bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
//判断mInboundQueue为空,则断定派发线程为休眠状态,因此将事件注入mInboundQueue后,需要唤醒。
bool needWake = mInboundQueue.isEmpty();
//将事件注入到mInboundQueue队列末尾。
mInboundQueue.enqueueAtTail(entry);
//还有一堆代码,为HOME键或者其他事件的响应速度优化代码。这种优化操作可能会导致派发队列之前的所有事件被丢弃。
......
return needWake;

上面两处代码可以知道,到达InputDispatcher的Key事件,被保存在KeyEntry中,然后被注入到mInboundQueue队列末尾,mInboundQueue就是InputDispatcher的派发队列,KeyEntry是EventEntry的子类。EventEntry是输入事件在InputDispatcher中的存在形式。

由于InputDispatcher在没有派发事件时,mInboundQueue为空,派发线程将会进入休眠状态,因此将事件注入时,需唤醒派发线程。

2.2.5 派发线程的工作流程

\frameworks\native\services\inputflinger\InputDispatcher.cpp->InputDispatcherThread::threadLoop()

1
2
3
4
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}

和InputReader类似,在threadLoop()中循环执行mDispatcher->dispatchOnce()

\frameworks\native\services\inputflinger\InputDispatcher.cpp->InputDispatcher::dispatchOnce()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void InputDispatcher::dispatchOnce() {
......
{ // acquire lock
......
// 此处调用dispatchOnceInnerLocked()对事件进行派发。参数nextWakeupTime决定了下次派发线程循环的执行时间。
if (!haveCommandsLocked()) {
dispatchOnceInnerLocked(&nextWakeupTime);
}

// 执行命令队列中的命令,nextWakeupTime = LONG_LONG_MIN将使派发线程立即开始下次线程循环。
if (runCommandsLockedInterruptible()) {
nextWakeupTime = LONG_LONG_MIN;
}
} // release lock

// 如果有必要,将派发线程进入休眠状态,并由nextWakeupTime确定休眠的具体时间。
nsecs_t currentTime = now();
int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}

派发线程的依次循环做了如下工作:

1.进行一次事件派发。事件派发仅当命令队列中没有命令时才会执行,派发工作会设置nextWakeupTime指定随后休眠时间长短。

2.执行命令列表中的命令。命令时一个符合Command签名的回调函数,可以通过InputDispatcher::postCommandLocked()创建并添加到命令队列mCommandQueue中,InputDispatcher执行命令的过程类似于Handler的工作方式。

3.陷入休眠状态。Looper的pollOnce()的实质就是epoll_wait(),因此派发线程的休眠在3种情况下可能被唤醒:

调用Looper::wake()的函数主动唤醒(有输入事件注入派发队列时)

到达nextWakeupTime的时间点时唤醒

epol_wait()监听fd有epoll_event发生是唤醒(次唤醒方式与ANR机制有关)

nextWakeupTime:当派发队列中最后一个事件派发完成后,nextWakeupTime将被设置为LONG_LONG_MAX,使之在新的输入事件或命令到来前休眠以节约资源。
有时由于窗口尚未准备好接收事件(如已有一个时间发送给窗口,但是此窗口尚未对其进行反馈),则可以放弃此事件的派发并设置nextWakeupTime为一个合理的时间点,以便下次循环再尝试派发。

通过上面代码,系统开始了一次事件派发dispatchOnceInnerLocked()函数体现了派发的整体流程。

\frameworks\native\services\inputflinger\InputDispatcher.cpp->InputDispatcher::dispatchOnceInnerLocked()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
......
//所有事件都会到达这里进行处理
// 如果InputDispatcher被冻结,则返回
//setInputDispatcherMode()函数可以铁环InputDispatcher状态(禁用,冻结,正常)
if (mDispatchFrozen) {
#if DEBUG_FOCUS
ALOGD("Dispatch frozen. Waiting some more.");
#endif
return;
}
......
//从派发队列中取出一个事件进行派发
if (! mPendingEvent) {
// 派发队列是空,则直接返回,此时nextWakeupTime将保持LONG_LONG_MAX,因此派发队列将进入无期限休眠状态。
if (mInboundQueue.isEmpty()) {
if (isAppSwitchDue) {
// The inbound queue is empty so the app switch key we were waiting
// for will never arrive. Stop waiting for it.
resetPendingAppSwitchLocked(false);
isAppSwitchDue = false;
}

// Synthesize a key repeat if appropriate.
if (mKeyRepeatState.lastKeyEntry) {
if (currentTime >= mKeyRepeatState.nextRepeatTime) {
mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
} else {
if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
*nextWakeupTime = mKeyRepeatState.nextRepeatTime;
}
}
}

// Nothing to do if there is no pending event.
if (!mPendingEvent) {
return;
}
} else {
// 从派发队列中将位于队首的一条EventEntry取出,并保存在mPendingEvent成员中。
mPendingEvent = mInboundQueue.dequeueAtHead();
traceInboundQueueLengthLocked();
}

// Poke user activity for this event.
if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
pokeUserActivityLocked(mPendingEvent);
}

// 为此事件重置ANR信息。
resetANRTimeoutsLocked();
}
......
bool done = false;
//dropReason描述了事件是否需要被丢弃,后面代码中可了解导致事件丢弃的各种原因
DropReason dropReason = DROP_REASON_NOT_DROPPED;
if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
//将事件注入派发队列时曾向DispatcherPolicy询问过派发策略。如果派发策略不允许此事件被派发给用户,则设置对应的dropReason
dropReason = DROP_REASON_POLICY;
} else if (!mDispatchEnabled) {
//如果InputDispatcher被禁用(通过InputDispatcher::setInputDispatchMode),此事件则会被丢弃
//注意禁用和冻结的区别,冻结不会将事件丢弃,而是等待解冻后继续派发
dropReason = DROP_REASON_DISABLED;
}
......

//判断事件类型进行分别派发处理
switch (mPendingEvent->type) {
// 配置改变类型
case EventEntry::TYPE_CONFIGURATION_CHANGED: {
ConfigurationChangedEntry* typedEntry =
static_cast<ConfigurationChangedEntry*>(mPendingEvent);
done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
dropReason = DROP_REASON_NOT_DROPPED; // configuration changes are never dropped
break;
}

//设备重启类型
case EventEntry::TYPE_DEVICE_RESET: {
DeviceResetEntry* typedEntry =
static_cast<DeviceResetEntry*>(mPendingEvent);
done = dispatchDeviceResetLocked(currentTime, typedEntry);
dropReason = DROP_REASON_NOT_DROPPED; // device resets are never dropped
break;
}

//按键类型事件
case EventEntry::TYPE_KEY: {
KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
//因Home键没能及时响应而丢弃
if (isAppSwitchDue) {
if (isAppSwitchKeyEventLocked(typedEntry)) {
resetPendingAppSwitchLocked(true);
isAppSwitchDue = false;
} else if (dropReason == DROP_REASON_NOT_DROPPED) {
dropReason = DROP_REASON_APP_SWITCH;
}
}
//因事件过期而被丢弃
if (dropReason == DROP_REASON_NOT_DROPPED
&& isStaleEventLocked(currentTime, typedEntry)) {
dropReason = DROP_REASON_STALE;
}
//因阻碍了其他窗口获得事件而被丢弃
if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
dropReason = DROP_REASON_BLOCKED;
}
//执行dispatchKeyLocked()进行事件派发,如果派发完成,无论是成功还是事件被丢弃,都返回true。
//否则返回false,以便下次循环再次尝试派发此事件。
done = dispatchKeyLocked(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();
//立刻开始下一次循环。如果此时派发队列为空,下次循环调用此函数时会保持nextWakeupTime为
//LONG_LONG_MAX并直接返回,使得派发线程进入无限期循环
*nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
}

}

dispatchOnceInnerLocked()总结:

1.如果派发队列为空,则会使派发线程陷入无期限休眠状态。

2.即将被派发的事件从派发队列中取出并保存在mPendingEvent成员变量中。

3.事件有可能因为某些原因而被丢弃,被丢弃的原因保存在dropReason中。

4.不同类型的事件使用不同派发函数进行实际派发。如上面提到的Key事件使用dispatcherKeyLocked()函数进行派发。

5.派发一个事件至少需要一次线程循环。是否在下次循环继续尝试此事件派发,由派发函数的返回值决定。

6.事件派发是串行的,队首事件完成派发或被丢弃前,不会对后续事件进行派发。

派发事件至少需要一次线程循环才能完成的原因是时间的目标窗口有可能正处理上一个输入事件,在其完成上一个输入事件的处理并给于反馈之前,InputDispatcher不会再向此窗口派发新的事件。
当派发队列为空时,派发线程可能需要在下次循环中生成重复按键事件,因此不能直接进入休眠。

2.2.6 事件丢弃分析之DropReason

DropReason枚举完整的描述了时间被丢弃的原因

\frameworks\native\services\inputflinger\InputDispatcher.h->enum DropReason {}

1
2
3
4
5
6
7
8
    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所指定),则此事件需要被抛弃。

2.2.7 Motion事件目标窗口确定

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

\frameworks\native\services\inputflinger\InputDispatcher.cpp->InputDispatcher::dispatchMotionLocked()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
bool InputDispatcher::dispatchMotionLocked(
nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
// Preprocessing.
//标记事件已经正式进入派发流程
if (! entry->dispatchInProgress) {
entry->dispatchInProgress = true;
}

// Clean up if dropping the event.
// 对于被丢弃的事件,直接返回
if (*dropReason != DROP_REASON_NOT_DROPPED) {
setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
return true;
}

bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;

// Identify targets.
// inputTargets中保存了此事件的发送目标
Vector<InputTarget> inputTargets;

bool conflictingPointerActions = false;
int32_t injectionResult;
// 根据Motion事件的类型,寻找合适的目标窗口。其返回值injectionResult指明了查找结果,如果找到合适的窗口将保存在inputTargets中
if (isPointerEvent) {
// Pointer event. (eg. touchscreen)
// 对于坐标点形式的事件,如触摸屏点击等。将根据坐标点、窗口ZOrder与区域查找目标窗口
injectionResult = findTouchedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
} else {
// Non touch event. (eg. trackball)
// 对于其他类型Motion事件(轨迹球等),将以拥有焦点的窗口做为目标
injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
}
// INPUT_EVENT_INJECTION_PENDING表示找到一个窗口,如果窗口处于无响应状态,则返回false,
// 也就是这个事件尚未派发完成,将在下次派发线程的循环中再次尝试派发。
if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
return false;
}

setInjectionResultLocked(entry, injectionResult);

//返回值不为SUCCEEDED,表示无法找到合适窗口,例如窗口未获取焦点,或点击位置没落在任何一个窗口内,此事件将被直接丢弃
if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
if (injectionResult != INPUT_EVENT_INJECTION_PERMISSION_DENIED) {
CancelationOptions::Mode mode(isPointerEvent ?
CancelationOptions::CANCEL_POINTER_EVENTS :
CancelationOptions::CANCEL_NON_POINTER_EVENTS);
CancelationOptions options(mode, "input event injection failed");
synthesizeCancelationEventsForMonitorsLocked(options);
}
return true;
}

// TODO: support sending secondary display events to input monitors
// 向inputTargets列表中添加特殊的接收目标
if (isMainDisplay(entry->displayId)) {
addMonitoringTargetsLocked(inputTargets);
}

// Dispatch the motion.
if (conflictingPointerActions) {
CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
"conflicting pointer actions");
synthesizeCancelationEventsForAllConnectionsLocked(options);
}
//将事件派发给inputTargets列表中的目标
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}

对于被丢弃的事件直接返回true。

给事件寻找合适的目标窗口,目标窗口分为普通窗口和监听窗口(monitoring),普通窗口通过按点查找与按焦点查找两种方式获得,而监听窗口则无条件监听所有输入事件。普通窗口的查找结果决定了此次线程循环是否可以完成事件派发。

如果成功找到可以接收事件的目标窗口,则调用dispatchEventLocked()函数进行派发。

查找到的派发目标存储在InputTarget结构体中

\frameworks\native\services\inputflinger\InputDispatcher.h->struct InputTarget{}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//输入目标指定如何将输入事件分派到特定的窗口,包括窗口的输入通道,控制标志,超时以及要添加到输入事件坐标以补偿窗口的绝对位置的X / Y偏移量 区。
struct InputTarget {
......

// InputChannel,是链接InputDispatcher和窗口的通信管道。InputDispatcher通过它将事件派发给目标窗口
// 并通过它来读取来自目标窗口对事件的响应。
sp<InputChannel> inputChannel;

// 事件的派发选项。将使得事件派发时可以自动产生一些辅助的输入事件。
// 向此目标窗口发送的事件内容有可能因这个flag的设置而发生改变。
int32_t flags;

// 目标窗口相对于屏幕坐标系的偏移量和缩放参数。
// (ignored for KeyEvents)
float xOffset, yOffset;

// 在交付时适用于MotionEvent的比例因子。
// (ignored for KeyEvents)
float scaleFactor;

// 指向该输入目标的运动事件中包含的指针ID的子集
// if FLAG_SPLIT is set.
BitSet32 pointerIds;
}

接下来看看按点查询和按焦点查询两种方式如何获取合适的InputTarget对象的。

一、坐标点查找目标窗口

\frameworks\native\services\inputflinger\InputDispatcher.cpp->InputDispatcher::findTouchedWindowTargetsLocked()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
bool* outConflictingPointerActions) {

......
if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {

......
// 从MotionEntry中获取事件坐标点
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));
......

// 从前到后浏览窗口,找到触摸窗口和外部目标。
// mWindowHandles中保存的InputWindowHandler类保存了窗口的InputChannel以及InputWindowInfo结构体
// InputWindowInfo结构体保存了窗口的各种布局信息,包括可见性、位置、尺寸、flag等。
size_t numWindows = mWindowHandles.size();

// 遍历mWindowHandles中的所有WindowHandle,检查时间坐标是否落在其上
for (size_t i = 0; i < numWindows; i++) {
sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
// 获取保存窗口布局信息的WindowInfo
const InputWindowInfo* windowInfo = windowHandle->getInfo();
if (windowInfo->displayId != displayId) {
continue; // wrong display
}
// 获取窗口flag
int32_t flags = windowInfo->layoutParamsFlags;
if (windowInfo->visible) {
if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
// 检查窗口是否指明了FLAG_NOT_TOUCH_MODAL选项
isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
| InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
// 一下是选择目标窗口的两个关键条件:坐标点落在窗口内,或者此窗口没有指定FLAG_NOT_TOUCH_MODAL选项
if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
newTouchedWindowHandle = windowHandle;
break; // found touched window, exit window loop
}
}

......
}
}
// 把选中的窗口保存到TempTouchState中,以便后续处理
mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
......
// 检查mTempTouchState中所有目标窗口是否准备好接受新的输入事件
for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
// Check whether the window is ready for more input.
String8 reason = checkWindowReadyForMoreInputLocked(currentTime,
touchedWindow.windowHandle, entry, "touched");
if (!reason.isEmpty()) {
// 如果窗口不能接收新事件,则记录不能接收的原因,并设置nextWakeupTime为5s后,如果5s后
// 线程仍未将次事件派发成功而进入这个分支,则会开始向Java层通报ANR。
// 在这里injectionResult被设置为INPUT_EVENT_INJECTION_PENDING
injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
NULL, touchedWindow.windowHandle, nextWakeupTime, reason.string());
// 因此次查找到的窗口不能接收事件,所以调过后续产生InputTarget的过程
goto Unresponsive;
}
}
}

......
// Success! Output targets.
// 如果执行到这里,说明窗口的查找过程一切顺利,设置injectionResult为SUCCEEDED,并将injectionResult放入参数inputTargets中
injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
// 为每一个mTempTouchState中的窗口生成InputTargets
for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i);
addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
touchedWindow.pointerIds, inputTargets);
}

.......

Unresponsive:
......

return injectionResult;
}

此函数中出现的mWindowHandles保存了所有窗口的信息,为查找输入窗口提供依据,因此每当窗口形态发生变化是,WMS会通过IMS将所有窗口提交到InputDispatcher,完成mWindowHandles的更新。

mWindowHandles中的窗口顺序,索引越小,ZOrder越大,这与WMS中的窗口列表相反

此函数做了三项工作

1.根据窗口点击坐标和事件发生坐标选择合适的目标窗口(ZOrder由上至下遍历,ZOrder越靠上,优先权越高),另外如果窗口没有在其LayoutParams.flag中指明FLAG_NOT_TOUCH_MODAL选项,说明是模式窗口,模式窗口将会阻止点击事件派发给其子窗口。

2.调用checkWindowReadyForMoreInputLocked()函数价差窗口是否可接收新的点击事件,如果无法接收,说明此窗口有可能发生ANR。handleTargetsNotReadyLocked()做下记录,并将injectionResult设置为PENDING,要求下次派发循环中重试。

3.如果找到可以接收新事件的窗口,则调用addWindowTargetLocked()生成InputTarget,并添加到inputTargets列表中。(InputTarget中几乎所有字段可在InputWindowHandle中找到)

二、根据焦点查找目标窗口

此项操作由findFocusedWindowTargetsLocked()函数完成,其主要流程与坐标查询类似,InputDispatcher中有一个名为mFocusedWindowHandle的InputWindowHandle对象,所以InputDispatcher只要判断此对象是否为NULL即可。

WMS在进行布局操作时,会根据处于焦点状态的Activity、窗口的属性与ZOrder确定处于焦点状态的窗口,并随同窗口列表一起提交至InputDispatcher

2.2.8 向窗口发送事件

当合适的目标窗口被确定以后,便可以开始发送事件给窗口了,现在回到dispatchMotionLocked(),最后一步调用了dispatchEventLocked(),将事件发送给指定的InputTarget。

\frameworks\native\services\inputflinger\InputDispatcher.cpp->InputDispatcher::dispatchEventLocked()

1
2


2.3窗口管道-Looper->事件处理者