Skip to main content
 首页 » 编程设计

Android输入系统(4)——Reader、Dispatcher、App处理流程

2022年07月19日53开发

一、两个线程启动过程

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

七、补充

InputReader 负责从 EventHub 里面把 Input 事件读取出来,然后交给 InputDispatcher 进行事件分发
InputDispatcher 在拿到 InputReader 获取的事件之后,对事件进行包装和分发 (也就是发给对应的)
OutboundQueue 里面放的是即将要被派发给对应 AppConnection 的事件
WaitQueue 里面记录的是已经派发给 AppConnection 但是 App 还在处理没有返回处理成功的事件
PendingInputEventQueue 里面记录的是 App 需要处理的 Input 事件,这里可以看到已经到了应用进程
deliverInputEvent 标识 App UI Thread 被 Input 事件唤醒
InputResponse 标识 Input 事件区域,这里可以看到** Input_Down 事件 + 若干个 Input_Move 事件 + 一个 Input_Up 事件这段都被算到了这里**
App 响应 Input 事件 : 这里是滑动然后松手,也就是我们熟悉的桌面滑动的操作,桌面随着手指的滑动更新画面,松手后触发 Fling 继续滑动,从 Systrace 就可以看到整个事件的流程

从 Systrace 来看,Input 事件的基本流向如下:

InputReader 读取 Input 事件,将读取的 Input 事件放到 InboundQueue 中
InputDispatcher 从 InboundQueue 中取出 Input 事件派发到各个 App(连接) 的 OutBoundQueue,同时将事件记录到各个 App(连接) 的 WaitQueue 
App 接收到 Input 事件,同时记录到 PaddingQueue ,然后对事件进行分发处理
App 处理完成后,回调 InputManagerService 将负责监听的 WaitQueue 中对应的 Input 移除。

InputDispatcher 的核心逻辑如下:

dispatchOnceInnerLocked(): 从 InputDispatcher 的 mInboundQueue 队列,取出事件 EventEntry。另外该方法开始执行的时间点 (currentTime) 便是后续事件 dispatchEntry 的分发时间 (deliveryTime)
dispatchKeyLocked():满足一定条件时会添加命令 doInterceptKeyBeforeDispatchingLockedInterruptible;
enqueueDispatchEntryLocked():生成事件 DispatchEntry 并加入 connection 的 outbound 队列
startDispatchCycleLocked():从 outboundQueue 中取出事件 DispatchEntry, 重新放入 connection 的 waitQueue 队列;
InputChannel.sendMessage 通过 socket 方式将消息发送给远程进程;
runCommandsLockedInterruptible():通过循环遍历地方式,依次处理 mCommandQueue 队列中的所有命令。而 mCommandQueue 队列中的命令是通过 postCommandLocked() 方式向该队列添加的。

InboundQueue

InputDispatcher 执行 notifyKey 的时候,会将 Input 事件封装后放到 InboundQueue 中,后续 InputDispatcher 循环处理 Input 事件的时候,就是从 InboundQueue 取出事件然后做处理

OutboundQueue

Outbound 意思是出站,这里的 OutboundQueue 指的是要被 App 拿去处理的事件队列,每一个 App(Connection) 都对应有一个 OutboundQueue ,从 InboundQueue 那一节的图来看,事件会先进入 InboundQueue ,然后被 InputDIspatcher 派发到各个 App 的 OutboundQueue

WaitQueue

当 InputDispatcher 将 Input 事件分发出去之后,将 DispatchEntry 从 outboundQueue 中取出来放到 WaitQueue 中,当 publish 出去的事件被处理完成(finished),InputManagerService 就会从应用中得到一个回复,此时就会取出 WaitQueue 中的事件,从 Systrace 中看就是对应 App 的 WaitQueue 减少

Input 刷新与 Vsync

Input 的刷新取决于触摸屏的采样,目前比较多的屏幕采样率是 120Hz 和 160Hz ,对应就是 8ms 采样一次或者 6.25ms 采样一次,我们来看一下其在 Systrace 上的展示

Dumpsys Input

主要是 Debug 用,我们也可以来看一下其中的一些关键信息,到时候遇到了问题也可以从这里面找。

InputDispatcher 状态:

InputDispatch 这里的重要信息主要包括:

FocusedApplication :当前获取焦点的应用

FocusedWindow : 当前获取焦点的窗口

TouchStatesByDisplay

Windows :所有的 Window

MonitoringChannels :Window 对应的 Channel

Connections :所有的连接

AppSwitch: not pending

Configuration

参考:

https://www.androidperformance.com/2019/11/04/Android-Systrace-Input/

https://zhuanlan.zhihu.com/p/29386642

http://gityuan.com/2016/12/17/input-dispatcher/

http://gityuan.com/2016/12/10/input-manager/

http://gityuan.com/2016/12/11/input-reader/


本文参考链接:https://www.cnblogs.com/hellokitty2/p/10884229.html