Android 2.3 input输入事件处理

经验总结---Android事件分发机制及设计思路 Android系统中将输入事件定义为InputEvent,而InputEvent根据输入事件的类型又分为了KeyEvent和// 输入事件的基类复制代码KeyEvent对应了键盘的输入事件,那么什么是?顾名思义,就是移动事件,鼠标、笔、手指、轨迹球等相关输入设备的事件都属于,本文我们简单地将其视为屏幕触摸事件。用户的输入种类繁多,由此可见,Android输入系统的设计中,将输入事件抽象为InputEvent是有必要的。 阅读详情

linux内核提供了一个Input子系统来实现的,Input子系统会在/dev/input/路径下创建我们硬件输入设备的节点,一般情况下在我们的手机中这些节点是以eventXX来命名的,如event0,event1等等,可以利用EVIOCGNAME获取此事件结点名称。这就是android中对于input事件处理数据的来源点,至于驱动写入数据这块就不说了。

首先,简而言之的介绍一下android事件传递的流程,按键,触屏等事件是经由WindowManagerService获取,并通过共享内存和管道的方式传递给ViewRoot,ViewRoot再dispatch给Application的View。当有事件从硬件设备输入时,system_server端在检测到事件发生时,通过管道(pipe)通知ViewRoot事件发生,此时ViewRoot再去的内存中读取这个事件信息。下面以一个模块划分图了解一下整个过程:



下面详细介绍一个各个模块主要处理流程:

1、建立通读通道初始化:

A、WindowManagerService与ViewRoot建立管道初始化

WindowManagerService : 主要负责事件传递,运行于system_server中,主要利用inputmanager启动input事件启动线程

读取event数据

WindowManagerService--->ViewRoot方向的管道通信,表示WMS通知ViewRoot有新事件被写入到共享内存;

     ViewRoot-->WindowManagerService方向的管道通信,表示ViewRoot已经消化完共享内存中的新事件,特此通知WMS。

     ViewRoot和WindowManagerService的管道的文件描述符都是被存储在一个名为InputChannel的类中,这个InputChannel类是

管道通信的载体。而这两者间通过ashmem_create_region创建匿名内存进行数据的传递 。

ViewRoot.java 端建立管道:

mInputChannel = new InputChannel();
                try {
                    res = sWindowSession.add(mWindow, mWindowAttributes,
                            getHostVisibility(), mAttachInfo.mContentInsets,
                            mInputChannel);
                } catch (RemoteException e)

WindowManagerService.java 建立管道:

if (outInputChannel != null) {
                String name = win.makeInputChannelName();
                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
                win.mInputChannel = inputChannels[0];
                inputChannels[1].transferToBinderOutParameter(outInputChannel);
                
                mInputManager.registerInputChannel(win.mInputChannel);
            }

创建一对InputChannel,这一对InputChannel中实现了一组全双工管道及申请共享内存,其中outInputChannel为ViewRoot传递来的

InputChannel对象,在addWindow中对其进行赋值。如此两者利用pipe建立控制通道,利用共享内存建立数据通道。

这是涉及到的文件如下:

frameworks/base/service/java/com/android/server/WindowManagerService.java

frameworks/base/core/java/android/view/ViewRoot.java

--> jni android_view_InputChannel.cpp

--> InputTransport.cpp

B、InputChannel的注册过程

一个管道通信只是对应一个Activity的事件处理,也就是当前系统中有多少个Activity就会有多少个全双工管道,那么系统需要一个管理者来管理以及调度每一个管道通信,因此我们在创建完InputChannel对象后,需要将其注册到这个InputManager管理中。

采用Looper来轮询是否有事件发生,InputManager启动了2个进程来管理事件发生与传递,InputReaderThread和InputDispatcherThread,InputReaderThread进程负责轮询事件发生; InputDispatcherThread负责dispatch事件。


2、数据处理流程:

Eventhub 从设备/dev/input/eventX中读取数据:

bool EventHub::getEvent(RawEvent* outEvent)
{

int pollResult = poll(mFDs, mFDCount, -1);

if (pfd.revents & POLLIN) {
                int32_t readSize = read(pfd.fd, mInputBufferData,
                        sizeof(struct input_event) * INPUT_BUFFER_SIZE);

...

}


InputReader 根据mapper处理不同的数据,这里有SwitchInputMapper、KeyboardInputMapper,TrackballInputMapper,TouchInputMapper,MouseInputMapper 等处理mapper过滤数据

void InputReader::loopOnce() {
    
RawEvent rawEvent;
    
mEventHub->getEvent(& rawEvent);

    process(& rawEvent);
}

void InputReader::process(const RawEvent* rawEvent) {

    switch (rawEvent->type) {
    case EventHubInterface::DEVICE_ADDED:
        addDevice(rawEvent->deviceId);
        break;


    case EventHubInterface::DEVICE_REMOVED:
        removeDevice(rawEvent->deviceId);
        break;


    case EventHubInterface::FINISHED_DEVICE_SCAN:
        handleConfigurationChanged(rawEvent->when);
        break;


    default:
        consumeEvent(rawEvent);
        break;
    }
}

分别通过:

    virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
            uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
            int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0;
    virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
            uint32_t policyFlags, int32_t action, int32_t flags,
            int32_t metaState, int32_t edgeFlags,
            uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
            float xPrecision, float yPrecision, nsecs_t downTime) = 0;
    virtual void notifySwitch(nsecs_t when,
            int32_t switchCode, int32_t switchValue, uint32_t policyFlags) = 0;

将数据回调给inputDisplatcher模块

 

 InputDispatcher 对于接收到的数据进行分发:

InputDispatcherThread线程的轮询过程dispatchOnce()-->dispatchOnceInnerLocked(), InputDispatcherThread线程不停的执行该操作,以达到轮询的目的。

处理进行分发给不同的应用
bool InputDispatcherThread::threadLoop() {
dispatchOnceInnerLocked{
case EventEntry::TYPE_KEY:
dispatchKeyLocked(); --> dispatchEventToCurrentInputTargetsLocked
--> prepareDispatchCycleLocked
case EventEntry::TYPE_MOTION:
dispatchMotionLocked();
}
}

dispatchKeyLocked函数,它接下来就调用dispatchEventToCurrentInputTargetsLocked来进一步处理了。把当前需要接受键盘事件的Activity窗口添加到mCurrentInputTargets中去了,因此,这里就分别把它们取出来,然后调用prepareDispatchCycleLocked函数把键盘事件分发给它们处理。

inputDispatcherThread处理流程:

inputDispatcherThread的主要操作是分两块同时进行的,

    一部分是对InputReader传递过来的事件进行dispatch前处理,比如确定focus window,特殊按键处理如HOME/ENDCALL等,在预处理完成 后,InputDispatcher会将事件存储到对应的focus window的outBoundQueue,这个outBoundQueue队列是InputDispatcher::Connection的成员函数,因此它是和ViewRoot相关的。

    一部分是对looper的轮询,这个轮询过程是检查NativeInputQueue是否处理完成上一个事件,如果NativeInputQueue处理完成事件,它就会向通过管道向InputDispatcher发送消息指示consume完成,只有NativeInputQueue consume完成一个事件,InputDispatcher才会向共享内存写入另一个事件。


这里主要用到了两个Queue队列:

Queue<DispatchEntry> outboundQueue; 利用handleReceiveCallback 处理收到的数据

利用inputPublisher中的publishKeyEvent及publishMotionEvent将event写入到共享内存中。


Queue<EventEntry> mInboundQueue; 处理notify回调来的数据


针对客户端数据处理逻辑:
InputConsumer 用于消费数据 (InputChannel.cpp中),其中核心的函数是:
status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** outEvent) {
    switch (mSharedMessage->type) {
    case AINPUT_EVENT_TYPE_KEY: {
        KeyEvent* keyEvent = factory->createKeyEvent();
        if (! keyEvent) return NO_MEMORY;


        populateKeyEvent(keyEvent);


        *outEvent = keyEvent;
        break;
    }


    case AINPUT_EVENT_TYPE_MOTION: {
        MotionEvent* motionEvent = factory->createMotionEvent();
        if (! motionEvent) return NO_MEMORY;


        populateMotionEvent(motionEvent);


        *outEvent = motionEvent;
        break;
    }
    ...
}


JNI 函数处理数据: android_view_InputQueue.cpp
int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) {
        /* 先收到信号 */
        status_t status = connection->inputConsumer.receiveDispatchSignal();
      
        /* 再处理数据 */
        status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent);

        /* 转换成java对象native数据 */

        android_view_KeyEvent_fromNative 及 android_view_MotionEvent_fromNative      

        /*  回调数据给Java层*/

env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,

            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
            jlong(finishedToken));

}


对于最后C++调用Java的方法说明一下:

首先注册两个方法:

env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
            jlong(finishedToken));

    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchKeyEvent, gInputQueueClassInfo.clazz,
            "dispatchKeyEvent",
            "(Landroid/view/InputHandler;Landroid/view/KeyEvent;J)V");


    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz,
            "dispatchMotionEvent",
            "(Landroid/view/InputHandler;Landroid/view/MotionEvent;J)V");


然后在InputQueue.java中有这两个方法的定义:

env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
            jlong(finishedToken));

    private static void dispatchKeyEvent(InputHandler inputHandler,
            KeyEvent event, long finishedToken) {
        Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
        inputHandler.handleKey(event, finishedCallback);
    }


    @SuppressWarnings("unused")
    private static void dispatchMotionEvent(InputHandler inputHandler,
            MotionEvent event, long finishedToken) {
        Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
        inputHandler.handleMotion(event, finishedCallback);
    }


env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
            jlong(finishedToken));


handleKey最后由InputHandler 处理keyevent及motionevent事件


客户端数据回调机制:

对于每个客户端注册了一个InputChannel 

status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj,

添加notify回调函数

        looper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); 

 

env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
            jlong(finishedToken));
Android Input的流程和原理 当输入设备可用时,Linux内核会在/dev/input/下创建对应名为event0~n或其他名称的设备节点。而当输入设备不可用时,则会将对应的节点删除。Android输入系统的工作原理概括来说,就是监控/dev/input/下的所有设备节点,当某个节点有数据可读时,将数据读出并进行一系列的翻译加工,然后再所有的窗户中寻找合适的事件接收者,并派发给它。 阅读详情

相关推荐

H5,JS仿微信输入法,键盘上面带input输入框,兼容安卓、ios

H5,JS仿微信输入法,键盘上面带input输入框

三俩句的博客 2190

android input命令模拟事件以及事件注入实现

在坐标(500,100)的位置模拟点击事件参数解析:int inputSource:表示模拟事件的类型float x, float y:表示事件坐标点int displayId:在哪个屏幕上触发事件(一般用于多屏)

yimelancholy的博客 2918

Android Input子系统 - 触控事件处理机制

最后借用业界大牛做的一张图来描述Android系统触控事件处理机制的整体全貌作为总结。

Jimmy的专栏 1995

【Framework】Android Input系统事件分发分析

你知道Android是怎么分发点击事件吗?

to_perfect的博客 4410

【android 9】【input】【2.结构体含义】

由于后续篇幅如input启动分析,增删设备事件分析,发送key按键分析中存在大量的结构体,包含了各种信息,为了更好的了解和读懂,此篇,将对大量的结构体和其参数的含义作出解释。

handsomethefirst的博客 1909

Android 输入事件调试与模拟:从 getevent 到 input 命令的实战解析

本文深入解析Android输入事件调试与模拟的实战技巧,重点介绍getevent和input命令的使用方法。通过详细案例演示如何监听原始输入事件、解析数据结构、使用dumpsys分析输入系统,以及模拟各种输入操作,帮助开发者高效解决输入事件相关问题,提升Android应用调试效率。

weixin_30387423的博客 438

Android Input系统1 输入系统概述

一 概述 当用户触摸屏幕或者按键操作,首次触发的是硬件驱动,驱动收到事件后,将该相应事件写入到输入设备节点, 这便产生了最原始的内核事件。接着,输入系统取出原始的输入事件,经过层层封装后成为 KeyEvent 或者 MotionEvent,最后交付给相应的目标窗口 (Window) 来消费该输入事件。可见,输入系统在整个输入事件处理过程中起到了承上启下的衔接作用。 二 Android输入事件模型 输入事件肯定要先捕获才能传给窗口,因此,首先应该有一个线程在不断的监听屏幕或其它输入端,一旦有触摸或其它输入事件

liuwg1226的专栏 5353

Android Input系统(1) Input事件的产生与传递

这篇博客介绍了Android系统中的Input系统,它是负责处理用户输入操作的核心组件。该系统从各种输入设备获取原始输入事件,并将其转换为Android应用可以理解和消费的KeyEvent或MotionEvent对象。博客详细解释了Input系统的角色和重要性,以及它的主要组成部分,包括InputReader、InputDispatcher等。还介绍了输入事件的来源,包括硬件设备和软件模拟,以及不同类型的输入事件,如按键事件和触摸事件。

我其实什么都不会 3285

js中input按钮的事件处理大全

input按钮的事件处理大全 1.<INPUT onclick=document.all.WebBrowser.ExecWB(1,1) type=button value=打开 name=Button1> 2.<INPUT onclick=document.all.WebBrowser.ExecWB(4,1) type=button value=另存为 name=Button2> 3.<INPUT onclick=document.all.WebBrows...

Kill_paradise的博客 2866

微信小程序 - 解决软键盘弹起遮挡输入框的问题,微信小程序input输入框被弹出的软键盘遮挡住一半或全部挡住了(键盘弹起时页面整体上移导致输入框被盖住挡住一部分,textarea同样可解决适用保搞定)

微信小程序防止输入框被键盘遮住,微信小程序软键盘弹起遮挡输入框的问题,微信小程序输入框拉起软键盘时,输入框显示不全,被软键盘挡住一半,弹出键盘时遮挡输入框,小程序苹果ios输入框被键盘遮挡,小程序底部input输入框,键盘弹起时页面整体上移问题解决,iOS微信小程序键盘弹出时输入框被挡住,微信小程序textarea软键盘遮挡解决方案,微信小程序底部输入框,键盘弹起时页面整体上移问题,input在底部时,会被弹出的键盘遮住,微信小程序弹出键盘遮挡住输入框,UniApp微信小程序页面输入框被弹出的键盘遮挡住,弹

🏆 前端领域优质创作者 4951

Android Input——输入子系统(三)

Android 设备可以同时连接多个输入设备,如触摸屏、键盘、鼠标等。每当用户在任何一个设备上产生输入操作时,都会触发一个中断。这些中断经过 Linux 内核的中断处理机制以及相应的设备驱动程序转换为事件(Event),并最终传递给用户空间的应用程序进行处理。同时,每个输入设备都有其特定的驱动程序,数据接口也各不相同。要在单个线程(例如 InputReader Thread)中捕获所有用户的输入,Linux 内核的输入子系统发挥了关键作用。

小旭的专栏 1493

高通安卓12-Input子系统

键值相关宏定义在:frameworks/base/core/java/android/view/KeyEvent.java。在/external/toybox/toys/android/sendevent.c)键值相关宏定义在:/usr/include/linux/input-event-codes.h。以上数据的格式为:/dev/input/event0: "事件类型" "参数位定义" "值"相关宏定义在:/usr/include/linux/input-event-codes.h。

Jun626的博客 1236

Android Input 子系统初探

在之前的内容中,我们提到了Input Device、Input Handler等名词,但还没有详细说明。本节将介绍Input子系统中的几个重要结构体,以便于后续的理解。

新程序圆 1201

Android dumpsys input详解

简介:华为P30,Android 9.0(API 28) 如果你在从事安卓的开发,如果你苦于看不懂android 的输入逻辑,如果你想找到更好Android 调试工具,那么强烈建议你看完本文。如果你那么做了,你会了解dumpsys input ,你会了解 input事件的派发逻辑,你会对Android 更感兴趣; HWELE:/ $ dumpsys input INPUT MANAGER (dumpsys input) Input Manager State: Interactive: true

qq_34262886的博客 5226

关于Selenium3在MicrosoftEdge浏览器中出现的问题

前言 最近在做web自动化测试,所用环境是java+selenium3.141.59+MicrosoftEdge(Chromium)浏览器。 在调试的过程中,出现了很多问题。比如selenium3在Edge浏览器中远程调用中,不支持addArguments方法等 问题 1.Edge浏览器弹窗问题 在测试过程中,会出现弹出窗提示 ...

zhucheng1994的博客 2188

Rstudio-server无法登陆?几种解决方法 卡死 崩溃了 卡住

Q6:Rstudio-server无法登陆?几种解决方法1 检查登陆方式是否正确 (IP/域名 端口是否弄错了)2 查看自己用户名和密码是否输入错误(大部分无法登陆的原因都是第1点和第2点)3清空浏览器cookie缓存,或者换个浏览器登陆,推荐谷歌浏览器4 如果是出现下面截图界面,试试点击Terminate R 或 Safe Mode(如果试了还不行,继续尝试后面方法5~8)5 ssh登录,然后查看自己的磁盘存储空间是否用尽(方法见Q9),如果是,先清理自己的磁盘空间。

生信小博士的博客 1万+
上一篇: Android深入浅出之Binder机制
下一篇: Sensor 代码流程分析
andyhuabing
博客等级 码龄19年 704粉丝 165原创
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值