Android事件输入和传递系统
事件的产生和传递
- 按键事件
- 触摸事件
- 鼠标事件
- 轨迹球事件
InputEvent 输入事件的基类,派生两个类型的子类KeyEvent和TouchEvent。
整个事件处理流程如下:
事件采集(由Linux驱动支持) -> 前期处理(提取有用信息) -> WindowManagerService分发 -> 应用程序处理
这里主要搞清楚两个问题:
1、输入事件是怎么监听到的?
2、监听到事件是怎么分发到应用程序中的?
InputManagerService也是在system进程启动的。
//frameworks.base.services.java.com.android.server.SystemServer.java
private void startOtherServices() {
InputManagerService inputManager = null;
inputManager = new InputManagerService(context);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
}
//java.com.android.server.input.InputManagerService.java
public void start() {
Slog.i(TAG, "Starting input manager");
//mPtr指针是NativeInputManager实例,在其构造函数中初始化了InputManager。
//mPtr是在nativeInit函数中初始化的
nativeStart(mPtr);
...
}
//构造函数
public InputManagerService(Context context) {
this.mContext = context;
this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
mUseDevInputEventForAudioJack =
context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
+ mUseDevInputEventForAudioJack);
//初始化NativeInputManager,同时初始化了InputManager
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
String doubleTouchGestureEnablePath = context.getResources().getString(
R.string.config_doubleTouchGestureEnableFile);
mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
new File(doubleTouchGestureEnablePath);
LocalServices.addService(InputManagerInternal.class, new LocalService());
}
//frameworks.base.services.core.jni.com_android_server_input_InputManagerService.cpp
//NativeInputManager构造函数,初始化InputManager
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用于监听输入事件
sp<EventHub> eventHub = new EventHub();
mInputManager = new InputManager(eventHub, this, this);
}
static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
//启动InputManager,调用其start函数
status_t result = im->getInputManager()->start();
if (result) {
jniThrowRuntimeException(env, "Input manager could not be started.");
}
}
事件采集
Native层的InputManager在创建后,会启动两个线程InputRenderThread和InputDispatcherThread。前者负责从驱动节点读取Event,后者负责分发这些事件。
//frameworks.base.services.inputflinger.InputManager.cpp
//构造函数
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
//InputDispatcher负责事件分发
mDispatcher = new InputDispatcher(dispatcherPolicy);
//InputReader负责事件采集,并发采集到事件经过处理后交给mDispatcher分发出去
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
void InputManager::initialize() {
//构建两个线程
mReaderThread = new InputReaderThread(mReader);
mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
status_t InputManager::start() {
//启动两个线程,Android中Thread的实现就是pthread_create函数,
//线程创建后最终会调用threadLoop()函数
status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
if (result) {
return result;
}
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
if (result) {
mDispatcherThread->requestExit();
return result;
}
return OK;
}
//frameworks.base.services.inputflinger.InputReader.cpp
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
void InputReader::loopOnce() {
//可以看到其核心是通过EventHub监听事件输入的,那么EventHub是怎么做到的呢?
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
if (count) {
//处理事件
processEventsLocked(mEventBuffer, count);
}
//将事件分发给监听者,也就是InputDispatcher
mQueuedListener->flush();
}
1.遍历/dev/input目录,为输入事件文件添加监听。
//事件输入目录,该目录下的每个文件都对应一个输入设备,文件名称样式为event0、event1...等等
static const char *DEVICE_PATH = "/dev/input";
status_t EventHub::scanDirLocked(const char *dirname)
{
char devname[PATH_MAX];
char *filename;
DIR *dir;
struct dirent *de;
dir = opendir(dirname);
if(dir == NULL)
return -1;
strcpy(devname, dirname);
filename = devname + strlen(devname);
*filename++ = '/';
while((de = readdir(dir))) {
if(de->d_name[0] == '.' &&
(de->d_name[1] == '\0' ||
(de->d_name[1] == '.' && de->d_name[2] == '\0')))
continue;
strcpy(filename, de->d_name);
openDeviceLocked(devname);
}
closedir(dir);
return 0;
}
//遍历出所有文件,并添加监听
status_t EventHub::openDeviceLocked(const char *devicePath) {
int fd = open(devicePath, O_RDWR | O_CLOEXEC);
...
//省略通过ioctl获取输入设备的名称、版本、标识、物理位置等信息。
...
//根据设备文件属性构造Device实例
Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
//然后判断是什么类型的输入设备(多点触控板、单点触控板、蓝牙书写笔、游戏摇杆等)
//如果是键盘,还要加载键盘布局
// 使用epoll监听文件输入事件
struct epoll_event eventItem;
memset(&eventItem, 0, sizeof(eventItem));
eventItem.events = EPOLLIN;
if (mUsingEpollWakeup) {
eventItem.events |= EPOLLWAKEUP;
}
eventItem.data.u32 = deviceId;
if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
ALOGE("Could not add device fd to epoll instance. errno=%d", errno);
delete device;
return -1;
}
//添加到Device队列头部
addDeviceLocked(device);
}
那么,是什么时候触发的输入设备扫描呢?就是前面提到的getEvents函数中。
//读取输入事件,其中第二个参数RawEvent是一个输出参数,用来存储原始的事件数据
//返回值size_t代表读取到的事件个数
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
//
struct input_event readBuffer[bufferSize];
RawEvent* event = buffer;
size_t capacity = bufferSize;
for (;;) {
//扫描输入设备
if (mNeedToScanDevices) {
mNeedToScanDevices = false;
scanDevicesLocked();
mNeedToSendFinishedDeviceScan = true;
}
//监听设备文件发出输入事件
//mPendingEventItems是一个长度为16的epoll_event数组
//pollResult代表发生的事件个数
int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
//赋值给mPendingEventCount,代表有等待处理的事件
mPendingEventCount = size_t(pollResult);
//循环处理epoll事件
while (mPendingEventIndex < mPendingEventCount) {
const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
//如果是输入事件,则从设备文件中读取事件数据
if (eventItem.events & EPOLLIN) {
int32_t readSize = read(device->fd, readBuffer,
sizeof(struct input_event) * capacity);
size_t count = size_t(readSize) / sizeof(struct input_event);
for (size_t i = 0; i < count; i++) {
//将input_event转换成RawEvent
struct input_event& iev = readBuffer[i];
event->deviceId = deviceId;
event->type = iev.type;
event->code = iev.code;
event->value = iev.value;
event += 1;
}
}
}
}
//RawEvent修改前后的差值就是事件个数
return event - buffer;
}
事件分发
InputReader获取到输入事件后,就会交给InputDispatcher去分发给合适的应用程序。
//构造函数
InputManager::InputManager(
const sp<EventHubInterface>& eventHub,
const sp<InputReaderPolicyInterface>& readerPolicy,
const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
//InputDispatcher负责事件分发
mDispatcher = new InputDispatcher(dispatcherPolicy);
//InputReader负责事件采集,并发采集到事件经过处理后交给mDispatcher分发出去
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
InputDispatcher在创建之初就与InputReader建立了联系。当InputReader通过EventHub拿到事件后,返回到loopOnce()函数后,继续执行mQueuedListener->flush(),mQueuedListener是InputListenerInterface接口类型子接口,内部添加了一个flush()函数,InputDispatcher类就继承了InputListenerInterface接口。
在分给应用程序之前,会对原始输入事件进行处理,每种事件类型都对应一个InputMapper,InputMapper实现不同类型输入事件的转换。以键盘输入事件为例:
//frameworks.base.services.inputflinger.InputReader.cpp
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
//区分不同的事件类型
switch (rawEvent->type) {
case EV_KEY: {
int32_t scanCode = rawEvent->code;
int32_t usageCode = mCurrentHidUsage;
mCurrentHidUsage = 0;
if (isKeyboardOrGamepadKey(scanCode)) {
//如果是按键,要根据扫描码转换成按键码
//rawEvent->value代表按下或抬起
processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);
}
break;
}
...
}
void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
int32_t usageCode) {
int32_t keyCode;
int32_t keyMetaState;
uint32_t policyFlags;
//由EventHub将扫描码scan转换成按键码,因为EventHub知道加载的什么样的键盘布局
if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, mMetaState,
&keyCode, &keyMetaState, &policyFlags)) {
keyCode = AKEYCODE_UNKNOWN;
keyMetaState = mMetaState;
policyFlags = 0;
}
...
//将按键事件封装成NotifyKeyArgs,并添加到QueuedInputListener的队列中
NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
getListener()->notifyKey(&args);
}
//frameworks.base.services.inputflinger.InputListener.cpp
Vector<NotifyArgs*> mArgsQueue;
//添加到队列中
void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
mArgsQueue.push(new NotifyKeyArgs(*args));
}
//添加到队列后,当再调用flush函数后就会处理队列中的事件了
void QueuedInputListener::flush() {
size_t count = mArgsQueue.size();
for (size_t i = 0; i < count; i++) {
NotifyArgs* args = mArgsQueue[i];
args->notify(mInnerListener);
delete args;
}
mArgsQueue.clear();
}
//实际上就是调用InputListenerInterface的notifyKey函数,这里也就是InputDispatcher类
void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
listener->notifyKey(this);
}
//frameworks.base.services.inputflinger.InputDispatcher.cpp
//InputDispatcher本身是一个线程,里面有一个循环队列Looper,添加事件后就开始处理,否则就阻塞。
void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
bool needWake;
//又将NotifyKeyArgs变成KeyEntry类型,并添加队列末尾中。
KeyEntry* newEntry = new KeyEntry(args->eventTime,
args->deviceId, args->source, policyFlags,
args->action, flags, keyCode, args->scanCode,
metaState, repeatCount, args->downTime);
needWake = enqueueInboundEventLocked(newEntry);
//如果当前循环处于阻塞状态,则需要重新唤醒。
if (needWake) {
mLooper->wake();
}
}
//线程循环,调用dispatchOnce函数,之后又会根据事件类型走不同的分发路径,按键事件对应dispatchKeyLocked函数
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
//1. 处理重复按键。重复按键行为可能是硬件产生的,也可能是软件模拟的。
//如果重复按键2次,则标识为长按事件
//2. 上次的拦截策略告诉我们要稍后重试
//3. 让拦截策略有机会拦截按键事件。这个拦截策略是由Java层的PhoneWindowManager interceptKeyBeforeDispatching方法实现的,返回0则继续分发,返回-1则停止分发。
//让系统可以处理一些特殊的按键事件。例如Home按键、音量键、截屏键等
//4.继续分发,找到当前获取焦点的窗口,然后分发给他。
Vector<InputTarget> inputTargets;//inputTargets是个输出参数
int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
entry, inputTargets, nextWakeupTime);
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
其实InputDispatcher中是持有当前焦点窗口引用的mFocusedWindowHandle,findFocusedWindowTargetsLocked函数只是判断了前端的窗口是否符合事件分发条件,例如是否正在启动、是否处于暂停状态、是否有权限限制。
那么InputDispatcher是怎么知道当前焦点窗口的呢?
答案是通过InputMonitor与WindowManagerService建立起联系,从而获得窗口信息,或者将按键事件反馈给WindowManagerService。
其实WindowManagerService中就包含了InputManagerService对象,为什么要再通过InputMonitor转发一次呢?
因为在把当前焦点变更的窗口告知InputManagerService的时候,有一些逻辑操作,为了避免将这些逻辑耦合到WindowManagerService中,所以由InputMonitor处理。
//WindowManagerService.java
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
final InputManagerService mInputManager;
final InputMonitor mInputMonitor = new InputMonitor(this);
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
...
//添加窗口时,通知InputMonitor窗口变更
if (focusChanged) {
mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
}
mInputMonitor.updateInputWindowsLw(false /*force*/);
}
}
//InputMonitor.java
//将缓存的窗口信息提供给事件输入分发器
public void updateInputWindowsLw(boolean force) {
// Send windows to native code.
//又交给InputManagerService去更新,最终会传递给InputDispatcher
mService.mInputManager.setInputWindows(mInputWindowHandles);
}
好了,到现在InputDispatcher知道该把事件分发给哪个Window了,因为InputManagerService和应用窗口不在同一个进程,那么是怎么把按键消息传递过去的呢?
答案是基于虚拟文件系统的Socket。
//InputTransport.cpp
//通过Unix Domain Socket实现两个进程的通信
status_t InputChannel::openInputChannelPair(const String8& name,
sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
int sockets[2];
//socketpair函数创建Socket的两个端,sockets就是两个文件描述符
//通过socketpair调用得到两个文件句柄,假设这两个文件句柄是fd1和fd2,这两个文件都对应有两个
//缓冲区(send_buf、rcv_buf),当某个进程或线程通过fd1写到他的send_buf的时候,内核里面的
//socket就会把send_buf里面的数据写到fd2的rcv_buf里面,另外一个线程或进程就可以读取fd2得
//到那些数据了,相反同理。
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
status_t result = -errno;
ALOGE("channel '%s' ~ Could not create socket pair. errno=%d",
name.string(), errno);
outServerChannel.clear();
outClientChannel.clear();
return result;
}
//设置Socket的读写缓冲区
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));
//分别为Socket的两端添加后缀标识,(server)和(client)
String8 serverChannelName = name;
serverChannelName.append(" (server)");
outServerChannel = new InputChannel(serverChannelName, sockets[0]);
String8 clientChannelName = name;
clientChannelName.append(" (client)");
outClientChannel = new InputChannel(clientChannelName, sockets[1]);
return OK;
}
InputChannel的创建是在应用进程请求WindowManagerService添加窗口时触发的。Socket的两个文件创建好后,会将其中一个返回给应用进程,应用进程通过监听这个文件描述符就可以接收到InputDispatcher发送过来的消息了。
创建好Socket之后,又将其封装成InputChannel。之后又将InputChannel注册到InputDispatcher中。
//WindowState.java
void openInputChannel(InputChannel outInputChannel) {
if (mInputChannel != null) {
throw new IllegalStateException("Window already has an input channel.");
}
String name = getName();
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
mInputWindowHandle.inputChannel = inputChannels[0];
if (outInputChannel != null) {
mClientChannel.transferTo(outInputChannel);
mClientChannel.dispose();
mClientChannel = null;
} else {
mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
}
//注册到InputDispatcher中
mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
}
//InputDispatcher.cpp
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
{ // acquire lock
AutoMutex _l(mLock);
//已经注册过
if (getConnectionIndexLocked(inputChannel) >= 0) {
ALOGW("Attempted to register already registered input channel '%s'",
inputChannel->getName().string());
return BAD_VALUE;
}
//构造成一个Connection类型
sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
int fd = inputChannel->getFd();
//以文件描述符为键,添加到KeyedVector集合中
mConnectionsByFd.add(fd, connection);
if (monitor) {
mMonitoringChannels.push(inputChannel);
}
//添加输入事件回调函数
mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
} // release lock
// 有新的客户端连接也要唤醒线程循环
mLooper->wake();
return OK;
}
好,注册完之后再回到dispatchEventLocked中。
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
//inputTargets是找到的拥有焦点的窗口,从mConnectionsByFd集合中找到对应的Connection就可以发送事件了。
for (size_t i = 0; i < inputTargets.size(); i++) {
const InputTarget& inputTarget = inputTargets.itemAt(i);
ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
if (connectionIndex >= 0) {
sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
} else {
}
}
}
//InputDispatcher.cpp
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection) {
//循环发送事件
while (connection->status == Connection::STATUS_NORMAL
&& !connection->outboundQueue.isEmpty()) {
DispatchEntry* dispatchEntry = connection->outboundQueue.head;
dispatchEntry->deliveryTime = currentTime;
status_t status;
EventEntry* eventEntry = dispatchEntry->eventEntry;
switch (eventEntry->type) {
case EventEntry::TYPE_KEY: {
KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
// 分发按键事件
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;
}
...
}
}
//InputTransport.cpp
//将按键信息拼装成InputMessage交由Socket发送
tatus_t InputPublisher::publishKeyEvent(
uint32_t seq,
int32_t deviceId,
int32_t source,
int32_t action,
int32_t flags,
int32_t keyCode,
int32_t scanCode,
int32_t metaState,
int32_t repeatCount,
nsecs_t downTime,
nsecs_t eventTime) {
if (!seq) {
ALOGE("Attempted to publish a key event with sequence number 0.");
return BAD_VALUE;
}
InputMessage msg;
msg.header.type = InputMessage::TYPE_KEY;
msg.body.key.seq = seq;
msg.body.key.deviceId = deviceId;
msg.body.key.source = source;
msg.body.key.action = action;
msg.body.key.flags = flags;
msg.body.key.keyCode = keyCode;
msg.body.key.scanCode = scanCode;
msg.body.key.metaState = metaState;
msg.body.key.repeatCount = repeatCount;
msg.body.key.downTime = downTime;
msg.body.key.eventTime = eventTime;
//调用InputChannel的sendMessage函数发送InputMessage
return mChannel->sendMessage(&msg);
}
status_t InputChannel::sendMessage(const InputMessage* msg) {
onst size_t msgLength = msg->size();
InputMessage cleanMsg;
//复制一份消息
msg->getSanitizedCopy(&cleanMsg);
ssize_t nWrite;
do {
//调用Socket的read函数发送消息
nWrite = ::send(mFd, &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
} while (nWrite == -1 && errno == EINTR);
return OK;
}
InputDispatcher通过Socket发送按键事件,应用进程是如何接收的呢?
InputChannel的创建时在setView方法中触发的,创建成功后会同时创建一个事件队列和一个窗口输入事件接口器,前者用来存储InputDispatcher发送过来的事件,后者用来实现Socket通信。
//ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
//声明一个未初始化的InputChannel
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
//通过WindowManagerService初始化InputChannel,mInputChannel是一个输出参数
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout,
mInputChannel);
//根据InputChannel生成事件接收器
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
//事件接收器
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
}
应用进程是怎么获取到输入事件的呢?
前面说到InputDispatcher通过socketpair创建了两个文件描述符,并把其中一个返回给了应用进程。应用进程则使用epoll函数等待这个文件描述符发生写入事件,从而实现进程间通信。
当应用进程处理完一个输入事件后,还要告诉InputDispatcher处理完成了,因为InputDispatcher还需要计算输入事件的处理时间,判断是否产生ANR。
WindowInputEventReceiver继承自InputEventReceiver,它的构造函数如下:
//InputEventReceiver.java
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
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();
//调用nativeInit方法,初始化Native层的NativeInputEventReceiver类
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
mCloseGuard.open("dispose");
}
//android_view_InputEventReceiver.cpp
//初始化InputEventReceiver
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
jobject inputChannelObj, jobject messageQueueObj) {
sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
inputChannelObj);
if (inputChannel == NULL) {
jniThrowRuntimeException(env, "InputChannel is not initialized.");
return 0;
}
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
return 0;
}
sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
receiverWeak, inputChannel, messageQueue);
//新建NativeInputEventReceiver实例,并调用initialize函数
status_t status = receiver->initialize();
if (status) {
String8 message;
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<jlong>(receiver.get());
}
//初始话函数
status_t NativeInputEventReceiver::initialize() {
setFdEvents(ALOOPER_EVENT_INPUT);
return OK;
}
//将SocketPair生成一个文件描述符添加到Looper的监听中
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
int fd = mInputConsumer.getChannel()->getFd();
if (events) {
mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
} else {
mMessageQueue->getLooper()->removeFd(fd);
}
}
}
//Looper.cpp
//Looper内部是使用epoll系统调用实现文件描述符监听的
int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
Request request;
request.fd = fd;
request.ident = ident;
request.events = events;
request.seq = mNextRequestSeq++;
request.callback = callback;
request.data = data;
struct epoll_event eventItem;
request.initEventItem(&eventItem);
epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
return 1;
}
事件处理
前面说到在ViewRootImpl中的setView(View, LayoutParams, View)方法中创建了消息接收器WindowInputEventReceiver。那么当消息通过Socket管道传到应用进程中后,会回调java层的dispatchInputEvent(int seq, InputEvent event)方法。该方法位于WindowInputEventReceiver的父类InputEventReceiver中。
// 由Native层回调到Java层的该方法
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
mSeqMap是一个SparseIntArray类的数组,用于存储事件分发器序号与事件序号的映射,当事件处理完成后,回调给Service进程时,要把事件分发器序号req传递回去。
然后调用了onInputEvent(Event)方法,该方法默认实现是通知Service进程该事件处理完成,子类(WindowInputEventReceiver)可以重写该方法实现自己的处理逻辑。
// android/view/ViewRootImpl.java
final class WindowInputEventReceiver extends InputEventReceiver {
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
}
从方法名上可以看出,直接把输入事件添加到了一个队列中。实际上也是这样的,不过是将InputEvent封装成了QueuedInputEvent对象,enqueueInputEvent方法的最后一个参数代表是否立即处理输入事件,这里为true,表示要立即处理。那什么时候不用立即处理呢?下面具体罗列这两种处理方式的场景:
- 立即处理: 屏幕触摸事件、键盘输入事件、WebView未处理的或被原生App拦截的按键事件
- 稍后处理: 游戏杆、轨迹球、
KeyEvent和MotionEvent的区别?
KeyEvent代表按键事件,包含了软键盘、导航栏、硬件按钮点击事件
MotionEvent代表运动事件,包含屏幕点击、滑动、抬起、取消等事件。
开始分发事件
// android/view/ViewRootImpl.java
private void deliverInputEvent(QueuedInputEvent q) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (q.mEvent instanceof KeyEvent) {
mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
}
if (stage != null) {
handleWindowFocusChanged();
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
责任链模式
NativePreImeInputStage -> ViewPreImeInputStage -> ImeInputStage -> EarlyPostImeInputStage -> NativePostImeInputStage -> ViewPostImeInputStatge ->
SyntheticInputStage
上述的输入事件的处理策略,每个策略只负责自己能处理的事件onProcess,对于处理不了事件,forward交给下一个策略去处理。对于自己能处理的事件,处理完成后会将事件标记为已处理,然后逐一透传到后面的策略中,最后一个策略会执行事件处理完成的方法finishInputEvent(QueuedInputEvent)。
下面逐一说明这些策略的用途:
NativePreImeInputStage
键盘输入事件预处理策略。将按键事件交给Native Activity去处理,Native Activity是一种由C/C++来开发的方式,适用于游戏开发。
ViewPreImeInputStage
视图的输入法事件预处理策略。该策略会回调View类的onKeyPreIme方法,让View有机会在输入法之前处理按键事件。一个典型场景是,应用可以处理物理返回键的点击事件,来更新UI,从而避免让输入法接收到该事件后隐藏自己。
ImeInputStage
将按键事件交给输入法去处理。
EarlyPostImeInputStage
在输入法处理事件前执行的一些操作,例如屏幕有焦点的视图会高亮显示,显示输入光标等。
NativePostImeInputStage
输入法处理事件完成后,将事件传递给Native Activity。
ViewPostImeInputStatge
输入法处理按键事件后,将事件传递给View。
final class ViewPostImeInputStage extends InputStage {
//这里区分不同的事件类型:
//KeyEvent 按键事件
//MotionEvent 运动事件
//MotionEvent又分为屏幕触摸、轨迹球触摸、通用运动事件(其中悬停事件单独处理)
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
}
SyntheticInputStage
将前面未处理的事件,重新整合成新事件处理。例如轨迹球、摇杆、虚拟导航按键。
View触摸事件分发
ViewRootImpl中保存了视图树的根视图DecorView,所以会将输入事件交给DecorView去分发。分发顺序如下:
- Window.Callback
由当前窗口处理。
当Activity或Dialog初始化时,会为当前窗口PhoneWindow添加事件处理回调。在DecorView分发事件会优先分发的Window.Callback中。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
- Dialog/Activity dispatchXXXEvent(InputEvent)
Dialog或Activity会实现Window.Callback,所以输入事件会传递到Dialog/Activity的事件处理方法中。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
为当前Activity单独响应ACTION_DOWN按下事件,回调方法为Activity的onUserInteraction()。
- 分发给ViewGroup的dispatchTouchEvent(InputEvent)方法
输入事件传递给Activity后,又会通过所关联的窗口Widow,将事件分发给DecorView的父类ViewGroup。到这里也就进入了我们常说的事件分发流程中。
//PhoneWidnow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
//DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
//ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
}
- Activity的onTouchEvent(InputEvent)
如果在上一步中,输入事件没有被任何一个视图处理,那么将会分发到Activity的onTouchEvent(InputEvent)方法中。
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
一般情况下,输入事件是不会传递到Activity的。使用场景是,当Activity持有的Window比较小,输入事件的位置落在了Window外面,这时就会把输入事件传递Activity了。
那么什么时候Window会比较小呢?
显然是在使用Dialog的时候。
我们知道Dialog也是一个单独的窗口,并且在创建Dialog时可以自定义它的Window尺寸。我们常用的CloseOnTouchOutside功能就是在Activity的onTouchEvent方法中实现的。
触摸事件在视图树中的分发逻辑
输入事件的一致性验证
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
根据安全策略过滤触摸事件
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//noinspection RedundantIfStatement
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window is obscured, drop this touch.
return false;
}
return true;
}
如果当前窗口正处于模糊阶段,如执行转场动画时,不响应触摸事件,将当前事件丢弃。
为ACTION_DOWN事件做初始化操作
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
当检测到ACTION_DOWN事件时,说明需要重新计算手势,此时就要把之前存储的状态清除掉。也就是向TouchTarget链表节点逐一发送Cancel事件,然后清空TouchTarget链表。
判断是否需要拦截当前事件
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
ViewGroup可以通过onInterceptTouchEvent方法拦截当前触摸事件,不分发给子视图。
我们也可以在创建布局时为ViewGroup指定FLAG_DISALLOW_INTERCEPT标识,让当前ViewGroup不拦截任何触摸事件。
设置方式如下:
ViewGroup.requestDisallowInterceptTouchEvent(true);
如果没有设置FLAG_DISALLOW_INTERCEPT,则执行默认的onInterceptTouchEvent(TouchEvent)方法。该方法会拦截对于滚动条的操作。
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
如果onInterceptTouchEvent(Event)方法返回true,触摸事件就不会分发给子视图,而是调用ViewGroup父类View的onTouchEvent(Event)方法来消耗当前触摸事件。
ViewGroup不拦截事件,分发给子视图
ViewGroup在寻找响应触摸事件的子视图时,需要每次都遍历所有子视图么?
答案当然不是,只有在ACTION_DOWN或ACTION_POINT_DOWN的时候才会遍历一次,找到对应的子视图后,会封装成TouchTarget节点,保存在mFirstTouchTarget链表中。后续的ACTION_MOIVE、ACTION_UP事件的分发,只需要遍历mFirstTouchTarget链接即可。
if (!canceled && !intercepted) {
//在不是取消事件,并且不拦截事件,并且是ACTION_DOWN的情况下,遍历所有子视图
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
//获取触摸事件的坐标
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//判断子视图是否可以接收触摸事件,并且触摸点落在子视图的范围内
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//如果满足上述条件,就从TouchTarget链表中找到对应的TouchTarget
//一般情况下第一个ACTION_DOWN事件的发生,TouchTarget链表为空,这里肯定找不到
//但是在多点触控情况,第二根手指也按在了相同的子视图上,那么会发生ACTION_POINT_DOWN事件,此时就可以找到对应的TouchTarget了
newTouchTarget = getTouchTarget(child);
//如果newTouchTarget不为空,说明第二根手指触摸到了屏幕,
//这时就需要把新的触摸点id添加到newTouchTarget中,然后结束遍历
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//如果newTouchTarget为空,那么就是第一次的ACTION_DOWN事件
//这时就要把ACTION_DOWN事件分发给子视图,
//如果子视图消费了该事件,就在TouchTarget链接中添加节点
if (dispatchTransformedTouchEvent(ev, false, child,
idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
//如果当前ViewGroup不包含子视图,或者没有任何一个子视图消费这个触摸事件
//那么就尝试将事件分发给TouchTarget链接中的最后一个节点,也就是最早加入链表的子视图
//在多点触控情况下,有可能这个事件是它想要的。
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
}
ViewGroup拦截事件自己处理
当ViewGroup想要拦截事件后,就会执行下面逻辑。
// 1. 在事件被拦截后,TouchTarget链表为空,所以直接将事件交给了ViewGroup的onTouchEvent方法去处理
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//2. 如果事件没有被拦截,并且不是ACTION_DOWN事件条件下,
//就会遍历TouchTarget链表,将事件分发给TouchTarget对应的子视图
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//在发生ACTION_DOWN事件,寻找合适子视图时,已经分发过ACTION_DOWN事件了,
//所以此处不必在分发一下
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//如果取消了某个子视图,则将该子视图从TouchTarget链表中移除。
//那么什么时候cancelChild为true呢?
//1、intercepted为true,也就是ViewGroup拦截了除ACTION_DOWN事件外的其他事件
//2、resetCancelNextUpFlag(target.child)为true,也就是这个子视图从视图树中被移除了,不需要再响应触摸事件了
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
将事件分发给子视图或自己
ViewGroup将触摸事件分发给自己还是子视图,都是通过dispatchTransformedTouchEvent实现的。
对于将要分发的事件,要针对多点触控的场景做一个事拆分处理。
怎么判断是否是多点触控呢?
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;//当前事件是否被消费的标识
...
//如果child为null,说明ViewGroup需要将事件分发自己,也就是调用父类View的
// dispatchTouchEvent方法
//如果child不为null,就要把事件分发给子视图了
//无论分发给父类,还是子视图,都是执行View类dispatchTouchEvent方法的默认实现
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
}
根据上面的方法名称可以看出,ViewGroup并不是将原始的MotionEvent分发出来,而是做了一些转换操作。
那么为什么要做转换?
- 在ViewGroup内有两个子View A和B,一个手指点击在View A上,此时第二个手指点击View B,当前ViewGroup会收到ACTION_POINTER_DOWN类型的事件,但是对于View B来说这是一个全新的事件序列,所以需要把ACTION_POINTER_DOWN转换为ACTION_DOWN,再分发给View B。
- 和1相同的场景,View B收到的是ACTION_DOWN,而View A也会收到一个ACTION_MOVE类型的事件。也就是说尽管两个手指没有点击在同一个View上,手指在View B上的操作均会给View A分发ACTION_MOVE事件
- 第三种转换是对MotionEvent的坐标转换,如果ViewGroup中包含的内容发生了滚动,要修正MotionEvent的坐标后再分发给子视图。
View类的dispatchTouchEvent(Motion)方法实现
前面说到在ViewGroup的事件分发流程中,最终都会调用到View的dispatchTouchEvent(TouchEvent)方法,无论是分发给子视图,还是自己处理。下面我们分析一下该方法的默认实现。
//View.java
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if ((mViewFlags & ENABLED_MASK) == ENABLED
&& handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
return result;
}
- 先判断是否是拖动滚动条,如果是,则代表由当前View消费该事件。
- 判断是够为View设置了OnTouchListener回调接口,如果设置了,并且该接口的方法onTouch返回了true,则代表由该回调消费了该事件。
- 如果前面两个步骤中消费了触摸事假,则不再将事件分发到View的onTouchEvent(TouchEvent)方法中,否则调用onTouchEvent(TouchEvent)方法
View类的onTouchEvent(TouchEvent)默认实现
View类的onTouchEvent(MotionEvent)默认实现了以下几个功能:
- 按照系统声音设置,播放点击音效
- 响应为View设置的OnClickListener事件
- 响应辅助功能的点击事件
- 修改当前视图的按下状态
- 在条件允许的情况下,获取焦点
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//对于被禁用的可点击视图,仍会消费触摸事件,只是不会产生点击事件的响应
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
//这里有个触摸事件的代理,也就是通过TouchDelegate将当前视图的触摸事件,转移到另外一个视图上
//SearchView中有使用TouchDelegate的示例。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//在当前视图处于可点击的状态,才消费触摸事件,否则返回false
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
...
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
总结
整体流程
点击屏幕
InputManagerService的Read线程捕获事件,预处理后发送给Dispatcher线程
Dispatcher找到目标窗口
通过Socket将事件发送到目标窗口
APP端被唤醒
找到目标窗口处理事件
事件在App端的分发策略
事件传递优先级:
Activity.dispatchTouchEvent(MotionEvent) ->
PhoneWindow.superDispatchTouchEvent(MotionEvent) ->
DecorView.superDispatchTouchEvent(MotionEvent) ->
ViewGroup.dispatchTouchEvent(MotionEvent) ->
ViewGroup.onInterceptTouchEvent(MotionEvent) ->
View.dispatchTouchEvent(MotionEvent) ->
View.OnTouchListener.onTouch(MotionEvent) ->
View.onTouchEvent(MotionEvent) ->
View.OnClickListenaer.onClick(View)->
Activity.onTouchEvent(MotionEvent)