b. display & window之事件
Android系统的事件输入基于linux的Input子系统。触控屏事件和按键事件,从哪里输入又往哪里输出,虽然这个流程大部分对于Android系统来说是透明的,但是在多display,多窗口,多屏幕的情况下是值得研究的。
input对应各种输入事件,通过IMS,WMS,给到对应的window去处理事件,附一张最简化的事件传递流程:
1. Android Input子系统简介
1.1 事件的输入
事件的输入,是从不同类获的设备得到不同的事件类型,事件类型也都是定义在/include/linux/input.h文件中。
adb shell getevent -l -c 16
add device 1: /dev/input/event0
name: "ACCDET"
add device 2: /dev/input/event3
name: "goodix_ts"
add device 3: /dev/input/event2
name: "aw8697_haptic"
add device 4: /dev/input/event1
name: "mtk-kpd"
//按键
adb shell getevent -l /dev/input/event1
EV_KEY KEY_VOLUMEDOWN DOWN
EV_SYN SYN_REPORT 00000000
EV_KEY KEY_VOLUMEDOWN UP
EV_SYN SYN_REPORT 00000000
EV_KEY KEY_VOLUMEUP DOWN
EV_SYN SYN_REPORT 00000000
EV_KEY KEY_VOLUMEUP UP
EV_SYN SYN_REPORT 00000000
//触摸
adb shell getevent -l /dev/input/event3
EV_ABS ABS_MT_TRACKING_ID 0000036c
EV_ABS ABS_MT_POSITION_X 000001fe
EV_ABS ABS_MT_POSITION_Y 00000717
EV_KEY BTN_TOUCH DOWN
EV_SYN SYN_REPORT 00000000
EV_ABS ABS_MT_POSITION_X 00000207
EV_SYN SYN_REPORT 00000000
EV_ABS ABS_MT_POSITION_X 00000219
EV_ABS ABS_MT_POSITION_Y 00000716
Android设备可以同时连接多个输入设备,比如说触摸屏,键盘,鼠标等等。用户在任何一个设备上的输入就会产生一个中断,经由Linux内核的中断处理以及设备驱动转换成一个Event,并专递给用户空间的应用程序进行处理。每个输入设备都有自己的驱动程序,数据接口也不尽相同,如何在一个线程里(上面说过只有一个nputReader Thread)把所有的用户输入都给捕捉到:这首先要归功于Linux 内核的输入子系统 (Input Subsystem),它在各种各样的设备驱动程序上加了一个抽象层,只要底层的设备驱动程席按照这层抽象接口来实现,上层应用就可以通过统的接口来访问所有的输入设备。
1.2 事件在IMS中的流程
输入子系统对input设备和event处理分发的枢纽是EventHub,以下构造函数,主要是通过mEpollFd和mINotifyFd监听input事件和设备增删事件:
EventHub::EventHub(void)
: mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
mNextDeviceId(1),
mControllerNumbers(),
mNeedToSendFinishedDeviceScan(false),
mNeedToReopenDevices(false),
mNeedToScanDevices(true),
mPendingEventCount(0),
mPendingEventIndex(0),
mPendingINotify(false) {
ensureProcessCanBlockSuspend();
mEpollFd = epoll_create1(EPOLL_CLOEXEC);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
mINotifyFd = inotify_init();
mInputWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
LOG_ALWAYS_FATAL_IF(mInputWd < 0, "Could not register INotify for %s: %s", DEVICE_PATH,
strerror(errno));
if (isV4lScanningEnabled()) {
mVideoWd = inotify_add_watch(mINotifyFd, VIDEO_DEVICE_PATH, IN_DELETE | IN_CREATE);
LOG_ALWAYS_FATAL_IF(mVideoWd < 0, "Could not register INotify for %s: %s",
VIDEO_DEVICE_PATH, strerror(errno));
} else {
mVideoWd = -1;
ALOGI("Video device scanning disabled");
}
struct epoll_event eventItem = {};
eventItem.events = EPOLLIN | EPOLLWAKEUP;
eventItem.data.fd = 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);
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];
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno);
eventItem.data.fd = mWakeReadPipeFd;
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
}
这个方法的调用在InputReader里面启了一个线程,循环调用去读事件mEventHub->getEvents和处理 processEventsLocked(mEventBuffer, count);
status_t InputReader::start() {
if (mThread) {
return ALREADY_EXISTS;
}
mThread = std::make_unique<InputThread>(
"InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
return OK;
}
...
void InputReader::loopOnce() {
int32_t oldGeneration;
int32_t timeoutMillis;
bool inputDevicesChanged = false;
std::vector<InputDeviceInfo> inputDevices;
{ // acquire lock
std::scoped_lock _l(mLock);
oldGeneration = mGeneration;
timeoutMillis = -1;
uint32_t changes = mConfigurationChangesToRefresh;
if (changes) {
mConfigurationChangesToRefresh = 0;
timeoutMillis = 0;
refreshConfigurationLocked(changes);
} else if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
}
} // release lock
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
if (count) {
processEventsLocked(mEventBuffer, count);
}
...
// mEventHub->getEvents
mLock.unlock(); // release lock before poll
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
mLock.lock(); // reacquire lock after poll
输入事件处理,从这里找到指定的设备来处理。
再之后经过一系统复杂的调用,到InputDispacher来进行分发,具体分析流程可以见博文,讲得非常详细https://www.jianshu.com/p/d10cfe854654 ,同时在这些调用中,可以发现安卓事件分发响应为啥这么耗时,因为这里面流程复杂,且有相当多的耗时函数。这里的注释意思是所有事件都要保存按顺序执行。
分发过程中,要确定targetdisplayid,inputTargets,就是拿到channels。
InputPublisher通过 InputChannel::sendMessage将DispatchEntry发送给焦点窗口,而InputChannel的创建也就到了WMS给每个应用进程创建window的过程。
2. Input与WMS的连接InputChannel
IMS里面InputChannel::sendMessage发送消息
最终会调到enqueueDispatchEntryLocked里的 connection->outboundQueue.push_back(dispatchEntry.release());
发送消息和选取window是事件需要处理的两个问题,当确定哪个inputchannel的时候就已经有这两个答案了。
InputChannel其实就是linux unix socket的一种封装, unixsocket是linux的一种跨进程通信方式。系统创建InputChannel对即unix socket对,系统server端和程序client各只有其中一个,这样通过unix socket就可以给对方发送消息,而这里的事件就是通过这种方式从系统进程传递到程序进程的。系统InputChannel的整个处理逻辑如下:
在上篇中,viewroot创建时会根据需要创建一个inputchannel,传递给wms,window创建完之后,WMS addwindow的过程中,创建channel对,一个保存在window里,另一个传到程序端。
InputChannel.openInputChannelPair(name)在实现在native中,
frameworks/base/core/jni/android_view_inputChannel.cpp,frameworks/native/libs/input/InputTransport.cpp
通过registerInputChannel注册到native层。客户端ViewRootImpl.setView的时候会把创建的inputchannel,注册并加到线程的looper里面。
frameworks/base/core/jni/android_view_inputEventReceiver.cpp
至此InputChannel在ViewRootImpl中创建,在wms中创建连接socket,保存到IMS的连接池的过程就完成了。Input子系统的事件可以传到应用进程了。当接收到事件的时候,就会在应用层按如下流程处理,基本都是标准流程,就不展开说明了。
Anrdoid事件流程流程大体如文中几个图所示,需要特别注意的是:对系统来说,Inputdispatcher.cpp是一个非常重要的类,event的监听,设备的监听,与client端channel的连接,window的分发都是由他来处理的。
参考资料
[Android Input(五)-InputChannel通信] https://www.jianshu.com/p/b09afd403f71
[Android输入子系统之InputDispatcher分发键盘消息过程分析] https://blog.csdn.net/chenweiaiyanyan/article/details/72955048