Android输入系统——InputStage
Posted hellokitty2
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android输入系统——InputStage相关的知识,希望对你有一定的参考价值。
一、两个线程启动过程
SystemService.java 启动 InputManagerService 服务 Service: InputManagerService.java JNI: com_android_server_input_InputManagerService.cpp InputManagerService(Context context) /*调用的第一个本地函数*/ mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue()); //InputManagerService.java initialize() //InputManager.cpp mReaderThread = new InputReaderThread(mReader); mDispatcherThread = new InputDispatcherThread(mDispatcher);
cpp实现的NativeInputManager是与Java程序对接的,它就是在JNI文件com_android_server_input_InputManagerService.cpp中创建的。
二、Reader线程操作
1. Reader线程使用EventHub读取事件
InputReader::loopOnce() // InputReader.cpp size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
2. Reader线程获取存放在RawEvent mEventBuffer[EVENT_BUFFER_SIZE];数组中,事件类型是RawEvent,它对事件类型进行了扩展,除了input_event
里面表示的type(EV_KEY...)外,其type还包括DEVICE_ADDED, DEVICE_REMOVED, FINISHED_DEVICE_SCAN。
3. 使用inotify检测/dev/input目录,使用epoll监听eventX和inotify_fd。
4. InputReader获取事件放到InboundEventQueue的过程
mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); //InputReader.cpp 获取的事件存放在RawEvent mEventBuffer数组中 InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) /*针对不同的事件类型调用对应的函数处理*/ addDeviceLocked(rawEvent->when, rawEvent->deviceId); /*DEVICE_ADDED*/ removeDeviceLocked(rawEvent->when, rawEvent->deviceId); /*DEVICE_REMOVED*/ handleConfigurationChangedLocked(rawEvent->when); /*FINISHED_DEVICE_SCAN*/ processEventsForDeviceLocked(deviceId, rawEvent, batchSize); device->process(rawEvents, count); /*对mMappers[]中的每一个InputMapper都调用其process()进行处理,process是一个纯虚函数*/ mapper->process(rawEvent); 根据createDeviceLocked(classes)参数决定将哪些InputMapper放入到mMappers[]中 addDeviceLocked uint32_t classes = mEventHub->getDeviceClasses(deviceId); //决定使用哪个或哪些InputMapper EventHub::getDeviceClasses(int32_t deviceId) //EventHub.cpp Device* device = getDeviceLocked(deviceId); return device->classes; /*这个classes最终是通过ioctl()读取支持的事件类型决定的*/ EventHub::openDeviceLocked(const char *devicePath) //EventHub.cpp InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes); mDevices.add(deviceId, device); 有如下InputMapper的实现类供选择: class SwitchInputMapper : public InputMapper class VibratorInputMapper : public InputMapper class KeyboardInputMapper : public InputMapper class CursorInputMapper : public InputMapper class TouchInputMapper : public InputMapper class JoystickInputMapper : public InputMapper 以KeyboardInputMapper为例进行分析: KeyboardInputMapper::process(const RawEvent* rawEvent) //InputReader.cpp getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags);//由内核的code获取映射的AKEYCODE processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags); NotifyKeyArgs args getListener()->notifyKey(&args); //构造一个NotifyKeyArgs类型的数据,然后上报。 InputDispatcher::notifyKey(const NotifyKeyArgs* args); //InputDispatcher.cpp mPolicy->interceptKeyBeforeQueueing(&event, policyFlags); //com_android_server_input_InputManagerService.cpp 放入队列前稍加处理 /* * 调用java程序的PhoneWindowManager.java中的同名函数interceptKeyBeforeQueueing() * 来判断是否要将事件传给App,也即是policyFlags是否或上POLICY_FLAG_TRUSTED. * 若处于交互模式下(屏亮着),普通按键需要发给User。 */ KeyEntry* newEntry = new KeyEntry(policyFlags); //根据上面函数传出的policyFlags构造一个KeyEntry enqueueInboundEventLocked(newEntry); //将KeyEntry放入mInboundQueue队列的尾部 mLooper->wake(); //有必要的话唤醒Dispatcher线程。 为什么是InputDispatcher::notifyKey: nativeInit //com_android_server_input_InputManagerService.cpp 输入子系统本地初始化的第一个函数 new NativeInputManager new InputManager(eventHub, this, this); new InputReader(eventHub, readerPolicy, mDispatcher); //mDispatcher mQueuedListener = new QueuedInputListener(listener); //InputReader.cpp
三、Dispatcher线程的处理
1. InputReader线程将事件放入到队列mInboundQueue中,Dispatcher线程将其取出,稍加处理后放入outBoundQueue队列中。
2. 稍加处理
a.分类:Golbal System User
b.处理紧急事件
对Global和System键默认是不会发给App的,Dispatcher线程处理完后直接把它给丢掉。对于要发给App的事件也要放到目标App的队列中。
无论是Global System 还是User key 只要其policy_flag是TO_USER,都会放到out队列中给APP.
3. 发给谁:
传给App的时候向WMS查询当前窗口,得到目标App的connection(WM创建的),从outBoundQueue取出数据通过connection发给App。
4. 参考:http://www.cnblogs.com/samchen2009/p/3368158.html 关注里面的Dispatcher处理流程
5.对Global key的处理
GlobalKeyManager.java中: loadGlobalKeys(Context context) context.getResources().getXml(com.android.internal.R.xml.global_keys); mKeyMapping.put(keyCode, ComponentName.unflattenFromString(componentName)); //如果以后可以从mKeyMapping中获取到,那么就是Global key 全局按键定义在global_keys.xml,格式如下: frameworks/base/core/res/res/xml/global_keys.xml <global_keys version="1"> <!-- Example format: keyCode = keycode to handle globally. component = component which will handle this key. --> <!-- <key keyCode="KEYCODE_VOLUME_UP" component="com.android.example.keys/.VolumeKeyHandler" /> --> <!-- I add it, eg: --> <key keyCode="KEYCODE_TV" component="com.android.example.xxxxxx" /> //按下这个按键会发广播给这个组件(App) </global_keys>
6.对应SystemKey分类处理
//以音量键为例查看怎么处理这些SystemKey interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) //PhoneWindowManager.java case KeyEvent.KEYCODE_VOLUME_DOWN: /*首先判断一下是否需要截屏,若是同时按下音量键和电源键就会截屏*/ interceptScreenshotChord();
7. Dispatcher线程分发数据流程
InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime); //InputDispatcher.cpp findFocusedWindowTargetsLocked(currentTime, entry, inputTargets, nextWakeupTime); //向WMS查询前台窗口 //mFocusedWindowHandle这个变量表示当前窗口 dispatchEventLocked(currentTime, entry, inputTargets); //发送出去 getConnectionIndexLocked(inputTarget.inputChannel); sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex); //根据文件句柄取出对应的connection prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget); //将输入事件放到这个connection的队列里。 enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget); enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, InputTarget::FLAG_XXX);//写入队列 connection->outboundQueue.enqueueAtTail(dispatchEntry); //dispatchEntry就表示一个输入事件,放入outboundQueue队列中。 startDispatchCycleLocked(currentTime, connection); //从队列头部取出事件放到文件句柄里面去。 DispatchEntry* dispatchEntry = connection->outboundQueue.head; connection->inputPublisher.publishKeyEvent(dispatchEntry,keyEntry); InputMessage msg; //构造一个InputMessage结构 mChannel->sendMessage(&msg); //发送 /* * ‘::‘表示这是一个全局函数,是不属于某个类的,就是send()系统调用。 * InputDispatcher执行到这里已经完成了它的使命,下一步就是应用程序怎么处理这个事件了。 * App会从socketpair fd取出表示事件的InputMessage结构。 */ nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL); //InputTransport.cpp
四、APP跟输入系统建立联系_InputChannel和Connection
1. 核心是socketpair,Dispatcher只需要将数据写入的socketpair的一端就可以了。
2. 找到App
Dispatcher线程将输入数据发给App,首先得找出App,怎么找出App呢:
系统上运行多个App,只有屏幕最前面的App可以接收到输入事件。需要由WMS告诉输入系统运行在最前台的应用程序是哪个。
对于每一个应用程序,在WMS中都有一个WindowState结构体表示它。
3. 输入系统是怎么与APP建立联系的:
InputReader 线程和 InputDispatcher 线程和 WindowManagerService 线程都处于同一个进程 SystemServer 中,因此他们三者之间可以直接
通信,而与App的通信需要借助socketpair实现进程间通信。
参考out目录下把IWindowSession.java,outInputChannel是binder传输的一个文件句柄。
openInputChannelPair(String name) //InputChannel.java nativeOpenInputChannelPair(name); android_view_InputChannel_nativeOpenInputChannelPair //android_view_InputChannel.cpp InputChannel::openInputChannelPair(name, serverChannel, clientChannel); socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets); //
五、APP程序如何获取处理事件
1. 把对应的socketpair fd放入Looper中使用epoll()监听。
2. APP中对fd(Input Channel)注册过程
APP中对fd(Input Channel)的注册过程是从new WindowInputEventReceive开始的
setView //ViewRootImpl.javas addToDisplay(mWindow, mDisplay.getDisplayId(), mInputChannel); //使用mInputChannel作为参数创建了WindowInputEventReceiver对象,派生于InputEventReceiver mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper()); //ViewRootImpl.java //InputEventReceiver类来自InputEventReceiver.java,对应的JNI文件为android_view_InputEventReceiver.cpp,成员函数: void dispatchInputEvent(int seq, InputEvent event) /*java中自动体现出多态,这调用的可能是子类的*/ onInputEvent(InputEvent event)
3.构造函数InputEventReceiver
InputEventReceiver(InputChannel inputChannel, Looper looper);//InputEventReceiver.java nativeInit(new WeakReference<InputEventReceiver>(this), inputChannel, mMessageQueue); //android_view_InputEventReceiver.cpp sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env, receiverWeak, inputChannel, messageQueue); receiver->initialize(); setFdEvents(ALOOPER_EVENT_INPUT); /*通过channel获得fd,然后把fd告诉Looper*/ int fd = mInputConsumer.getChannel()->getFd(); mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL); /*上面是framework下的文件中的函数*/ int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data)//system文件夹下的Looper.cpp request.callback = callback; //这个callback就是上面的this,就是NativeInputEventReceiver对象 epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem); //将此fd添加到epoll()中去监听 /* 分析到这里猜测:当App从队列中收到事件后会调用callback的NativeInputEventReceiver::handleEvent,它再调用 consumeEvents(env, false /*consumeBatches*/, -1, NULL);然后它会回调JNI函数,调用到InputEventReceiver.java的 dispatchInputEvent() */ NativeInputEventReceiver类中 //它会回调到InputEventReceiver.java中的dispatchInputEvent consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch); env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
4. App获取和处理这些输入事件的流程
//猜测:应用程序启动后肯定会进入一个循环,来监听事件 Looper::pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData); //system/core/libutils/Looper.cpp pollOnce(timeoutMillis, outFd, outEvents, outData); pollInner(timeoutMillis); int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); //等待事件的发生 //监听到事件后: pushResponse(events, mRequests.valueAt(requestIndex)); //调用所有的挂起的消息回调 MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0); sp<MessageHandler> handler = messageEnvelope.handler; handler->handleMessage(message); //调用所有的response回调 //由上面的注册过程可知,这里的callback就是NativeInputEventReceiver对象。 response.request.callback->handleEvent(fd, events, data); //system/core/libutils/Looper.cpp NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data);//frameworks下的android_view_InputEventReceiver.cpp consumeEvents(env, false /*consumeBatches*/, -1, NULL); //通过JNI回调到InputEventReceiver.java的dispatchInputEvent env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj); dispatchInputEvent(int seq, InputEvent event) //InputEventReceiver.java //java中自动体现出多态,这个是ViewRootImpl.java中的 onInputEvent(event); //ViewRootImpl.java enqueueInputEvent(event, this, 0, true);
处理过程恰好和注册过程的处理刚好相反。
5. 对于App来说,对输入事件的处理,只需要看ViewRootImpl.java中的 WindowInputEventReceiver 类中的 onInputEvent 函数即可。这个函
数是java应用程序处理的总入口。
onInputEvent(InputEvent event); //ViewRootImpl.java enqueueInputEvent(event, this, 0, true); doProcessInputEvents(); deliverInputEvent(q); //传输这些输入事件 /* * 判断是否需要忽略输入法,它决定开始处理的流程。 * 处理方式是确定第一个stage,然后调用其deliver() */ stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage; stage.deliver(q); /*若事件的状态是finished就传给下一个stage进行处理*/ if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) { forward(q); /*如果这个输入事件可以丢弃就调用finish()*/ } else if (shouldDropInputEvent(q)) { /*设置上FLAG_FINISHED标志然后传给下一个stage*/ finish(q, false); } else { /* * 如果这个输入事件既没有finish也不能丢弃,就需要调用其onProcess去处理。 * 之后再调用apply将事件传给下一个stage */ apply(q, onProcess(q)); }
6. 在任何一个stage中对输入事件的处理只有3个结果
a.发现标记位为FLAG_FINISHED,直接把它传递给下一个stage。
b.发现其可丢弃掉,就使其标志位或上FLAG_FINISHED,然后把它传递给下一个stage。
c.调用本stage的onProcess(q)进行处理,处理完后再传给下一个stage.
7. Android中对输入事件的处理分为好几个阶段,参见:www.cnblogs.com/samchen2009/p/3368158.html
8. 如果应用程序不使用输入法的话就从EarlyPostIme进行处理,使用输入法的话就从NativePreIme进行处理(Ime是输入法)。
9. 应用程序有可能是C++写的,若是C++写的称为NativeActivity
六、App分多个InputStage处理事件
1. 使用Java写的Activity程序主要关注输入法处理之前的ViewPreIme和输入法之后的ViewPostIme的处理。
2. 输入事件传给的控件称为输入焦点。
3. 每一个Window都有一个DecorView也有一个ViewRootImpl
4. java源代码分析使用SourceInsight进行跳转是不合适的,因为跳转是跳转到基类里面去了,调用的函数却是n级派生类的。
分析的时候更注重的是哪个类的对象!可以使用dump调用栈来辅助做UML图,因为它里面会打印出指定的类,从而不易导致混淆。
打印java调用栈的方法:
Log.d(TAG, Log.getStackTraceString(new Throwable()));
5. 如果在上一个inputStage中处理后返回为true,就不会再传给下一个InputStage进行处理了。
6. onKeyDown()使用kcm文件将一个keyCode转换成某个字符,然后将其显示出来。
7. onKeyPreIme的调用流程
ViewPreImeInputStage类: onProcess(QueuedInputEvent q) //ViewRootImpl.java processKeyEvent(q); //如果是按键类事件则调用它 mView.dispatchKeyEventPreIme(event) //ViewGroup.java 由继承多态关系是它。在输入法处理之前做的分发 mFocused.dispatchKeyEventPreIme(event); //mFocused就是焦点,也是控件. dispatchKeyEventPreIme(KeyEvent event); //View.java /* * 下面这个函数直接返回了false,因此,若是想让App在输 * 入法处理之前做一些事情的话应该重写这个函数。 */ onKeyPreIme(int keyCode, KeyEvent event) return false;
最后都是找到焦点控件,然后调用其dispatchKeyEventPreIme
以上是关于Android输入系统——InputStage的主要内容,如果未能解决你的问题,请参考以下文章