做Android开发的少不了对触摸、按键事件进行处理,对于手机来说,主要是手势移动、触摸按下等,而TV主要通过遥控器、按键操作,按键事件不同于触摸事件,必须先获得焦点,然后才能移动、选择。
android输入设备支持鼠标、键盘(按键)、触摸屏(单点、多点)、轨迹球等,这些设备所产生的输入事件Input Event从底层驱动开始经过input子系统核心层到达Event Handler事件层,最终把事件copy_to_user到用户空间,然后由用户空间层获取这些事件进行分发、传递。整个过程涉及到内核层、Framework层以及应用层,内核层传递过程不在本文研究范围内,本文主要对按键事件在Framework层、应用层的传递过程进行分析(本文基于Android5.1.1版本),带着问题出发:
1. Framework层如何获得输入事件
2. Framework层获得按键事件后如何处理、存储
3. Framework层如何分发、传递给应用层
4. Framework层服务端管道和应用程序客户端管道如何创建、注册
5. 应用层如何从Framework层接收按键事件
6. 应用层接收到按键事件后如何传递
7. 特殊按键如何处理
8. 总结
要分析输入系统,最好先从输入事件系统服务InputManagerService入手,这是了解输入系统的起点,所有其他相关类、线程都因此被创建或间接创建。
1.1 InputManagerService的初始化
Android系统启动时,Android部分第一个用户空间进程zygote(注:如果从Linux角度出发,第一个用户空间进程是init)首先fork创建了SystemServer对象,随后SystemServer创建了核心服务ActivityManagerService、PowerManagerService、PackageManagerService、InputManagerService等,相关代码在SystemServer.Java的run方法中,与InputManagerService相关部分:
1.1.1 InputManagerService的构造方法
创建了一个InputManagerHandler对象,参数是HandlerThread中创建的looper对象
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
用Looper中的消息队列作为参数,调用本地方法nativeInit,返回C++中的NativeInputManager对象地址赋给mPtr,mPtr在1.5节会用到。
把InputManagerInternal.class和LocalService对象作为一对映射添加到ArrayMap
1.1.2 nativeInit方法实现
static jlongnativeInit(JNIEnv* env, jclassclazz,
jobjectserviceObj, jobjectcontextObj, jobjectmessageQueueObj) {
sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast(im);
}
android_os_MessageQueue_getMessageQueue方法:
sp android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobjectmessageQueueObj) {
jlongptr = env->GetLongField(messageQueueObj, gMessageQueueClassInfo.mPtr);
return reinterpret_cast(ptr);
}
获得了C++层NativeMessageQueue对象,NativeMessageQueue对象是在创建Looper对象时创建的,在本博客Handler机制中已经分析过
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
messageQueue->getLooper());
im->incStrong(0);
return reinterpret_cast(im);
创建了NativeInputManager对象,返回到java层的nativeInit,并将地址赋给mPtr;getLooper()获得c++层的Looper对象,在Handler机制中,此时Looper对象也已初始化,同时创建了管道并注册在epoll兴趣列表中
NativeInputManager的构造函数:
NativeInputManager::NativeInputManager(jobjectcontextObj,
jobjectserviceObj, const sp& 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;
}
sp eventHub = new EventHub();
mInputManager = new InputManager(eventHub, this, this);
}
把获得的Looper对象传给过来赋给mLooper,用来回调Looper对象的方法;创建c++层中全局变量mContextObj, mServiceObj;创建EventHub对象并作为参数创建InputManager对象。
1.1.3 EventHub.cpp的构造函数
初始化必要的变量,采用epoll机制系统调用接口epoll_create创建epoll对象,返回文件描述符mEpollFd,代表epoll对象;采用inotify机制监听文件或目录的移动、读取、写入或删除等事件,inotify机制主要包含2个系统调用接口:inotify_init, inotify_add_watch
inotify_init:创建一个inotify对象,如果成功,返回一个文件描述符,作为该对象;返回-1表示出错
int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE)
inotify_add_watch:把监控项添加到mINotifyFd对象的监控列表中,第二个参数DEVICE_PATH就是被监控对象,该对象一般是文件或目录,本文中被监控的DEVICE_PATH就是/dev/input目录;第三个参数是一个位掩码,表示被监控对象上发生的具体事件,可以由1个或多个掩码位或门组成。IN_DELETE:当被监控目录内删除文件或目录时触发该事件;IN_CREATE:当被监控目录内创建文件或目录时触发该事件。比如,插入、拔出鼠标时,就会触发该事件。
如果inotify_add_watch执行成功,返回一个非负监控描述符(假如为wd),代表被监控的项,如果将来不需要再监听了,可以使用inotify_rm_watch(fd, wd)删除所添加的监控项wd。
把inotify对象mINotifyFd添加到epoll对象的兴趣列表中,此处采用inotify与epoll机制结合起来检查文件
创建了管道对象读取端文件描述符mWakeReadPipeFd并添加到epoll对象的兴趣列表中
读到此处,提出2个问题:
Q1 epoll机制一般有3个系统调用接口,而截止到此处,只提到epoll_create, epoll_ctl,还缺epoll_wait,猜测在代码某处肯定有epoll_wait,否则epoll机制无法工作
Q2 如果找到了epoll_wait,调用进程处于等待状态,那么肯定还有一处用来唤醒调用进程的代码,比如Looper的wake函数或者其他地方调用了write系统调用
记住这两个问题,有助于在分析多进程控制时的代码走向
1.1.4 InputManager.cpp的构造器
mInputManager = new InputManager(eventHub, this, this);
InputManager::InputManager(
const sp& eventHub,
const sp& readerPolicy,
const sp& dispatcherPolicy) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
第一个参数是刚创建的EventHub对象,第二、三个参数都是this,当前NativeInputManager对象,传递过去后分别是InputReaderPolicyInterface、InputDispatcherPolicyInterface类型,这是为何?因为NativeInputManager继承了InputReaderPolicyInterface、InputDispatcherPolicyInterface类,实际类型还是NativeInputManager
用这两个传递过去的对象类型作为参数分别创建了InputDispatcher和InputReader对象,再调用initialize方法分别创建了与InputDispatcher和InputReader对应的线程InputDispatcherThread和InputReaderThread对象
1.1.5 InputDispatcher的构造函数
InputDispatcher::InputDispatcher(const sp& policy) :
mPolicy(policy),
mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX),
mNextUnblockedEvent(NULL),
mDispatchEnabled(false), mDispatchFrozen(false), mInputFilterEnabled(false),
mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
mLooper = new Looper(false);
mKeyRepeatState.lastKeyEntry = NULL;
policy->getDispatcherConfiguration(&mConfig);
}
把传递过来的NativeInputManager对象赋给mPolicy,该NativeInputManager对象又是InputDispatcherPolicyInterface接口类型,然后又初始化了很多变量,其中包括:
mLooper = new Looper(false);
创建并初始化了Looper对象,在Looper构造方法创建了管道并采用epoll机制把管道加入到兴趣列表中。
注意,此处创建的Looper对象是在InputDispatcher中的,与主线程中的Looper没有关系。
Q3 既然有了epoll_ctl,那肯定某处会调用epoll_wait
1.1.6 InputReader的构造函数
InputReader::InputReader(const sp& eventHub,
const sp& policy,
const sp& listener) :
mContext(this), mEventHub(eventHub), mPolicy(policy),
mGlobalMetaState(0), mGeneration(1),
mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
mConfigurationChangesToRefresh(0) {
mQueuedListener = new QueuedInputListener(listener);
{ // acquire lock
AutoMutex_l(mLock);
refreshConfigurationLocked(0);
updateGlobalMetaStateLocked();
} // release lock
}
第一个参数是EventHub对象,第二个policy赋值给mPolicy,policy是InputReaderPolicyInterface接口类型,实际是NativeInputManager对象类型。第三个listener实际是InputDispatcher类型,因为InputDispatcher实现了InputDispatcherInterface,InputDispatcherInterface又实现了InputListenerInterface,因此policy也是InputListenerInterface对象类型
mQueuedListener = new QueuedInputListener(listener);
把InputListenerInterface对象类型listener作为参数创建QueuedInputListener对象,传递过去后赋给mInnerListener变量
1.1.7 InputManager.cpp的initialize函数
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
用InputReader对象作为参数创建InputReaderThread线程对象,用InputDispatcher作为参数创建InputDispatcherThread线程对象,InputReaderThread的构造函数:
把InputReader对象赋给mReader变量
InputDispatcherThread的构造函数:
把InputDispatcher对象赋给mDispatcher变量
进程执行到此处,InputManagerService的初始化就完成,整个过程都是在创建、初始化各种对象。
Q4 既然创建了线程对象,那么就应该在某处启动线程,否则线程无法运行,在哪里启动线程
1.2 WindowManagerService的main方法
public static WindowManagerServicemain(final Contextcontext,
final InputManagerServiceim,
final boolean haveInputMethods, final boolean showBootMsgs,
final boolean onlyCore) {
final WindowManagerService[] holder = new WindowManagerService[1];
DisplayThread.getHandler().runWithScissors(new Runnable() {
@Override
public void run() {
holder[0] = new WindowManagerService(context, im,
haveInputMethods, showBootMsgs, onlyCore);
}
}, 0);
return holder[0];
}
mInputManager = inputManager;
main方法创建了WindowManagerService对象,第二个参数是InputManagerService对象,传递到WindowManagerService的构造函数中赋给了mInputManager
1.3 ServiceManager的addService方法
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
把InputManagerService服务注册到ServiceManager中
1.4 InputManagerService的setWindowManagerCallbacks方法
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
把InputMonitor对象传递到InputManagerService中去,方便回调,比如7.2节通过该参数回调InputMonitor中的interceptKeyBeforeQueueing方法
1.5 InputManagerService的start方法
start方法中有两句:
nativeStart(mPtr);
Watchdog.getInstance().addMonitor(this);
nativeStart的本地实现:
static void nativeStart(JNIEnv* env, jclassclazz, jlongptr) {
NativeInputManager* im = reinterpret_cast(ptr);
status_tresult = im->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
ptr在1.1.1节已经提到过,是NativeInputManager对象地址,只不过是长整形,此处再转化成NativeInputManager型指针
Watchdog是看门狗,一个单例类,addMonitor方法把InputManagerService对象添加到Watchdog,便于回调
Q5 创建了看门狗,有什么用?
nativeStart的传递过程: nativeStart —-> com_android_server_input_InputManagerService.cpp的nativeStart —-> InputManager.cpp的start()
1.5.1 InputManager.cpp的start
首先启动分发器线程InputDispatcherThread,调用run方法后,开始执行threadLoop方法,如果threadLoop返回true,就再次执行threadLoop方法直到requestExit方法停止线程;再调用InputReaderThread的run方法启动接收器线程。这回答了1.1.7节Q4问题,只要创建了线程,那就应该有启动线程的地方。
threadLoop中调用了mDispatcher->dispatchOnce()
1.5.2 InputDispatcher.cpp的dispatchOnce
一开始,CommandQueue队列中没有任何命令,haveCommandsLocked为false,if语句成立,执行dispatchOnceInnerLocked
1.5.2.1 InputDispatcher.cpp的dispatchOnceInnerLocked
mPendingEvent初始化为空,mInboundQueue队列也为空,下面语句:
if (! mPendingEvent) {
if (mInboundQueue.isEmpty()) {
成立,进入到if语句中
如果isAppSwitchDue为true,表明切换按键(比如HOME、拨号键)的执行时间还没有到,系统就自动放弃这一次的按键处理,并把isAppSwitchDue设置false
mKeyRepeatState.lastKeyEntry初始化为false,跳过:
if (mKeyRepeatState.lastKeyEntry) {
这条语句,继续:
if (!mPendingEvent) {
return;
}
mPendingEvent初始化为空,表示还没有等待处理的输入事件,if语句为true,执行return返回到dispatchOnce方法中继续后面语句的执行:
一开始,没有命令待处理,runCommandsLockedInterruptible返回false,跳过语句;如果有命令的话,就取出来处理
1.5.2.2 Looper的pollOnce
mLooper->pollOnce(timeoutMillis);
看到熟悉的语句了,pollOnce一直执行到epoll_wait监控管道读取端,如果管道有I/O事件发生,epoll_wait就会返回发生事件的个数;如果还没有事件,进程InputDispatcherThread在此等待,这就回答了Q3
Q6 既然有Looper的pollOnce方法,就有对应的唤醒方法,在哪里?
1.5.3 InputReader.cpp的loopOnce
void InputReader::loopOnce() {
int32_toldGeneration;
int32_ttimeoutMillis;
bool inputDevicesChanged = false;
Vector inputDevices;
{ // acquire lock
AutoMutex_l(mLock);
oldGeneration = mGeneration;
timeoutMillis = -1;
uint32_tchanges = mConfigurationChangesToRefresh;
if (changes) {
mConfigurationChangesToRefresh = 0;
timeoutMillis = 0;
refreshConfigurationLocked(changes);
} else if (mNextTimeout != LLONG_MAX) {
nsecs_tnow = systemTime(SYSTEM_TIME_MONOTONIC);
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
}
} // release lock
size_tcount = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
{ // acquire lock
AutoMutex_l(mLock);
mReaderIsAliveCondition.broadcast();
if (count) {
processEventsLocked(mEventBuffer, count);
}
if (mNextTimeout != LLONG_MAX) {
nsecs_tnow = systemTime(SYSTEM_TIME_MONOTONIC);
if (now >= mNextTimeout) {
#if DEBUG_RAW_EVENTS
ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
#endif
mNextTimeout = LLONG_MAX;
timeoutExpiredLocked(now);
}
}
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);
}
// Flush queued events out to the listener.
// This must happen outside of the lock because the listener could potentially call
// back into the InputReader's methods, such as getScanCodeState, or become blocked
// on another thread similarly waiting to acquire the InputReader lock thereby
// resulting in a deadlock. This situation is actually quite plausible because the
// listener is actually the input dispatcher, which calls into the window manager,
// which occasionally calls into the input reader.
mQueuedListener->flush();
}
InputDeviceInfo:描述输入设备特征和特性的类
getEvents用来获取输入设备信息、输入事件;processEventsLocked用来处理获得的事件的;按照顺序,首先进入到getEvents
1.5.3.1-1 EventHub.cpp的getEvents函数
size_tEventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_tbufferSize) {
ALOG_ASSERT(bufferSize >= 1);
AutoMutex_l(mLock);
struct input_eventreadBuffer[bufferSize];
RawEvent* event = buffer;
size_tcapacity = bufferSize;
bool awoken = false;
for (;;) {
nsecs_tnow = systemTime(SYSTEM_TIME_MONOTONIC);
// Reopen input devices if needed.
if (mNeedToReopenDevices) {
mNeedToReopenDevices = false;
ALOGI("Reopening all input devices due to a configuration change.");
closeAllDevicesLocked();
mNeedToScanDevices = true;
break; // return to the caller before we actually rescan
}
// Report any devices that had last been added/removed.
while (mClosingDevices) {
Device* device = mClosingDevices;
ALOGV("Reporting device closed: id=%d, name=%s\n",
device->id, device->path.string());
mClosingDevices = device->next;
event->when = now;
event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id;
event->type = DEVICE_REMOVED;
event += 1;
deletedevice;
mNeedToSendFinishedDeviceScan = true;
if (--capacity == 0) {
break;
}
}
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
while (mOpeningDevices != NULL) {
Device* device = mOpeningDevices;
ALOGV("Reporting device opened: id=%d, name=%s\n",
device->id, device->path.string());
mOpeningDevices = device->next;
event->when = now;
event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
event->type = DEVICE_ADDED;
event += 1;
mNeedToSendFinishedDeviceScan = true;
if (--capacity == 0) {
break;
}
}
if (mNeedToSendFinishedDeviceScan) {
mNeedToSendFinishedDeviceScan = false;
event->when = now;
event->type = FINISHED_DEVICE_SCAN;
event += 1;
if (--capacity == 0) {
break;
}
}
// Grab the next input event.
bool deviceChanged = false;
while (mPendingEventIndex < mPendingEventCount) {
const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {
if (eventItem.events & EPOLLIN) {
mPendingINotify = true;
} else {
ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
}
continue;
}
if (eventItem.data.u32 == EPOLL_ID_WAKE) {
if (eventItem.events & EPOLLIN) {
ALOGV("awoken after wake()");
awoken = true;
char buffer[16];
ssize_tnRead;
do {
nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
} while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
} else {
ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
eventItem.events);
}
continue;
}
ssize_tdeviceIndex = mDevices.indexOfKey(eventItem.data.u32);
if (deviceIndex < 0) {
ALOGW("Received unexpected epoll event 0x%08x for unknown device id %d.",
eventItem.events, eventItem.data.u32);
continue;
}
Device* device = mDevices.valueAt(deviceIndex);
if (eventItem.events & EPOLLIN) {
int32_treadSize = read(device->fd, readBuffer,
sizeof(struct input_event) * capacity);
if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
// Device was removed before INotify noticed.
ALOGW("could not get event, removed? (fd: %d size: %" PRId32
" bufferSize: %zu capacity: %zu errno: %d)\n",
device->fd, readSize, bufferSize, capacity, errno);
deviceChanged = true;
closeDeviceLocked(device);
} else if (readSize < 0) {
if (errno != EAGAIN && errno != EINTR) {
ALOGW("could not get event (errno=%d)", errno);
}
} else if ((readSize % sizeof(struct input_event)) != 0) {
ALOGE("could not get event (wrong size: %d)", readSize);
} else {
int32_tdeviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
size_tcount = size_t(readSize) / sizeof(struct input_event);
for (size_t i = 0; i < count; i++) {
struct input_event& iev = readBuffer[i];
ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d",
device->path.string(),
(int) iev.time.tv_sec, (int) iev.time.tv_usec,
iev.type, iev.code, iev.value);
// Some input devices may have a better concept of the time
// when an input event was actually generated than the kernel
// which simply timestamps all events on entry to evdev.
// This is a custom Android extension of the input protocol
// mainly intended for use with uinput based device drivers.
if (iev.type == EV_MSC) {
if (iev.code == MSC_ANDROID_TIME_SEC) {
device->timestampOverrideSec = iev.value;
continue;
} else if (iev.code == MSC_ANDROID_TIME_USEC) {
device->timestampOverrideUsec = iev.value;
continue;
}
}
if (device->timestampOverrideSec || device->timestampOverrideUsec) {
iev.time.tv_sec = device->timestampOverrideSec;
iev.time.tv_usec = device->timestampOverrideUsec;
if (iev.type == EV_SYN && iev.code == SYN_REPORT) {
device->timestampOverrideSec = 0;
device->timestampOverrideUsec = 0;
}
ALOGV("applied override time %d.%06d",
int(iev.time.tv_sec), int(iev.time.tv_usec));
}
#ifdef HAVE_POSIX_CLOCKS
// Use the time specified in the event instead of the current time
// so that downstream code can get more accurate estimates of
// event dispatch latency from the time the event is enqueued onto
// the evdev client buffer.
//
// The event's timestamp fortuitously uses the same monotonic clock
// time base as the rest of Android. The kernel event device driver
// (drivers/input/evdev.c) obtains timestamps using ktime_get_ts().
// The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere
// calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a
// system call that also queries ktime_get_ts().
event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL
+ nsecs_t(iev.time.tv_usec) * 1000LL;
ALOGV("event time %" PRId64 ", now %" PRId64, event->when, now);
// Bug 7291243: Add a guard in case the kernel generates timestamps
// that appear to be far into the future because they were generated
// using the wrong clock source.
//
// This can happen because when the input device is initially opened
// it has a default clock source of CLOCK_REALTIME. Any input events
// enqueued right after the device is opened will have timestamps
// generated using CLOCK_REALTIME. We later set the clock source
// to CLOCK_MONOTONIC but it is already too late.
//
// Invalid input event timestamps can result in ANRs, crashes and
// and other issues that are hard to track down. We must not let them
// propagate through the system.
//
// Log a warning so that we notice the problem and recover gracefully.
if (event->when >= now + 10 * 1000000000LL) {
// Double-check. Time may have moved on.
nsecs_ttime = systemTime(SYSTEM_TIME_MONOTONIC);
if (event->when > time) {
ALOGW("An input event from %s has a timestamp that appears to "
"have been generated using the wrong clock source "
"(expected CLOCK_MONOTONIC): "
"event time %" PRId64 ", current time %" PRId64
", call time %" PRId64 ". "
"Using current time instead.",
device->path.string(), event->when, time, now);
event->when = time;
} else {
ALOGV("Event time is ok but failed the fast path and required "
"an extra call to systemTime: "
"event time %" PRId64 ", current time %" PRId64
", call time %" PRId64 ".",
event->when, time, now);
}
}
#else
event->when = now;
#endif
event->deviceId = deviceId;
event->type = iev.type;
event->code = iev.code;
event->value = iev.value;
event += 1;
capacity -= 1;
}
if (capacity == 0) {
// The result buffer is full. Reset the pending event index
// so we will try to read the device again on the next iteration.
mPendingEventIndex -= 1;
break;
}
}
} else if (eventItem.events & EPOLLHUP) {
ALOGI("Removing device %s due to epoll hang-up event.",
device->identifier.name.string());
deviceChanged = true;
closeDeviceLocked(device);
} else {
ALOGW("Received unexpected epoll event 0x%08x for device %s.",
eventItem.events, device->identifier.name.string());
}
}
// readNotify() will modify the list of devices so this must be done after
// processing all other events to ensure that we read all remaining events
// before closing the devices.
if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
mPendingINotify = false;
readNotifyLocked();
deviceChanged = true;
}
// Report added or removed devices immediately.
if (deviceChanged) {
continue;
}
// Return now if we have collected any events or if we were explicitly awoken.
if (event != buffer || awoken) {
break;
}
// Poll for events. Mind the wake lock dance!
// We hold a wake lock at all times except during epoll_wait(). This works due to some
// subtle choreography. When a device driver has pending (unread) events, it acquires
// a kernel wake lock. However, once the last pending event has been read, the device
// driver will release the kernel wake lock. To prevent the system from going to sleep
// when this happens, the EventHub holds onto its own user wake lock while the client
// is processing events. Thus the system can only sleep if there are no events
// pending or currently being processed.
//
// The timeout is advisory only. If the device is asleep, it will not wake just to
// service the timeout.
mPendingEventIndex = 0;
mLock.unlock(); // release lock before poll, must be before release_wake_lock
release_wake_lock(WAKE_LOCK_ID);
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
mLock.lock(); // reacquire lock after poll, must be after acquire_wake_lock
if (pollResult == 0) {
// Timed out.
mPendingEventCount = 0;
break;
}
if (pollResult < 0) {
// An error occurred.
mPendingEventCount = 0;
// Sleep after errors to avoid locking up the system.
// Hopefully the error is transient.
if (errno != EINTR) {
ALOGW("poll failed (errno=%d)\n", errno);
usleep(100000);
}
} else {
// Some events occurred.
mPendingEventCount = size_t(pollResult);
}
}
// All done, return the number of events we read.
return event - buffer;
}
第二个参数RawEvent* buffer:存储底层上报的输入事件,因为包含各种事件,没有过滤,称为原始事件
第三个参数bufferSize:已分配input_event的buffer大小,256字节,input_event是所获取事件的结构体:
time:事件的执行时间
type:事件类型,EV_KEY(按键),EV_ABS(绝对坐标,如触摸屏),EV_REL(相对坐标,轨迹,如鼠标),除此之外,还有EV_LED(LED),EV_FF(力反馈)等
code:如果type是按键,code表示原始按键码值;如果是EV_REL,code表示轨迹类型,包括REL_X(X轴方向),REL_Y(Y轴方向);如果是EV_ABS,code表示坐标类型,ABS_X(X轴坐标),ABS_Y(Y轴坐标)
value:事件值,如果type是按键,按下时value为1,松开时为0;如果是EV_REL,value表示x,y方向上的偏移量;如果是EV_ABS,value表示x,y轴坐标值
mNeedToReopenDevices,mClosingDevices一开始初始化为false,跳过:
if (mNeedToReopenDevices) {
与
while (mClosingDevices) {
mNeedToScanDevices初始化为true,执行到:
mNeedToScanDevices初始化为true,scanDevicesLocked的调用过程:scanDevicesLocked() —-> scanDirLocked(DEVICE_PATH) —-> openDeviceLocked(devname)
DEVICE_PATH被赋值为/dev/input,这是输入设备节点目录,该目录下包含所有输入设备节点,在命令行下进入该目录看看,一般包含:
event0一般代表按键或键盘,可用命令cat /proc/bus/input/device查看所有输入设备详细信息
1.5.3.2 EventHub.cpp的openDeviceLocked函数
int fd = open(devicePath, O_RDWR | O_CLOEXEC);
devicePath为传过来的设备节点路径,比如按键为:/dev/input/event0
open以读写方式打开该设备文件,返回设备文件描述符fd
然后做了一系列动作:获取设备名称、检查是否已经包含在容器mExcludedDevices中、获取该设备的驱动版本、获取设备身份信息保存到inputid结构体中并赋给InputDeviceIdentifier对象中的变量、获取设备的物理地址和设备唯一id号,设置文件描述符fd为非阻塞模式O_NONBLOCK属性等。
从底层获取了该设备的所有信息后,创建临时Device对象代表该设备,比如,代表按键设备节点对象
检查该设备是按键或键盘,就把设备对象的classes属性标为INPUT_DEVICE_CLASS_KEYBOARD,表示当前发生输入事件的设备是按键或键盘;
在这段代码之后,系统做了很多事情,比如,继续判断设备是不是轨迹球或鼠标、多点触摸屏、单点触摸屏、操作杆、开关、振动器、虚拟按键;如果是键盘、操纵杆等,就配置其布局。这一过程的代码与本文关联性不大,没有贴上,如果研究鼠标、触摸屏等,可参考这段代码。
把设备文件描述符注册到epoll对象中,如果linux内核版本号大于或等于3.5,就设置监控事件为EPOLLIN,表示可读事件,并把设备id保存到data结构体中。
addDeviceLocked(device);
最后调用addDeviceLocked(device)把设备id、设备对象作为一对映射保存到mDevices容器中,并设device->next = mOpeningDevices,mOpeningDevices初始化为0,表示下一个设备还没有,同时设mOpeningDevices为当前设备对象。
openDeviceLocked函数执行完毕返回到getEvents的scanDevicesLocked()函数后继续执行
1.5.3.1-2 EventHub.cpp的getEvents函数
mOpeningDevices不为空,进入到while循环:
while (mOpeningDevices != NULL) {
该while循环把所有设备Device的内容保存到RawEvent结构体中,把event->type设为DEVICE_ADDED,意味着事件被成功添加到RawEvent,再把mNeedToSendFinishedDeviceScan设为true,意味着结束设备扫描。继续执行到:
while (mPendingEventIndex < mPendingEventCount) {
mPendingEventCount和mPendingEventIndex都初始化为0,while循环不成立,跳过该while循环
mPendingINotify初始化为false,跳过:
if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
deviceChanged也为false,跳过:
if (deviceChanged) {
执行到:
if (event != buffer || awoken) {
break;
}
不相等,break跳出for循环执行到最后event-buffer得到输入设备的个数,返回到loopOnce中执行processEventsLocked —> addDeviceLocked把设备添加到KeyedVector中,同时设置一些变量,比如把keyboardSource为AINPUT_SOURCE_KEYBOARD,表示按键
因为InputReaderThread是循环执行的,进程又一次进入到getEvents函数中,开始执行for循环,这时候设备都已经取出来了就不再执行scanDevicesLocked()扫描获取设备
mPendingEventCount还是为0,所以:
while (mPendingEventIndex < mPendingEventCount) {
仍然不成立,进程执行到:
一开始,队列等待中的事件都为0,mPendingEventIndex设为0;调用epoll_wait机制监控epoll对象mEpollFd的兴趣列表中的事件,InputRead线程处于等待状态,等待被唤醒,什么时候被唤醒
在1.1.3节,提到Q1和Q2两个问题,Q1在此处可以解答。
要想回答Q2,就必须要清楚epoll兴趣列表中注册了哪些文件描述符,在1.1.3节可知,注册了mINotifyFd, mWakeReadPipeFd和mWakeWritePipeFd;由1.5.3.2可知,注册了设备文件描述符fd。要使epoll_wair能继续执行,管道肯定要被写入数据才可以,因为管道有数据了,表明发生了I/O事件,epoll机制才会触发,也就意味着InputRead线程被唤醒。搜索一下代码,发现EventHub.cpp的wake()方法中有管道写入端描述符mWakeWritePipeFd,这是一个线索:
1.5.3.3 EventHub.cpp的wake()
void EventHub::wake() {
ALOGV("wake() called");
ssize_tnWrite;
do {
nWrite = write(mWakeWritePipeFd, "W", 1);
} while (nWrite == -1 && errno == EINTR);
if (nWrite != 1 && errno != EAGAIN) {
ALOGW("Could not write wake signal, errno=%d", errno);
}
}
在wake方法中,管道写入端mWakeWritePipeFd写入了一个字符w,管道中有数据了,epoll机制监控到管道有I/O事件发生,于是epoll_wait执行,返回发生I/O事件的个数,并且把发生的事件放在epoll_event结构数组mPendingEventItems中,这时调用者线程InputReaderThread被唤醒,这就回答了1.1.3节Q2问题,但是新的问题又来了
Q7:wake方法是什么被调用的?被谁调用的?
先继续epoll_wait方法后面的代码分析
1.5.3.1-3 EventHub.cpp的getEvents函数
epoll_wait返回发生事件的个数赋给pollResult,pollResult肯定大于0,执行:
mPendingEventCount = size_t(pollResult);
pollResult赋给了mPendingEventCount,表明有事件待处理
再次执行getEvents中的for循环
mOpeningDevices在1.5.3.1-1节中已经被赋为device->next即0,所以while循环:
while (mClosingDevices) {
不成立,跳过。
mPendingEventCount大于0了,mPendingEventIndex < mPendingEventCount成立,进入到下面while循环:
while (mPendingEventIndex < mPendingEventCount) {
根据发生事件的类型,判断:
if (eventItem.data.u32 == EPOLL_ID_WAKE) {
成立,进程执行到:
从管道中读取数据,该数据就是一个字符w,执行完后continue跳出循环,又执行到epoll_wait,InputReader又被阻塞等待;然后1.5.3.3节的wake()函数又调用write写入字符w触发了epoll_wait,唤醒了InputReader线程,然后又开始执行for循环……
Q8 这样循环下去的意义何在?
循环的意义:确保InputReader线程始终处于活动状态,能及时获取输入事件。因为按键、触摸等输入事件还没有发生时,InputReader线程处于阻塞状态,当一定的时间过去后,仍然没有输入事件,epoll_wait可能超时返回0,因此,下段语句:
成立,跳出for循环返回了,如果此时有事件发生的话,因为epoll_wait还没有执行到,可能会漏掉事件处理。为了保障InputReader线程能够及时获取事件,系统创建了看门狗线程,每隔一段时间向管道中写入数据唤醒InputReader线程去读取事件,这样,Q5就有了答案,看门狗WatchDog实际上也是一个线程,只不过会定时发送消息给InputReader线程读取输入事件。
1.5.3.1-4 EventHub.cpp的getEvents函数
在1.5.3.2节openDeviceLocked函数中,把输入设备文件描述符注册到mEpollFd中,因此,当有输入事件发生时,epoll_wait方法开始执行返回发生的个数赋给pollResult变量,此时pollResult肯定大于0,mPendingEventCount也大于0,继续for循环,进入到:
while (mPendingEventIndex < mPendingEventCount) {
此时eventItem.data.u32的值是设备id,if语句:
if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {
与
if (eventItem.data.u32 == EPOLL_ID_WAKE) {
都不成立,进程执行到:
根据设备id从容器中取出设备对象,如果发生了可读事件,调用read从该设备文件中读取事件保存到readBuffer中,如果没出错的话,执行:
计算出有多少个事件并取出来保存到input_event结构体变量iev中,然后计算相应的事件执行时间,再把input_event结构体变量中的内容保存到原始事件RawEvent中,然后循环直到所有事件都被获取到后,进程执行:
event指向的内存现在已经保存了事件,而buffer还是指向首地址,不相等,if成立,执行break跳出for(::)无限循环,执行到getEvents最后一句:
return event - buffer;
返回读取事件的个数,并返回到InputReader的loopOnce中这一句:
count大于0,执行:
if (count) {
processEventsLocked(mEventBuffer, count);
}
调用processEventsLocked对事件进行处理,此处属于事件处理阶段,在第2节分析。
第1节做了3件事:a. 创建InputDispatchThread, InputReaderThread,InputManager等主要输入系统核心组件
b. 扫描所有输入设备,获取输入设备各种信息
c. 获取输入设备事件
第1节Framework层如何获取事件的过程分析完毕,还留下Q6问题
通过getEvents获得事件后,执行过程:processEventsLocked —-> processEventsForDeviceLocked
在processEventsLocked中,rawEvent->type表示事件的类型,有如下几种:
EV_KEY,EV_ABS,EV_REL,EV_SW,EV_LED,EV_FF,...
本文针对按键事件,因此type是EV_KEY,小于FIRST_SYNTHETIC_EVENT,
2.1 InputReader.cpp的processEventsForDeviceLocked函数
从KeyedVector容器中根据设备id号取出设备对象赋给device,参数count为多少?正常情况下按键按下一次,产生一个按下事件,类型为EV_KEY,还有一个同步事件EV_SYNC,因此count为2
然后调用过程为:
device->process(rawEvents, count) —-> mapper->process(rawEvent) —-> KeyboardInputMapper::processKey
不同的输入时间对应不同的InputMapper对象,按键事件对应的是KeyboardInputMapper对象,就执行到了KeyboardInputMapper的process方法,该方法中,会把获得原始按键值转化成framework层按键值(暂时这么称呼)。
再根据按键类型,执行到KeyboardInputMapper::processKey函数,在processKey中,主要代码在最后部分:
NotifyKeyArgs类用来描述一个按键事件,包括特性属性和notify函数;倒数第二句调用构造函数初始化特征属性,NotifyKeyArgs特征属性包括:
nsecs_t eventTime:按键事件的发生时间
int32_t deviceId:按键的设备id
uint32_t source:输入源类型,此处由1.5.3.1-2节设置为AINPUT_SOURCE_KEYBOARD,表示按键或键盘
uint32_t policyFlags:按键功能标志位,比如,如果是外插键盘,应该具备唤醒设备的功能,就把policyFlags设为POLICY_FLAG_WAKE,如果是内置设备,就不设置该标志。
int32_t action:按键动作标志,是按下按键还是松开按键
int32_t flags:操作按键的主体是谁,如果是用户操作,就设置为AKEY_EVENT_FLAG_FROM_SYSTEM
int32_t keyCode:按键值,按键值在底层和上层值不一样,上层的值一般在KeyEvent.java中设置
int32_t scanCode:原始按键值,就是从底层获取到的按键值
int32_t metaState:功能按键状态,比如如果有ALT、SHIFT、CTRL按键按下,就会设置一个具体的状态,如果是普通按键,比如方向键,数字键等,此变量就设为0
nsecs_t downTime:按键按下的时间,与eventTime不一样,eventTime从底层传过来,downTime是在framework层计算的变量
getListener()->notifyKey(&args);
mQueuedListener是在1.1.6节创建InputReader时创建的,创建QueuedInputListener时期参数listener实际类型是InputDispatcher,而InputDispatcher实现了InputDispatcherInterface,InputDispatcherInterface又实现了InputListenerInterface,因此listener也是InputListenerInterface对象类型。而notifyKey在InputDispatcher中有实现,这样进程就执行到了InputDispatcher的notifyKey函数,如果在InputDispatcher中没有实现,就要查看InputDispatcherInterface, InputListenerInterface中是否有。
2.2 InputDispatcher.cpp的notifyKey函数
该函数做了很重要的任务:前半部分对传递过来的按键事件进行检查、验证,之后处理特殊按键,再把按键放到InboundQueue队列中,最后调用Looper对象的wake像管道中写入字符唤醒InputDispatcherThread线程
if (!validateKeyEvent(args->action)) {
return;
}
检查按键是否按下、松开
设置flags标致为AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY,虚拟硬件,比如手机上除了屏幕显示外的MENU、HOME、BACK键
设置metaState为meta键标志
policyFlags |= POLICY_FLAG_TRUSTED;
设置该标志表示按键事件是可信任的,比如,一个直接与Android机器相连接的按键设备
把参数传递给KeyEvent对象event
mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
拦截按键进行特殊处理,比如HOME、音量键、拨号键,电源等,这一步将在第7节分析
needWake = enqueueInboundEventLocked(newEntry);
创建KeyEntry对象并把按键事件作为该对象的内容,enqueueInboundEventLocked函数根据输入事件的类型作进一步处理:
bool needWake = mInboundQueue.isEmpty();
mInboundQueue.enqueueAtTail(entry);
traceInboundQueueLengthLocked();
switch (entry->type) {
case EventEntry::TYPE_KEY: {
// Optimize app switch latency.
// If the application takes too long to catch up then we drop all events preceding
// the app switch key.
KeyEntry* keyEntry = static_cast(entry);
if (isAppSwitchKeyEventLocked(keyEntry)) {
if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
mAppSwitchSawKeyDown = true;
} else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
if (mAppSwitchSawKeyDown) {
#if DEBUG_APP_SWITCH
ALOGD("App switch is pending!");
#endif
mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
mAppSwitchSawKeyDown = false;
needWake = true;
}
}
}
break;
}
mInboundQueue一开始没有数据为空,enqueueAtTail函数把按键事件对象KeyEntry插入到mInboundQueue队尾。
case语句用来切换app,如果是HOME、挂号按键,就把needWake设为true,表示可以唤醒InputDispatcherThread来分发按键事件,否则,不唤醒InputDispatcherThread,那么按键就不会处理
if (needWake) {
mLooper->wake();
}
wake函数会调用write系统调用向管道中写入数据唤醒读端线程,读端线程是谁?在1.5.2.2节,InputDispatcherThread线程执行到了Looper对象的pollOnce函数时处于阻塞状态,此时epoll机制检测到管道中有数据写入事件发生,由这篇文章:Handler机制可知,读端线程InputDispatcherThread被唤醒,InputDispatcherThread线程从dispatchOnce返回到threadLoop,threadLoop的返回值为true,表明是线程是循环执行的,又一次调用dispatchOnce继续执行。唤醒了InputDispatcherThread,Q6也就有了答案。
小结:
把获取的原始按键码值转化成应用层按键值,对按键事件进行检查,处理特殊按键,最后把按键事件封装成EventEntry后临时存储到InboundQueue队列中等待处理。
现在,按键事件读取后的处理已经完成,cpu就把控制权从InputReaderThread交给了InputDispatcherThread线程,开始分发事件。
由2.2节可知,InputDispatcherThread线程被唤醒后,又一次地执行dispatchOnce函数,在dispatchOnce中调用了dispatchOnceInnerLocked函数
3.1 InputDispatcher.cpp的dispatchOnceInnerLocked
mInboundQueue中已经有了按键事件,不为空,执行到这句:
dequeueAtHead函数从队列mInboundQueue头部取出一个元素EventEntry对象交给mPendingEvent,EventEntry代表按键事件
在switch语句中,根据类型来选择执行语句,按键为TYPE_KEY
case EventEntry::TYPE_KEY: {
KeyEntry* typedEntry = static_cast(mPendingEvent);
......
done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
break;
}
忽略不重要的代码,下一步调用dispatchKeyLocked函数:
先判断是否包含重复按键事件,如果是,暂时先清除掉,然后计算repeatCount值;如果没有重复按键事件,就保存按键状态、执行时间;如果计算后的repeatCount为1,设flags为AKEY_EVENT_FLAG_LONG_PRESS,表示长按事件。
entry->dispatchInProgress = true;
把dispatchInProgress设为true,意味着即将进行分发事件了
if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
if (mFocusedWindowHandle != NULL) {
commandEntry->inputWindowHandle = mFocusedWindowHandle;
}
commandEntry->keyEntry = entry;
entry->refCount += 1;
return false; // wait for the command to run
} else {
entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
}
}
entry->interceptKeyResult一开始默认为INTERCEPT_KEY_RESULT_UNKNOWN,if语句成立
如果entry->policyFlags为POLICY_FLAG_PASS_TO_USER,表示按键需要传递给应用程序;doInterceptKeyBeforeDispatchingLockedInterruptible函数也是拦截按键处理的方法,在7.2节详解
InputTarget结构体:指定一个输入事件如何被分发到一个特定窗口。该结构体包含了很多特征变量:x,y坐标,输入事件通道InputChannel等;findFocusedWindowTargetsLocked找到获得焦点的窗口,如果成功,返回值设为INPUT_EVENT_INJECTION_SUCCEEDED,并把获得焦点的窗口的输入通道保存到InputTarget对象中,再把InputTarget对象放到inputTargets堆栈顶,setInjectionResultLocked函数把该获得焦点的标志INPUT_EVENT_INJECTION_SUCCEEDED保存到InjectionState结构体元素injectionResult中
dispatchEventLocked函数从inputTarget容器中取出获得焦点的输入通道管理对象Connection,Connection用来管理单个输入通道InputChannel的分发状态。dispatchEventLocked函数的调用过程:
dispatchEventLocked —-> prepareDispatchCycleLocked —-> enqueueDispatchEntriesLocked
outboundQueue一开始为空,当执行完enqueueDispatchEntryLocked后,outboundQueue就有了按键事件DispatchEntry,不为空,所以if语句:
if (wasEmpty && !connection->outboundQueue.isEmpty()) {
成立,startDispatchCycleLocked开始发送事件,先看enqueueDispatchEntryLocked:
enqueueDispatchEntryLocked函数相关代码:
void InputDispatcher::enqueueDispatchEntryLocked(
const sp& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
int32_tdispatchMode) {
int32_tinputTargetFlags = inputTarget->flags;
if (!(inputTargetFlags & dispatchMode)) {
return;
}
inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;
// This is a new event.
// Enqueue a new dispatch entry onto the outbound queue for this connection.
DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, // increments ref
inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
inputTarget->scaleFactor);
// Apply target flags and update the connection's input state.
switch (eventEntry->type) {
case EventEntry::TYPE_KEY: {
KeyEntry* keyEntry = static_cast(eventEntry);
dispatchEntry->resolvedAction = keyEntry->action;
dispatchEntry->resolvedFlags = keyEntry->flags;
if (!connection->inputState.trackKey(keyEntry,
dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
connection->getInputChannelName());
#endif
deletedispatchEntry;
return; // skip the inconsistent event
}
break;
}
......
}
// Remember that we are waiting for this dispatch to complete.
if (dispatchEntry->hasForegroundTarget()) {
incrementPendingForegroundDispatchesLocked(eventEntry);
}
// Enqueue the dispatch entry.
connection->outboundQueue.enqueueAtTail(dispatchEntry);
}
最后一个参数dispatchMode针对触摸的,与触摸相关的代码暂时去掉
DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, // increments ref
inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
inputTarget->scaleFactor);
创建DispatchEntry按键事件对象,把该对象作为一个发送数据包加入到outboundQueue队列中
trackKey函数把按键分为按下和松开两类事件处理,如果是按下就就先保存到KeyMemento结构体中,如果是在松开状态下,该事件是回退事件,即不在应用程序中处理的事件,就从回退事件集合中删除该按键
再把新构建的按键事件数据包添加到outboundQueue队列尾部即栈顶。
startDispatchCycleLocked函数:
while (connection->status == Connection::STATUS_NORMAL
&& !connection->outboundQueue.isEmpty()) {
DispatchEntry* dispatchEntry = connection->outboundQueue.head;
dispatchEntry->deliveryTime = currentTime;
// Publish the event.
status_tstatus;
EventEntry* eventEntry = dispatchEntry->eventEntry;
switch (eventEntry->type) {
case EventEntry::TYPE_KEY: {
KeyEntry* keyEntry = static_cast(eventEntry);
// Publish the key event.
status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
keyEntry->deviceId, keyEntry->source,
dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
keyEntry->keyCode, keyEntry->scanCode,
keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
keyEntry->eventTime);
break;
}
}
当outboundQueue非空并且其他条件都具备时(比如,inputchannel已经注册,通信没有错误等),取出outboundQueue队列头元素,赋给dispatchEntry,再取出事件对象KeyEntry,根据事件类型确定case语句分支,如果是按键事件,就调用connection的InputPublisher的publishKeyEvent函数发送到inputchannel中,如果publishKeyEvent返回0,表示按键事件发送成功
3.2 InputTransport.cpp的publishKeyEvent
创建Message消息,把传递过来的事件信息保存到Message对象中,调用服务端管道InputChannel的sendMessage方法发送消息,sendMessage源码:
status_tInputChannel::sendMessage(const InputMessage* msg) {
size_tmsgLength = msg->size();
ssize_tnWrite;
do {
nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
} while (nWrite == -1 && errno == EINTR);
if (nWrite < 0) {
int error = errno;
#if DEBUG_CHANNEL_MESSAGES
ALOGD("channel '%s' ~ error sending message of type %d, errno=%d", mName.string(),
msg->header.type, error);
#endif
if (error == EAGAIN || error == EWOULDBLOCK) {
return WOULD_BLOCK;
}
if (error == EPIPE || error == ENOTCONN || error == ECONNREFUSED || error == ECONNRESET) {
return DEAD_OBJECT;
}
return -error;
}
if (size_t(nWrite) != msgLength) {
#if DEBUG_CHANNEL_MESSAGES
ALOGD("channel '%s' ~ error sending message type %d, send was incomplete",
mName.string(), msg->header.type);
#endif
return DEAD_OBJECT;
}
#if DEBUG_CHANNEL_MESSAGES
ALOGD("channel '%s' ~ sent message of type %d", mName.string(), msg->header.type);
#endif
return OK;
}
send系统调用相当于write调用,区别在于:send专用于socket文件描述符;send多了一个flags参数表示I/O事件的行为。send向服务端fd发送消息msg,如果成功,返回成功发送消息的大小;返回-1表示发送了错误
在3.4.1.1节通过socketpair创建一对socket对象,一个用作服务端,一个用作客户端,并且是已经连接好的插口,无需再通过bind、connect去连接,两端都可以发送、接收,全双工通信。在按键事件中,用socket[0]作为服务端,通过send向服务端发送数据,recv从客户端socket[1]接收数据。
在3.5.3.1节,socket客户端文件描述符被注册到了主线程epoll兴趣列表中,如果socket服务端没有往inputchannel中写入数据,客户端暂时处于阻塞状态,一旦服务端有了数据后,客户端也被唤醒了,客户端唤醒后的处理过程在第5节分析。
Q9 在publishKeyEvent中调用这句时:
return mChannel->sendMessage(&msg)
mChannel是服务端管道对象,该对象如何创建、注册的?socket文件描述符mFd什么时候创建;既然有服务端,肯定有客户端来接收数据,那么应用程序客户端socket如何创建?应用程序客户端InputChannel对象又是什么时候创建、注册的?
关于此问题将在第4节分析
涉及到应用程序相关的,就从ViewRootImpl来分析
4.1 ViewRootImpl.java的setView方法
当窗口没有按键事件传输通道时,创建一个InputChannel对象,相当于一个管道,其他进程通过该管道像窗口发送按键事件消息。
mWindowSession在ViewRootImpl构造器中创建:
mWindowSession = WindowManagerGlobal.getWindowSession();
4.2 WindowManagerGlobal.java的getWindowSession方法
public static IWindowSessiongetWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManagerimm = InputMethodManager.getInstance();
IWindowManagerwindowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
getWindowManagerService方法源码:
public static IWindowManagergetWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
try {
sWindowManagerService = getWindowManagerService();
ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale());
} catch (RemoteException e) {
Log.e(TAG, "Failed to get WindowManagerService, cannot set animator scale", e);
}
}
return sWindowManagerService;
}
}
getWindowSession方法做了很多工作,单例类InputMethodManager通过getInstance返回其唯一的对象,InputMethodManager表示输入法框架系统级API,用以输入法和应用程序之间的交互。
getService(“window”)获得一个WindowManagerService服务的Binder对象,实际是BinderProxy对象,asInterface接口根据BinderProxy对象生成Proxy客户端代理对象,返回到getWindowSession中赋给IWindowManager接口类型windowManager变量,然后利用该代理对象来访问WindowManagerService服务端中的方法openSession,其参数涵义:
第一个参数是一个IWindowSessionCallback.Stub对象
第二个参数imm.getClient(),返回IInputMethodClient.Stub对象
第三个参数imm.getInputContext(),返回ControlledInputConnectionWrapper对象
在openSession方法中,创建了Session对象,并把这三个参数及WindowManagerService对象传递到Session中
得到Session对象后,返回到4.1节ViewRootImpl构造器中赋给IWindowSession接口类型mWindowSession,再调用Session对象的addToDisplay
4.3 Session.java的addToDisplay方法
public int addToDisplay(IWindowwindow, int seq, WindowManager.LayoutParamsattrs,
int viewVisibility, int displayId, RectoutContentInsets, RectoutStableInsets,InputChanneloutInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outInputChannel);
}
mWindow:W对象,W实现了IWindow.Stub抽象类,继承了Binder类
mSeq:一个整型,每个按键事件的序号
mWindowAttributes:初始化语句有:
创建了一个布局参数对象
mAppVisible:确定mView即DecorView的可见性,默认为true,可见
mDisplay.getDisplayId():获得应用程序逻辑显示区域id号
mAttachInfo.mContentInsets:
final RectmContentInsets = new Rect();
一个矩形对象,作为窗口可见的矩形修饰
mAttachInfo.mStableInsets:
final RectmStableInsets = new Rect();
也是一个矩形对象,作为窗口固定的修饰
mInputChannel:待注册的管道对象
mService是WindowManagerService对象,在addToDisplay中调用WindowManagerService的addWindow方法
4.4 WindowManagerService.java的addWindow方法
与inputChannel相关部分:
if (outInputChannel != null && (attrs.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
String name = win.makeInputChannelName();
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
win.setInputChannel(inputChannels[0]);
inputChannels[1].transferTo(outInputChannel);
mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
}
4.4.1 InputChannel的openInputChannelPair
openInputChannelPair的实现在本地方法android_view_InputChannel_nativeOpenInputChannelPair中:
static jobjectArrayandroid_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
jclassclazz, jstringnameObj) {
const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
String8name(nameChars);
env->ReleaseStringUTFChars(nameObj, nameChars);
sp serverChannel;
sp clientChannel;
status_tresult = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
if (result) {
String8message;
message.appendFormat("Could not open input channel pair. status=%d", result);
jniThrowRuntimeException(env, message.string());
return NULL;
}
jobjectArraychannelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
if (env->ExceptionCheck()) {
return NULL;
}
jobjectserverChannelObj = android_view_InputChannel_createInputChannel(env,
new NativeInputChannel(serverChannel));
if (env->ExceptionCheck()) {
return NULL;
}
jobjectclientChannelObj = android_view_InputChannel_createInputChannel(env,
new NativeInputChannel(clientChannel));
if (env->ExceptionCheck()) {
return NULL;
}
env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
return channelPair;
}
声明服务端管道serverChannel、客户端管道clientChannel,作为openInputChannelPair的参数
4.4.1.1 InputTransprot.cpp的openInputChannelPair
status_tInputChannel::openInputChannelPair(const String8& name,
sp& outServerChannel, sp& outClientChannel) {
int sockets[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
status_tresult = -errno;
ALOGE("channel '%s' ~ Could not create socket pair. errno=%d",
name.string(), errno);
outServerChannel.clear();
outClientChannel.clear();
return result;
}
int bufferSize = SOCKET_BUFFER_SIZE;
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
String8serverChannelName = name;
serverChannelName.append(" (server)");
outServerChannel = new InputChannel(serverChannelName, sockets[0]);
String8clientChannelName = name;
clientChannelName.append(" (client)");
outClientChannel = new InputChannel(clientChannelName, sockets[1]);
return OK;
}
int sockets[2];
socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)
创建一个饱含2个元素的socket数组,socketpair创建一对socket对象,SOCK_SEQPACKET表示创建连续可靠的数据包连接,如果创建成功,返回0,如果返回-1,出错。
以这一对socket文件描述符、通道名称作为参数分别创建服务端管道对象outServerChannel和客户端管道对象clientChannelName
在InputChannel构造函数中,把管道名称传给mName,socket文件描述符保存到mFd中,设置mFd为非阻塞模式
4.4.1.2 android_view_InputChannel.cpp的android_view_InputChannel_nativeOpenInputChannelPair
创建包含2个元素的数组,数组对象类型是gInputChannelClassInfo.clazz即InputChannel类型,元素初始化为空
用已创建的服务端通道serverChannel和客户端通道clientChannel作为参数分别创建本地通道对象NativeInputChannel,android_view_InputChannel_createInputChannel:
创建Java层InputChannel类的对象保存到inputChannelObj中,如果成功,再调用android_view_InputChannel_setNativeInputChannel把刚才创建的NativeInputChannel对象指针赋给InputChannel对象中的变量Ptr,最后返回新创建的java层的InputChannel对象分别赋给serverChannelObj和clientChannelObj
用新创建的一对管道对象初始化数组channelPair,返回该数组赋给WindowManagerService中的inputChannels数组
小结:
makeInputChannelName创建了唯一的管道名称,openInputChannelPair以该名称作为参数创建一对管道InputChannel对象,存储在数组inputChannels中,inputChannels[0]作为服务端管道提供给inputdispatcher使用,用来发送按键事件;inputChannels[1]作为客户端管道提供给应用程序主线程使用,用来接收、消费按键事件。
4.4.2 InputManagerService的registerInputChannel
服务端管道inputChannels[0]创建好后,需要注册:
mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
参数win.mInputWindowHandle是一个输入事件窗口的句柄,有多个作用:InputDispatcher在发送按键事件前会通过该句柄检查是否有获得焦点的窗口;如果有焦点的窗口,可以通过该句柄获得应用程序的InputChannel
registerInputChannel —-> nativeRegisterInputChannel
4.4.2.1 com_android_server_input_InputManagerService.cpp的nativeRegisterInputChannel函数
static void nativeRegisterInputChannel(JNIEnv* env, jclassclazz,
jlongptr, jobjectinputChannelObj, jobjectinputWindowHandleObj, jbooleanmonitor) {
NativeInputManager* im = reinterpret_cast(ptr);
sp inputChannel = android_view_InputChannel_getInputChannel(env,
inputChannelObj);
if (inputChannel == NULL) {
throwInputChannelNotInitialized(env);
return;
}
sp inputWindowHandle =
android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);
status_tstatus = im->registerInputChannel(
env, inputChannel, inputWindowHandle, monitor);
if (status) {
String8message;
message.appendFormat("Failed to register input channel. status=%d", status);
jniThrowRuntimeException(env, message.string());
return;
}
if (! monitor) {
android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
handleInputChannelDisposed, im);
}
}
先获得本地InputChannel对象和InputWindowHandle对象,再调用NativeInputManager的registerInputChannel函数:
通过InputManager对象获得对应的InputDispatcher对象,进而调用其registerInputChannel函数注册
status_tInputDispatcher::registerInputChannel(const sp& inputChannel,
const sp& inputWindowHandle, bool monitor) {
#if DEBUG_REGISTRATION
ALOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(),
toString(monitor));
#endif
{ // acquire lock
AutoMutex_l(mLock);
if (getConnectionIndexLocked(inputChannel) >= 0) {
ALOGW("Attempted to register already registered input channel '%s'",
inputChannel->getName().string());
return BAD_VALUE;
}
sp connection = new Connection(inputChannel, inputWindowHandle, monitor);
int fd = inputChannel->getFd();
mConnectionsByFd.add(fd, connection);
if (monitor) {
mMonitoringChannels.push(inputChannel);
}
mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
} // release lock
// Wake the looper because some connections have changed.
mLooper->wake();
return OK;
}
检查inputChannel是否注册,首次执行时,没有注册
通过创建的管道inputChannel以及窗口句柄inputWindowHandle创建Connection对象,Connection用来管理inputChannel
getFd获得服务端管道inputChannel上的socket文件描述符,然后以键值对的方式把connect加入到KeyedVector中,这就完成了服务端通道的注册。
然后把socket服务端加入到epoll兴趣列表中,mLooper属于InputDispatcherThread,不属于主线程,注意与4.5.3.1节的区别,4.5.3.1节是注册到主线程对应的epoll对象中。ALOOPER_EVENT_INPUT表示可读事件,意思是在服务端socket中读取数据,这样设置的目的在于,当客户端socket接受到事件处理完后会向服务端发送一个反馈信号,服务端接收此信号后会回调handleReceiveCallback函数,该函数会再一次执行startDispatchCycleLocked函数从outboundQueue中取出按键事件进行发送,循环处理。
addFd函数的具体执行过程与4.5.4节几乎一样,不再分析。
4.4.3 InputChannel.java的transferTo
在4.4.2节已经注册了服务端管道inputChannels[0],那么客户端管道inputChannels[1]如何注册?
inputChannels[1].transferTo(outInputChannel);
public void transferTo(InputChanneloutParameter) {
if (outParameter == null) {
throw new IllegalArgumentException("outParameter must not be null");
}
nativeTransferTo(outParameter);
}
outInputChannel是InputChannel对象,inputChannels[1]也是刚刚创建的InputChannel对象,transferTo两头都是InputChannel对象,这又是怎么回事
transferTo的调用者是当前新创建客户端管道对象,参数outInputChannel是在ViewRootImpl中创建的,两者是不同的对象。
4.4.3.1 nativeTransferTo的本地实现
static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobjectobj,
jobjectotherObj) {
if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
jniThrowException(env, "java/lang/IllegalStateException",
"Other object already has a native input channel.");
return;
}
NativeInputChannel* nativeInputChannel =
android_view_InputChannel_getNativeInputChannel(env, obj);
android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);
android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
}
env:JNI接口对象
obj:本地方法所在对象的引用,就是inputChannels[1]客户端对象的引用
otherObj:在ViewRootImpl中创建的InputChannel对象
首先调用android_view_InputChannel_getNativeInputChannel函数
static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env,
jobjectinputChannelObj) {
jlonglongPtr = env->GetLongField(inputChannelObj, gInputChannelClassInfo.mPtr);
return reinterpret_cast(longPtr);
}
传过来的对象时otherObj,其变量mPtr在初始化时为空,没有额外赋值,所以longPtr为空,这句:
if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
不成立
又一次调用android_view_InputChannel_getNativeInputChannel函数,只不过第二个参数是obj即inputChannels[1],此时inputChannels[1]中的mPtr指针不为空,因此在3.4.1.2节中设置了mPtr为inputChannels[1]对应的NativeInputChannel地址,返回NativeInputChannel指针给nativeInputChannel变量,而后调用android_view_InputChannel_setNativeInputChannel函数把otherObj中的mPtr设为NativeInputChannel对象地址,这就表明ViewRootImpl中创建的InputChannel对象是客户端管道
读到此处可知,transferTo方法只是把ViewRootImpl中创建的InputChannel对象中的mPtr变量赋了值,该值就是客户端InputChannle对应的NativeInputChannel对象地址。
4.5 ViewRootImpl.java的setView方法
在4.4.2.1节,注册服务端管道InputChannel[0]时,创建了InputPublisher对象,用来发送消息到input channel,那么注册在客户端管道InputChannel[1]时,也会创建一个接收消息的对象,这就是InputConsumer,从管道中获取事件。客户端如何创建?还是在ViewRootImpl.java的setView方法中:
view是DecorView对象,DecorView实现了RootViewSurfaceTaker接口,因此,view也是RootViewSurfaceTaker接口类型,if语句成立,willYouTakeTheInputQueue实际返回一个NativeActivity对象,因为NativeActivity实现了InputQueue.Callback接口
InputQueue:应用程序通过InputQueue来获得输入设备事件
InputQueue.Callback:当InputQueue绑定、解除绑定与线程关系时的回调接口
创建了InputQueue对象,作为NativeActivity中回调方法onInputQueueCreated的参数;用InputChannel和Looper对象作为参数创建WindowInputEventReceiver对象并用mInputEventReceiver指向该对象。
InputQueue构造器中调用了本地方法nativeInit
第一个参数是InputQueue弱引用对象,第二个参数是主线程MessageQueue对象
4.5.1 android_view_InputQueue.cpp的nativeInit
static jlongnativeInit(JNIEnv* env, jobjectclazz, jobjectqueueWeak, jobjectjMsgQueue) {
sp messageQueue = android_os_MessageQueue_getMessageQueue(env, jMsgQueue);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
sp queue = InputQueue::createQueue(queueWeak, messageQueue->getLooper());
if (!queue.get()) {
jniThrowRuntimeException(env, "InputQueue failed to initialize");
return 0;
}
queue->incStrong(&gInputQueueClassInfo);
return reinterpret_cast(queue.get());
}
android_os_MessageQueue_getMessageQueue获得C++层NativeMessageQueue对象,也是MessageQueue对象,getLooper()获得Looper对象,queueWeak是java层传递过来的InputQueue弱引用对象,createQueue源码:
InputQueue* InputQueue::createQueue(jobjectinputQueueObj, const sp& looper) {
int pipeFds[2];
if (pipe(pipeFds)) {
ALOGW("Could not create native input dispatching pipe: %s", strerror(errno));
return NULL;
}
fcntl(pipeFds[0], F_SETFL, O_NONBLOCK);
fcntl(pipeFds[1], F_SETFL, O_NONBLOCK);
return new InputQueue(inputQueueObj, looper, pipeFds[0], pipeFds[1]);
}
创建了一个管道pipeFds,设置管道读、写端为非阻塞模式
创建了c++层InputQueue对象,赋给queue变量。InputQueue的构造函数:
分别把管道读端、写端文件描述符赋给mDispatchReadFd、mDispatchWriteFd;用JNI接口方法NewGlobalRef创建inputQueueObj本地全局引用赋给mInputQueueWeakGlobal
4.5.2 NativeActivity的onInputQueueCreated
public void onInputQueueCreated(InputQueuequeue) {
if (!mDestroyed) {
mCurInputQueue = queue;
onInputQueueCreatedNative(mNativeHandle, queue.getNativePtr());
}
}
把创建的InputQueue对象赋给mCurInputQueue,当创建好InputQueue后,既可回调onInputQueueCreated重写自己的代码,表明可以从InputQueue中获取事件
4.5.3 WindowInputEventReceiver的构造器
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());
public WindowInputEventReceiver(InputChannelinputChannel, Looperlooper) {
super(inputChannel, looper);
}
public InputEventReceiver(InputChannelinputChannel, Looperlooper) {
if (inputChannel == null) {
throw new IllegalArgumentException("inputChannel must not be null");
}
if (looper == null) {
throw new IllegalArgumentException("looper must not be null");
}
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference(this),
inputChannel, mMessageQueue);
mCloseGuard.open("dispose");
}
调用WindowInputEventReceiver的构造方法创建了一个WindowInputEventReceiver对象,其构造方法没有实现,直接调用父类InputEventReceiver的构造方法,第一个参数就是客户端管道对象,其内部mPtr变量指向了客户端NativeInputChannel对象地址。
第一个参数this,实际类型是WindowInputEventReceiver类型,这个参数很重要,在5.3节会根据该类型决定调用哪个onInputEvent方法。
static jlongnativeInit(JNIEnv* env, jclassclazz, jobjectreceiverWeak,
jobjectinputChannelObj, jobjectmessageQueueObj) {
sp inputChannel = android_view_InputChannel_getInputChannel(env,
inputChannelObj);
if (inputChannel == NULL) {
jniThrowRuntimeException(env, "InputChannel is not initialized.");
return 0;
}
sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
sp receiver = new NativeInputEventReceiver(env,
receiverWeak, inputChannel, messageQueue);
status_tstatus = receiver->initialize();
if (status) {
String8message;
message.appendFormat("Failed to initialize input event receiver. status=%d", status);
jniThrowRuntimeException(env, message.string());
return 0;
}
receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object
return reinterpret_cast(receiver.get());
}
用本地InputChannel, NativeMessageQueue及java层InputEventReceiver对象作为参数创建本地NativeInputEventReceiver对象并初始化,receiverWeak就是传递过来的WindowInputEventReceiver对象,赋给了mReceiverWeakGlobal变量。
创建好NativeInputEventReceiver对象后返回到java层InputEventReceiver中赋给mReceiverPtr变量,java层拥有这个变量就等于拥有了c++层NativeInputEventReceiver对象的指针
4.5.3.1 NativeInputEventReceiver的initialize函数
status_tNativeInputEventReceiver::initialize() {
setFdEvents(ALOOPER_EVENT_INPUT);
return OK;
}
InputConsumer获取客户端inputChannel管道上的socket文件描述符添加到epoll对象兴趣列表中,fd是socket的客户端。mMessageQueue是主线程中的消息队列,getLooper返回主线程Looper对象,因此,客户端socket文件描述符被注册到了主线程中epoll对象中。
4.5.4 Looper.cpp的addFd函数
int Looper::addFd(int fd, int ident, int events, const sp& callback, void* data) {
#if DEBUG_CALLBACKS
ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident,
events, callback.get(), data);
#endif
if (!callback.get()) {
if (! mAllowNonCallbacks) {
ALOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
return -1;
}
if (ident < 0) {
ALOGE("Invalid attempt to set NULL callback with ident < 0.");
return -1;
}
} else {
ident = POLL_CALLBACK;
}
int epollEvents = 0;
if (events & EVENT_INPUT) epollEvents |= EPOLLIN;
if (events & EVENT_OUTPUT) epollEvents |= EPOLLOUT;
{ // acquire lock
AutoMutex_l(mLock);
Requestrequest;
request.fd = fd;
request.ident = ident;
request.callback = callback;
request.data = data;
struct epoll_eventeventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = epollEvents;
eventItem.data.fd = fd;
ssize_trequestIndex = mRequests.indexOfKey(fd);
if (requestIndex < 0) {
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
if (epollResult < 0) {
ALOGE("Error adding epoll events for fd %d, errno=%d", fd, errno);
return -1;
}
mRequests.add(fd, request);
} else {
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
if (epollResult < 0) {
ALOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);
return -1;
}
mRequests.replaceValueAt(requestIndex, request);
}
} // release lock
return 1;
}
int epollEvents = 0;
if (events & EVENT_INPUT) epollEvents |= EPOLLIN;
addFd中第2个参数为0,第3个参数是ALOOPER_EVENT_INPUT,第4个参数this,是NativeInputEventReceiver对象,也是LooperCallback对象类型,第5个参数是NULL
第一个if语句不成立,把ident设置成POLL_CALLBACK,表示调用者有回调方法会被执行。
int epollEvents = 0;
if (events & EVENT_INPUT) epollEvents |= EPOLLIN;
设置监控事件为EPOLLIN可读事件
Requestrequest;
request.fd = fd;
request.ident = ident;
request.callback = callback;
request.data = data; (base\services\core\jni) 58730 2015/6/6
用传递过来的参数初始化Request结构体变量request
声明epoll机制事件结构体变量eventItem并初始化,设events为可读事件,注册的文件描述符为socket服务端fd
判断容器mRequests中是否有socket文件描述符fd,如果没有,就把fd注册到epoll对象mEpollFd中,否则,修改现有的epoll对象的兴趣列表中的fd,更新了eventItem。同时把调用者相关信息对象Request与fd作为一对映射关系保存到mRequests容器中,待执行epoll_wait时使用。
小结:
应用程序端管道inputChannels[1]注册在InputEventReceiver中,其socket对象注册到主线程epoll对象兴趣列表中,当socket对端即服务端inputChannels[0]上有数据写入后,应用程序便可从服务端socket对象上读取数据。
由3.2和4.5.4节可知,当InputDispatcher通过服务端管道向socket文件描述符发送消息后,epoll机制监听到了I/O事件,epoll_wait就会执行返回发生事件的个数给eventCount,主线程开始执行epoll_wait后面的代码:
fd是客户端socket文件描述符,不是mWakeReadPipeFd,因此if语句不成立,进入else子句。mRequests不为空(3.4.2.2节中已经把Request保存在了mRequests中),pushResponse函数把request取出来赋给response,再放到mResponses容器中保存。
mMessageEnvelopes被初始化为空,也没有加入数据,依然为空,这句:
while (mMessageEnvelopes.size() != 0) {
不成立,跳过while循环。
for (size_t i = 0; i < mResponses.size(); i++) {
Response& response = mResponses.editItemAt(i);
if (response.request.ident == POLL_CALLBACK) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
this, response.request.callback.get(), fd, events, data);
#endif
int callbackResult = response.request.callback->handleEvent(fd, events, data);
if (callbackResult == 0) {
removeFd(fd);
}
// Clear the callback reference in the response structure promptly because we
// will not clear the response vector itself until the next poll.
response.request.callback.clear();
result = POLL_CALLBACK;
}
mResponses不为空,for循环取出里面的response对象,然后执行:
int callbackResult = response.request.callback->handleEvent(fd, events, data);
fd就是服务端socket文件描述符,events是发生的事件,data为空,response.request.callback就是addFd中的第4个实参NativeInputEventReceiver对象也是LooperCallback对象,这句话就是回调主线程中的NativeInputEventReceiver对象的handleEvent函数
5.1 NativeInputEventReceiver的handleEvent
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
#if DEBUG_DISPATCH_CYCLE
// This error typically occurs when the publisher has closed the input channel
// as part of removing a window or finishing an IME session, in which case
// the consumer will soon be disposed as well.
ALOGD("channel '%s' ~ Publisher closed input channel or an error occurred. "
"events=0x%x", getInputChannelName(), events);
#endif
return 0; // remove the callback
}
if (events & ALOOPER_EVENT_INPUT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
status_tstatus = 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);
status_tstatus = mInputConsumer.sendFinishedSignal(finish.seq, finish.handled);
if (status) {
mFinishQueue.removeItemsAt(0, i);
if (status == WOULD_BLOCK) {
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Sent %u queued finish events; %u left.",
getInputChannelName(), i, mFinishQueue.size());
#endif
return 1; // keep the callback, try again later
}
ALOGW("Failed to send finished signal on channel '%s'. status=%d",
getInputChannelName(), status);
if (status != DEAD_OBJECT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
String8message;
message.appendFormat("Failed to finish input event. status=%d", status);
jniThrowRuntimeException(env, message.string());
mMessageQueue->raiseAndClearException(env, "finishInputEvent");
}
return 0; // remove the callback
}
}
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Sent %u queued finish events; none left.",
getInputChannelName(), mFinishQueue.size());
#endif
mFinishQueue.clear();
setFdEvents(ALOOPER_EVENT_INPUT);
return 1;
}
ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
"events=0x%x", getInputChannelName(), events);
return 1;
}
先对events事件进程必要的检查,如果包含ALOOPER_EVENT_ERROR或ALOOPER_EVENT_HANGUP表示管道关闭,这种情况下丢弃事件
if (events & ALOOPER_EVENT_INPUT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
status_tstatus = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
return status == OK || status == NO_MEMORY ? 1 : 0;
}
如果为ALOOPER_EVENT_INPUT事件,调用consumeEvents继续执行;如果为ALOOPER_EVENT_OUTPUT表示客户端已经收到数据,需要发送一个完成信号给服务端
5.2 NativeInputEventReceiver的consumeEvents
status_tNativeInputEventReceiver::consumeEvents(JNIEnv* env,
bool consumeBatches, nsecs_tframeTime, bool* outConsumedBatch) {
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%lld.",
getInputChannelName(), consumeBatches ? "true" : "false", frameTime);
#endif
if (consumeBatches) {
mBatchedInputEventPending = false;
}
if (outConsumedBatch) {
*outConsumedBatch = false;
}
ScopedLocalRef receiverObj(env, NULL);
bool skipCallbacks = false;
for (;;) {
uint32_tseq;
InputEvent* inputEvent;
status_tstatus = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent);
if (status) {
if (status == WOULD_BLOCK) {
if (!skipCallbacks && !mBatchedInputEventPending
&& mInputConsumer.hasPendingBatch()) {
// There is a pending batch. Come back later.
if (!receiverObj.get()) {
receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
if (!receiverObj.get()) {
ALOGW("channel '%s' ~ Receiver object was finalized "
"without being disposed.", getInputChannelName());
return DEAD_OBJECT;
}
}
mBatchedInputEventPending = true;
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Dispatching batched input event pending notification.",
getInputChannelName());
#endif
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchBatchedInputEventPending);
if (env->ExceptionCheck()) {
ALOGE("Exception dispatching batched input events.");
mBatchedInputEventPending = false; // try again later
}
}
return OK;
}
ALOGE("channel '%s' ~ Failed to consume input event. status=%d",
getInputChannelName(), status);
return status;
}
assert(inputEvent);
if (!skipCallbacks) {
if (!receiverObj.get()) {
receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
if (!receiverObj.get()) {
ALOGW("channel '%s' ~ Receiver object was finalized "
"without being disposed.", getInputChannelName());
return DEAD_OBJECT;
}
}
jobjectinputEventObj;
switch (inputEvent->getType()) {
case AINPUT_EVENT_TYPE_KEY:
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Received key event.", getInputChannelName());
#endif
inputEventObj = android_view_KeyEvent_fromNative(env,
static_cast(inputEvent));
break;
case AINPUT_EVENT_TYPE_MOTION: {
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Received motion event.", getInputChannelName());
#endif
MotionEvent* motionEvent = static_cast(inputEvent);
if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
*outConsumedBatch = true;
}
inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
break;
}
default:
assert(false); // InputConsumer should prevent this from ever happening
inputEventObj = NULL;
}
if (inputEventObj) {
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName());
#endif
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
if (env->ExceptionCheck()) {
ALOGE("Exception dispatching input event.");
skipCallbacks = true;
}
env->DeleteLocalRef(inputEventObj);
} else {
ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName());
skipCallbacks = true;
}
}
if (skipCallbacks) {
mInputConsumer.sendFinishedSignal(seq, false);
}
}
}
在for循环中调用InputConsumer对象的consume函数从socket客户端获取InputDispatcher发送来的事件保存到inputEvent对象中,先分析consume方法,然后返回来再看后面的代码
在consume函数中有这句:
status_tresult = mChannel->receiveMessage(&mMsg);
receiveMessage的源码中有这句:
在3.2节通过send向服务端socket发送了数据,那么在此处通过recv从客户端socket上获取的消息并保存到mMsg中,如果成功,再根据事件类型选择case语句:
switch (mMsg.header.type) {
case InputMessage::TYPE_KEY: {
KeyEvent* keyEvent = factory->createKeyEvent();
if (!keyEvent) return NO_MEMORY;
initializeKeyEvent(keyEvent, &mMsg);
*outSeq = mMsg.body.key.seq;
*outEvent = keyEvent;
#if DEBUG_TRANSPORT_ACTIONS
ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",
mChannel->getName().string(), *outSeq);
#endif
break;
}
把消息保存到KeyEvent对象中,再付给outEvent,如果一切成功,返回OK到5.2节这句mInputConsumer.consume的后面继续执行:
mReceiverWeakGlobal就是传递过来的WindowInputEventReceiver对象的本地引用
jobjectinputEventObj;
switch (inputEvent->getType()) {
case AINPUT_EVENT_TYPE_KEY:
#if DEBUG_DISPATCH_CYCLE
ALOGD("channel '%s' ~ Received key event.", getInputChannelName());
#endif
inputEventObj = android_view_KeyEvent_fromNative(env,
static_cast(inputEvent));
break;
如果读取所有事件成功,再根据事件的类型选择相应执行语句,如果是按键事件,就调用android_view_KeyEvent_fromNative把按键事件传递给Java层KeyEvent对象并初始化,然后返回该对象赋给变量inputEventObj,继续执行到这句:
receiverObj.get()得到WindowInputEventReceiver对象,gInputEventReceiverClassInfo.dispatchInputEvent就是待调方法的id号,CallVoidMethod函数的作用就是调用第一个参数WindowInputEventReceiver对象的dispatchInputEvent方法,后面两个实参会传递给该方法,如果这一切都成功,就把按键事件传递到java层处理。
由于WindowInputEventReceiver中没有实现dispatchInputEvent,因此直接调用父类InputEventReceiver的dispatchInputEvent方法
5.3 InputEventReceiver的dispatchInputEvent
onInputEvent在WindowInputEventReceiver中已经实现,就调用WindowInputEventReceiver中的onInputEvent方法,onInputEvent调用了enqueueInputEvent
5.4 ViewRootImpl的enqueueInputEvent
enqueueInputEvent(event, this, 0, true);
void enqueueInputEvent(InputEventevent,
InputEventReceiverreceiver, int flags, boolean processImmediately) {
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// Always enqueue the input event in order, regardless of its time stamp.
// We do this because the application or the IME may inject key events
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
QueuedInputEventlast = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
obtainQueuedInputEvent方法把输入事件封装成QueuedInputEvent对象并放入到QueuedInputEvent对象池中,然后取出一个QueuedInputEvent事件后按照次序排列,再调用doProcessInputEvents方法从队列中循环取出事件发送给输入法或者应用程序等不同阶段进行处理。
doProcessInputEvents的调用过程:
doProcessInputEvents —-> deliverInputEvent —-> stage.deliver(q)
public final void deliver(QueuedInputEvent q) {
if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
forward(q);
} else if (shouldDropInputEvent(q)) {
finish(q, false);
} else {
apply(q, onProcess(q));
}
}
当前事件还没有处理,因此不包含FLAG_FINISHED标致,if语句不成立;正常情况下不会丢弃当前事件,第一个else子句也不成立,执行最后一个else子句。apply的第二个参数是onProcess方法,ViewRootImpl中有8个onProcess方法,具体调用哪个?
在setView方法最后创建了很多InputStage对象:
InputStage是抽象基类,ViewPostImeInputStage,EarlyPostImeInputStage,ViewPreImeInputStage等对象都是为了处理输入事件在不同阶段而创建的,比如:ViewPostImeInputStage表示发送输入事件给view树进行处理,这些输入事件都是在输入法处理之后的。ViewPreImeInputStage表示输入事件必须在输入法处理之前发送给view树处理。
ViewPreImeInputStage表示在输入法之前处理,ImeInputStage表示进入输入法处理,ViewPostImeInputStage表示发送给视图。如果有输入法窗口,就先传输给ViewPreImeInputStage处理,如果没有,传输给ViewPostImeInputStage,一般情况下,都是传给ViewPostImeInputStage。
此处会调用ViewPostImeInputStage的onProcess来处理:
if语句成立,调用processKeyEvent
5.5 ViewPostImeInputStage的processKeyEvent
processKeyEvent方法非常重要了,做了很多任务
mView是DecorView,所有view的根,这段话就是把按键事件传给view处理,从此处开始就正式转交给应用层,在第6节将进行详细分析
处理Ctrl组合按键
处理所有回退按键事件,主要是一些还没有处理的特殊按键。比如相机拍照、拨号按键等。如果特殊按键没有在PhoneWindowManager、view树、窗口中处理,就传到此处
处理方向键和TAB键,找到获得焦点的view并把这几个按键传递过去,如果没有view有焦点,就找一个最合适的view并把按键传递过去
小结:
应用程序客户端通过NativeInputEventReceiver的InputConsumer方法从客户端管道InputChannel中获取事件消息,经过过滤,转化成应用层按键类型,再把按键事件传递到输入法窗口,应用层
由5.5节这句:
可知,按键事件传递到了mView的dispatchKeyEvent方法,mView就是PhoneWindow内部类DecorView对象,因此,应用层按键事件就从DecorView的dispatchKeyEvent方法开始
也可以这样理解,InputDispatcher先找到当前获得焦点的窗口,把事件发送给该窗口,窗口在启动activity时会创建,按键事件就传递到了获得焦点的窗口对应的所有view的根类DecorView,也可以说传递给了获得焦点的窗口对应的Activity对象。
Q10 mView是什么时候创建的?如何传递的?见6.6节
6.1 DecorView的dispatchKeyEvent
前面两个if语句是快捷按键的处理
getCallback返回的是CallBack对象cb,cb对象代表一个Activity或Dialog对象,一般情况下不为空。本文主要讨论Activity,不再使用“Activity或Dialog对象”这样的术语;
mFeatureId:代表应用程序的特征标识或者整个屏幕的标识,如果是应用程序,就为-1,具体赋值过程为:
Activity的onCreate —-> setContentView —-> PhoneWindow的setContentView —-> installDecor() —->
generateDecor() —-> new DecorView(getContext(), -1)
如果Activity对象不为空,mFeatureId为-1,调用Activity对象的dispatchKeyEvent方法,将在6.2节分析;
如果为空,就调用super.dispatchKeyEvent(event)即父类ViewGroup的dispatchKeyEvent,将在6.2.1节分析。
如果返回结果为true,表明已经消耗,按键事件不再往后传递,否则执行到:
这一步传递到PhoneWindow的onKeyDown、onKeyUp方法,将在6.4节分析。
Q11 cb对象为什么是Activity?
在这篇文章: 启动Activity的流程(Launcher中点击图标启动)
过程18中创建Activity对象后,调用了Activity对象的attach进行初始化,在attach中有:
PolicyManager类的静态方法makeNewWindow —-> Policy的makeNewWindow —-> new PhoneWindow(context)
makeNewWindow 最终创建了一个PhoneWindow对象,setCallback方法把该this对象即Activity传过去赋值给mCallback,然后getCallback返回mCallback即该Activity对象
通过这段话可知,当启动某个应用的Activity时,系统会创建一个PhoneWindow对象与之对应并拥有一个该对象引用,在PhoneWindow对象中通过该对象引用回调Activity的方法,比如dispatchKeyEvent。
6.2 Activity的dispatchKeyEvent
Activity的dispatchKeyEvent起到拦截按键作用,如果这一步不处理,将分发给view或viewGroup处理。
onUserInteraction();
如果有按键、触摸、轨迹球事件分发给Activity时,在具体事件处理之前,会回调onUserInteraction,一般情况下,用户需要自行实现该方法,与onUserLeaveHint一起配合使用辅助Activity管理状态栏通知。在按键按下、抬起时都会触发该方法回调;但在触摸时,只有触摸按下时被回调,触摸移动、抬起时不会回调。
如果有Menu键且状态栏ActionBar消耗了该键,就直接返回true;否则继续往下处理
public boolean superDispatchKeyEvent(KeyEventevent) {
// Give priority to closing action modes if applicable.
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
if (mActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mActionMode.finish();
}
return true;
}
}
return super.dispatchKeyEvent(event);
}
获得当前Activity对应的Window对象,也就是PhoneWindow,把按键事件传递给PhoneWindow对象,主要对Back按键松开的特殊处理,如果没有消耗,连同其它按键事件一起传递到super.dispatchKeyEvent即父类ViewGroup,具体处理过程在6.2.1节;
如果父类ViewGroup也没有处理,传递到KeyEvent的dispatch方法,具体处理过程在6.3节。
6.2.1 ViewGroup的dispatchKeyEvent
对按键事件进行一致性检查,这种检查防止同样的错误多次发生,如果有错误,日志会打印出来
PFLAG_FOCUSED表示获得焦点,PFLAG_HAS_BOUNDS表示大小、边界被确定了,如果ViewGroup本身有焦点且其大小已确定,就调用该ViewGroup的父类即View的dispatchKeyEvent来处理,具体在6.2.1.1中分析
mFocused是ViewGroup内部包含的获得焦点的子view,如果该子view获得焦点且大小、边界已确定,就调用该子view的dispatchKeyEvent处理。子view既可以是ViewGroup、LinearLayout、RelativeLayout等布局也可以是view、TextView、Button、ListView等,第二个if语句就是递归传递到所有布局和view中
假如某Activity的布局是自定义一个LinearLayout,称为A,其内部包含一个LinearLayout,称为B,B中包含一个TextView C,C有焦点。dispatchKeyEvent传递流程是:ViewGroup —-> A —-> B —-> C
传递到view时的具体情况在6.2.2节
6.2.2 view的dispatchKeyEvent
ListenerInfo类专门描述所有view相关监听器信息的类,比如OnFocusChangeListener、OnScrollChangeListener、OnClickListener、mOnTouchListener等。
当某个view比如button、imageView设置了某个监听器时,在setOnXXXListener方法中就会调用getListenerInfo方法创建ListenerInfo对象并赋值给mListenerInfo变量,因此,该变量肯定不为空,如果没有设置监听器,那就为空,假设有监听器,下面这条语句成立:
if (li != null && li.mOnKeyListener != null
如果view设置了监听器,且enable属性为true,会优先调用OnKeyListener的onKey方法处理,如果没有设置该监听器或者该监听器没有消耗掉,按键继续传递到KeyEvent的dispatch
6.2.2.1 KeyEvent的dispatch
receiver:既可能是Activity对象,又可能是view对象,当前是view对象,因为是view的dispatchKeyEvent传过来的,在6.3节,传递过来的是Activity对象
state:通过getKeyDispatcherState返回KeyEvent内部类DispatcherState对象,该对象用来进行高级别的按键事件处理,如长按事件等;在getKeyDispatcherState方法内部,mAttachInfo是AttachInfo对象,当view关联到窗口时的一系列信息,AttachInfo类用来描述、跟踪这些信息,一般情况下不为空
第三个参数target,实参是this,既是Activity或view对象,也是KeyEvent的内部类CallBack类型
case语句先处理按键按下ACTION_DOWN动作:
先清掉FLAG_START_TRACKING标记,再调用view的onKeyDown方法,此方法在6.2.2.2节进行分析
如果onKeyDown返回true,表明已经消耗,res为true
如果view的onKeyDown已经消耗掉,且是第一次按下,mFlags包含FLAG_START_TRACKING,就调用startTracking跟踪按键事件,这样做便于判断是否是长按事件的条件,如果有长按事件,并且正在跟踪当前按键,就调用view的onKeyLongPress处理:
public boolean onKeyLongPress(int keyCode, KeyEventevent) {
return false;
}
源码总是返回false,不作任何处理,用户可以根据需求重写该方法来实现长按
case语句处理按键松开ACTION_UP动作:
return receiver.onKeyUp(mKeyCode, this);
回调view的onKeyUp方法,mKeyCode是按键码值,this是KeyEvent对象,此方法也在6.2.2.2节分析,此时,dispatch主要作用是回调view的onKeyDown、onKeyUp,注意与6.3节的区别。
6.2.2.2 view的onKeyDown、onKeyUp
view的onKeyDown:
public boolean onKeyDown(int keyCode, KeyEventevent) {
boolean result = false;
if (KeyEvent.isConfirmKey(keyCode)) {
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
// Long clickable items don't necessarily have to be clickable
if (((mViewFlags & CLICKABLE) == CLICKABLE ||
(mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
(event.getRepeatCount() == 0)) {
setPressed(true);
checkForLongClick(0);
return true;
}
}
return result;
}
主要针对KEYCODE_DPAD_CENTER、KEYCODE_ENTER事件进行处理:
如果当前按键事件包含KEYCODE_DPAD_CENTER、KEYCODE_ENTER,并且view设置了DISABLED属性,直接返回true,说明该view已经被按下;
如果view设置了单击CLICKABLE或长按状态LONG_CLICKABLE,就调用setPressed把该view设置为PRESSED状态,同时更新其绘制状态(显示状态,比如更换了背景图片、颜色等);如果仅是长按状态,系统在500秒后执行下面几种情形:
a. 如果有长按监听器OnLongClickListener,就回调onLongClick,如果成功,系统会发出一个触觉反馈;
b. 如果没有长按监听器,就显示一个菜单。
如果按键事件不包含KEYCODE_DPAD_CENTER、KEYCODE_ENTER,onKeyDown不作任何处理,直接return false
view的onKeyUp:
在onKeyUp方法中,setPressed(false)去除view的pressed状态,同时更新其绘制状态(显示状态)
如果在onKeyDown中没有处理长按事件,那么mHasPerformedLongPress为false,就把长按事件对象从消息的队列中移除。最后,调用performClick:
public boolean performClick() {
final boolean result;
final ListenerInfoli = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
如果设置了OnClickListener监听器,就回调onClick方法。
由此可知,当遥控器按键(KEYCODE_DPAD_CENTER、KEYCODE_ENTER)松开时,如果设置了OnClickListener监听器,会调用onClick方法。
6.3 keyEvent的dispatch
该方法已经在6.2.2节分析过,只不过receiver对象已经不再是view,而是Activity,因为是从Activity的dispatchKeyEvent传递而来,此时,dispatch主要作用是回调Activity的onKeyDown、onKeyUp
6.3.1 Activity的onKeyDown,onKeyUp
如果Activity里面的任何view、布局都没有处理按键,就会传递到Activity的onKeyDown,onKeyUp。比如,当在EditText中输入文字时,Activity的onKeyDown,onKeyUp不会接收到按键事件,因为EditText有自己的处理按键事件的方法,如果此时把焦点从EditText移走,onKeyDown,onKeyUp就会接收到按键事件。
onKeyDown源码:
这段话需要结合onKeyUp来看:
如果在Android2.1之前的版本(不包含),按下BACK键后调用onBackPressed直接退出Activity;
如果在Android 2.1(包含)之后的版本,先调用startTracking方法把mFlags置为FLAG_START_TRACKING,系统会跟踪按键传递过程,直到松开按键进入到onKeyUp方法时才会调用onBackPressed,可以重写该方法退出Activity,也就是说,只有松开BACK按键时才会退出Activity,如果不松开不会退出Activity。
随后根据按键模式mDefaultKeyMode决定做哪些事情,此处不是重点,本文不作分析
onKeyUp源码
if语句就是配合onKeyDown使用的,如果在android 2.1(包含)之后的版本,松开按键时才会退出Activity;其他按键直接返回false,一般重写onKeyUp实现自己的需求。
如果onKeyDown,onKeyUp没有消耗掉按键事件,就逆向返回到KeyEvent的dispatch中处理,如果dispatch也没有消耗掉,就返回到Activity —-> DecorView —-> PhoneWindow,进入到 PhoneWindow中处理。
6.4 PhoneWindow的onKeyDown、onKeyUp
经过上述方法处理,如果返回false,说明所有应用程序层的view、viewGroup、Activity都没有消耗掉,按键事件传递到了当前窗口window中进行处理
onKeyDown源码:
onKeyDown/onKeyUp方法主要针对当前获得焦点的窗口对一些特殊按键进行处理,包括音量+/-,多媒体控制按键,MENU,BACK
注意:PhoneFallbackEventHandler中也是对特殊按键进行处理,但是那是针对所有所有的窗口,包括当前获得焦点的窗口,而PhoneWindow只针对当前获得焦点的窗口
6.5 小结
应用层按键事件传递时涉及到很多情况,大概传递流程:
a. 应用层按键事件传递到view树根DecorView后分为两步:view树内部;view树外部(获得焦点的窗口)
如果view树内部没有消耗,就传递到view树外部,即传递给获得焦点的窗口的onKeyDown/onKeyUp;
b. view树内部一般先传递到当前Activity对象,如果没有消耗,传递到Activity的onKeyDown/onKeyUp;
c. Activity对象内部先分发给ViewGroup,viewGroup如果本身有焦点就传递给其父类view;
如果viewGroup本身没有焦点,就传递给其获得焦点的子view。子view分为两种情况:
如果子view是LinearLayout等常见布局,就递归传递过去,最后传递给获得焦点的view视图;
如果子view是纯粹的view视图,就传递给该视图;
d. view视图内部,如果设置了OnKeyListener监听器,就传递给OnKey;如果没有OnKeyListener监听器,就分发给KeyEvent的dispatch,dispatch主要回调view的onKeyDown/onKeyUp;
e. 在view的onKeyDown/onKeyUp中,如果是DPAD_CENTER,KEYCODE_ENTER,直接处理;否则,更新绘制状态、执行长按处理、执行onClick方法等。
该小结没有考虑所有条件,只是大概给出传递流程,因为很多时候重写某个方法返回true不再传递下去,因此也就没有过多步骤。
6.6 mView的创建过程
在 启动Activity的流程(Launcher中点击图标启动) 这篇文章中的过程18的handleResumeActivity方法中,有这段语句:
r.activity就是新启动的目标Activity对象,getWindow返回mWindow对象,mWindow的创建过程:
mWindow = PolicyManager.makeNewWindow(this);
PolicyManager对象的makeNewWindow —-> sPolicy.makeNewWindow(context)
sPolicy是Policy对象,程序调到了Policy的makeNewWindow:
public WindowmakeNewWindow(Contextcontext) {
return new PhoneWindow(context);
}
创建了一个PhoneWindow对象并返回给mWindow,再赋值给r.window
r.window.getDecorView()方法调用PhoneWindow对象的getDecorView方法
如果mDecor为空,就调用installDecor方法新创建一个DecorView对象,否则,直接返回该对象。
r.window.getDecorView() —-> PhoneWindow的installDecor方法 —-> mDecor = generateDecor() —->
return new DecorView(getContext(), -1)
在PhoneWindow中创建了一个DecorView对象并返回给decor变量
通过这两句可知,启动一个新的Activity时,系统会创建一个对应的widow窗口对象(实际是PhoneWindow对象),这是一对一关系;同时,如果已经有了DecorView就复用之,否则,新创建一个DecorView对象(DecorView最终继承于View,也是一个View对象),这是多对一关系。
ViewManagerwm = a.getWindowManager();
getWindowManager返回mWindowManager变量,mWindowManager的赋值语句为:
mWindowManager = mWindow.getWindowManager();
已经可知mWindow是一个PhoneWindow对象,这样就调到了PhoneWindow的getWindowManager方法,PhoneWindow中没有实现getWindowManager,直接调用父类Window的getWindowManager:
WindowManagerImpl继承了WindowManager,createLocalWindowManager方法源码:
public WindowManagerImplcreateLocalWindowManager(WindowparentWindow) {
return new WindowManagerImpl(mDisplay, parentWindow);
}
创建了一个与Activity对应的WindowManagerImpl对象。
wm.addView(decor, l);
调用WindowManagerImpl对象的addView方法,源码:
mGlobal是WindowManagerGlobal的单例对象,addView方法中有:
root.setView(view, wparams, panelParentView);
root是ViewRootImpl对象,调用其setView方法把DecorView对象传递过去并赋值给mView
小结:mView就是与Activity对应的DecorView对象,在创建PhoneWindow对象时创建的。
特殊按键处理方法主要有:
interceptKeyBeforeQueueing
interceptKeyBeforeDispatching
PhoneWindow的onKeyDown/onKeyUp
PhoneFallbackEventHandler的dispatchKeyEvent
每个方法用在不同的时刻
7.1 interceptKeyBeforeQueueing
在2.2节提到,NativeInputManager传递过来后赋给了mPolicy变量,interceptKeyBeforeQueueing在NativeInputManager中也实现了,interceptKeyBeforeQueueing用以处理系统级按键,比如HOME、TVSOURCE等
7.1.1 com_android_server_input_InputManagerService.cpp的interceptKeyBeforeQueueing
if (mInteractive) {
policyFlags |= POLICY_FLAG_INTERACTIVE;
}
设置按键标志为活动状态
jobjectkeyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
jobjectandroid_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event) {
jobjecteventObj = env->CallStaticObjectMethod(gKeyEventClassInfo.clazz,
gKeyEventClassInfo.obtain,
nanoseconds_to_milliseconds(event->getDownTime()),
nanoseconds_to_milliseconds(event->getEventTime()),
event->getAction(),
event->getKeyCode(),
event->getRepeatCount(),
event->getMetaState(),
event->getDeviceId(),
event->getScanCode(),
event->getFlags(),
event->getSource(),
NULL);
if (env->ExceptionCheck()) {
ALOGE("An exception occurred while obtaining a key event.");
LOGE_EX(env);
env->ExceptionClear();
return NULL;
}
return eventObj;
}
android_view_KeyEvent_fromNative方法中,通过调用JNI接口的CallStaticObjectMethod方法获得第二个参数gKeyEventClassInfo.obtain返回的值并把值赋给eventObj,gKeyEventClassInfo.obtain是什么?在register_android_view_KeyEvent函数中通过findclass得知,gKeyEventClassInfo.clazz就是java层KeyEvent类的类本地引用,gKeyEventClassInfo.obtain就是KeyEvent中的obtain方法在本地的method id号;CallStaticObjectMethod函数调用KeyEvent中obtain方法,返回值是java层KeyEvent对象,因此,android_view_KeyEvent_fromNative返回值为java层的KeyEvent对象在本地的引用,赋给keyEventObj
由1.1.1和1.1.2节可知,mServiceObj就是传递过来的InputManagerService对象,gServiceClassInfo.interceptKeyBeforeQueueing保存了InputManagerService对象中interceptKeyBeforeQueueing方法的method id号,CallIntMethod方法调用InputManagerService中interceptKeyBeforeQueueing方法并把返回值赋给wmActions变量
handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
void NativeInputManager::handleInterceptActions(jintwmActions, nsecs_twhen,
uint32_t& policyFlags) {
if (wmActions & WM_ACTION_PASS_TO_USER) {
policyFlags |= POLICY_FLAG_PASS_TO_USER;
} else {
#if DEBUG_INPUT_DISPATCHER_POLICY
ALOGD("handleInterceptActions: Not passing key to user.");
#endif
}
}
根据返回值wmActions决定特殊按键的走向,如果wmActions为WM_ACTION_PASS_TO_USER即1,那么把policyFlags设为POLICY_FLAG_PASS_TO_USER,意思是说该按键应该传输给应用程序处理,不在framework层处理,比如,HOME按键不应该传给应用程序,而应在framework层处理,不针对某一个应用程序,针对的整个系统,在任何应用程序界面下按下HOME都能起作用。
7.1.2 InputManagerService的interceptKeyBeforeQueueing
mWindowManagerCallbacks是InputMonitor对象,在1.4节提到过,该对象在InputManagerService对象创建后通过其setWindowManagerCallbacks传递过去,便于回调InputMonitor对象中的方法
7.3 InputMonitor的interceptKeyBeforeQueueing
/* Provides an opportunity for the window manager policy to intercept early key
* processing as soon as the key has been read from the device. */
@Override
public int interceptKeyBeforeQueueing(KeyEventevent, int policyFlags) {
return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags);
}
mPolicy是WindowManageService对象在初始化时创建的PhoneWindowManager对象,因此,最终调到了PhoneWindowManager的interceptKeyBeforeQueueing
interceptKeyBeforeQueueing作用:当按键事件从设备中读取后,对按键进行最早期拦截预处理,因为某些特殊按键直接影响设备状态,比如,电源键、唤醒键,此外,还包括拨号键、挂号键、音量键等
7.2 interceptKeyBeforeDispatching
在3.1节提到过doInterceptKeyBeforeDispatchingLockedInterruptible函数,这也是拦截按键方法
7.2.1 InputDispatcher的doInterceptKeyBeforeDispatchingLockedInterruptible
在1.1.5节提到,NativeInputManager传递过来后赋给了mPolicy变量,所以:
mPolicy->interceptKeyBeforeDispatching
调到了NativeInputManager中的interceptKeyBeforeDispatching,经过与7.1节interceptKeyBeforeQueueing类似的调用过程,interceptKeyBeforeDispatching函数最终会调用到PhoneWindowManager中同名方法。
interceptKeyBeforeDispatching作用:在input dispatcher thread把按键分发给窗口之前拦截,根据某种策略决定如何处理按键事件。
具体策略为:
如果返回值delay为-1,interceptKeyResult设置为INTERCEPT_KEY_RESULT_SKIP,表示按键事件已经消耗掉,不需要传给应用程序;
如果delay等于0,interceptKeyResult设置为INTERCEPT_KEY_RESULT_CONTINUE,表示按键事件没有特殊处理,继续传递给应用程序;
如果delay大于0,interceptKeyResult设置为INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,表示按键事件需要重新执行一次,同时设置按键下一次执行时间为now() + delay
interceptKeyBeforeDispatching中拦截的特殊按键有:HOME, MENU, SEARCH, SOURCE通道, 亮度调节等
7.3 PhoneWindow的onKeyDown/onKeyUp
在6.4节已经分析过,针对当前获得焦点的窗口进行处理
7.4 PhoneFallbackEventHandler的dispatchKeyEvent
在5.5节,有这样一句:
mFallbackEventHandler就是PhoneFallbackEventHandler对象,这是最后拦截特殊按键进行处理的方法,与 PhoneWindow区别的是,PhoneFallbackEventHandler针对所有窗口的
7.5 小结
按键事件的特殊处理可用逻辑执行顺序简单表达:
PhoneWindowManager的interceptKeyBeforeQueueing —->PhoneWindowManager的interceptKeyBeforeDispatching —-> PhoneWindow的onKeyDown/onKeyUp —-> PhoneFallbackEventHandler的dispatchKeyEvent
这个逻辑不是必须的,主要看按键是否消耗、按键的具体功能需求,有时候只需要在PhoneWindowManager中处理即可
8.1 按键事件传递流程总结
a. InputReaderThread获取输入设备事件后过滤,保存到InboundQueue队列中
b. InputDispatcherThread从InboundQueue取出数据,先进行特殊处理,然后找到获得焦点的窗口,再把数据临时放到OutboundQueue中,然后取出数据打包后发送到服务端socket
c. 应用程序主线程中的WindowInputEventReceiver从客户端socket上读取按键数据,再传递给应用层
d. 应用层获取到事件后先分发给view树处理,再处理回退事件
8.2 输入事件核心组件
InputManagerService:输入事件的服务端核心组件,直接创建看门狗Watchdog、NativeInputManager对象,间接创建EventHub、InputManager以及InputDispatcherThread、InputReaderThread线程,通过InputManager方法间接启动接收器线程、分发器线程;对系统特殊按键的处理;注册服务端管道
NativeInputManager:本地InputManager,创建c++层InputManager、EventHub对象
C++ 层InputManager:创建InputDispatcher、InputReader对象,创建并启动InputDispatcherThread、InputReaderThread线程
Java层InputManager:提供输入设备信息、按键布局等
InputReader:输入事件接收者,从EventHub中获得原始输入事件信息
InputDispatcher:分发事件给应用层,发送事件的服务端
EventHub:事件的中心枢纽,收集了所有输入设备的事件,包括虚拟仿真设备事件。
InputEventReceiver:接收输入事件的客户端
InboundQueue:InputReaderThread读取事件后保存到InboundQueue中等到InputReaderThread接收
outboundQueue:InputDispatcherThread从InboundQueue中取出数据放到outboundQueue中等待发送,然后从outboundQueue取出数据发送到服务端InputChannel等待应用层(或者称为客户端)接收
c++层InputChannel:一个输入通道,包含一对本地unix socket对象,服务端InputDispatcherThread向socket对象写入数据,客户端InputEventReceiver从客户端socket读取数据
8.3 按键分类
一般按键:一般会传递到应用程序中进行处理,比如,在app中经常调用菜单键弹出菜单,按下方向键使得焦点移动等;主要有数字键,方向键,确认OK键,返回BACK键,菜单MENU键等;
特殊按键:一般不传递到应用程序中,直接在framework层处理,不与某个应用有直接的关联,应用范围适用整个系统,比如,HOME键,按下HOME就返回到桌面,不限制于某个应用;音量键,控制系统音量,不特别针对某个应用;主要有电源POWER键,HOME键,音量VOLUME键,静音MUTE键等;
TV一般按键:在TV的应用中处理的键,比如,在DTV的Activity中通过EPG调用节目信息,通过SUBTITLE调用字幕界面等;包含INFO信息键,喜爱节目FAV键,EPG键,字幕SUBTITLE,音轨TRACK键,多媒体播放键等;
TV特殊按键:不传递到TV的应用中,在framework层进行处理,比如SOURCE通道键,在任何界面下按下SOURCE都能够调出通道界面切换通道。
在Android手机上,常见的一般按键是MENU、BACK,特殊按键包括电源键,HOME键,音量键等
在TV上,特殊按键主要指HOME键,SOURCE键,电源键,音量键等
8.4 工作记录
a. 如果主界面包括epg,channellist, TV多个窗口,需要在这些小界面上跳动焦点,如何实现
重写Activity的dispatchkeyEvent和onKeyDown或onKeyUp,控制某个小界面获得焦点
b. 如果app中按键不响应,通过getEvent命令检查是否有输入设备,是否能够获得按键事件,如果能,可以确保底层没问题,问题出现在应用层或Framework层,检查PhoneWindowManager, dispatchKeyEvent;如果getEvent检查不到设备,检查驱动(前提是确保硬件等正常);如果有设备,但getEvent获取不到事件,检查系统能否接收到红外信号,同时确定kl文件是否配对。
c. 如要在任何app下都可以启动、杀死某个应用,可在PhoneWindowManager或PhoneFallbackEventHandler中对按键进行特殊处理,采用该应用的包名、类名,直接控制该应用