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的主要内容,如果未能解决你的问题,请参考以下文章

现在到哪获取android系统源代码?

Android 系统搜索框 如何限制输入字数长度?

Android.Settings类&设置默认输入法

Android.Settings类&设置默认输入法

Android系统输入事件分发详解

Android系统输入事件分发详解