文档章节

Android4.0 input事件输入流程详解(中间层到应用层)

s
 sflfqx
发布于 2014/07/23 18:08
字数 2833
阅读 285
收藏 13

Android系统中,类似于键盘按键、触摸屏等事件是由WindowManagerService服务来管理的,然后再以消息的形式来分发给应用程序进行处理。系统启动时,窗口管理服务也会启动,该服务启动过程中,会通过系统输入管理器InputManager来负责监控键盘消息。当某一个Activity激活时,会在该Service下注册一个接收消息的通道,表明可以处理具体的消息,然后当有消息时,InputManager就会分发给当前处于激活状态下的Activity进行处理。

 

InputManager的启动过程

         InputManager负责事件的监控以及分发,而其启动需要WindowManagerService的启动来完成。而系统启动Win  dowManagerService的时候,会执行WindowManagerService.java文件的main方法。

         main方法下会创建一个线程类的实例,并执行其start方法,即调用该线程类下的run方法。run方法里面首先创建一个Looper消息循环,而looperAndroid系统里面处理消息的一种机制,然后调用WindowManagerService的构造方法来启动WindowManagerService

       最后调用Looperloop方法,将该线程添加到一个消息循环里面去,这样系统就可以持续的等待接收并处理到来的消息。

          

        WindowManagerService的构造方法里,调用InputManager的构造方法创建一个InputManager对象的实例,并将WindowManagerService对象作为参数传入构造方法里面。在InputManager的构造方法里面得到前面创建的looper对象并将其内部的消息队列作为参数传给JNI层的nativeInit函数里面。

        nativeInit函数的实现位于JNI层的com_android_server_InputManager.cpp里面。该函数下主要做两件事情。

        ◆ 通过java层传入的looper消息队列得到和JNI层的looper对象,是其对应起来

       ◆同样为使和java层的InputManager对象对应起来,创建了本地的InputManager对象实例即NativeInputManager

        在创建NativeInputManager的过程中,会创建属于JNI层真正地InputManager对象,并将创建的EventHub对象实例当做参数传入到其构造函数里面。(注意这儿传入的EventHub实例)

       Initialize函数

        InputManager构造函数里面,会分别创建InputDispatcherInputReader对象的实例并将在创建InputDispatcherThreadInputReaderThread作为参数传入。此时需要注意传入InputReader构造函数里面的eventHubmDispatcher。并且在创建InputDispatcher的时候,创建了一个属于自己的Looper实例。

        此时,InputDispatcherInputReader会通过InputDispatcherThreadInputReaderThread两个线程类来具体的完成事件的分发和读取。到此,InputManager启动完成。

 

应用程序注册键盘消息接收通道

           Activity启动时,系统会为其创建一个ViewRoot实例,并通过其函数setView方法来将有关的view设置到ViewRoot中去,而Activity正是通过setView来注册消息接收通道的。

         setView方法中会创建一个输入通道InputChannel的对象实例,并作为参数传入到接下来调用的Session类中的add方法中。

        而该方法正是调用WindowManagerService类中的addWindow方法,且将InputChannel实例作为参数传入到该方法中。

        此处调用openInputChannelPair方法来创建一个InputChannel类型的数组,而该方法的具体实现是在InputTransport.cpp内完成的。

         在该方法中,先创建一个服务端的匿名共享内存,可读可写。并将复制一份用于客户端的匿名共享内存,然后通过调用InputChanel的构造函数对服务端和客户端的通道进行实例化。

       其中服务端为反向管道读端与正向管道写端,客户端正相反。这样就可以使客户端和服务端两通道交叉连接,进行消息的传递。

        继续回到WindowManagerServiceaddWindow方法里面,当创建了两个通道后,需要将各自的通道分别注册到客户端和服务端。

       registerInputChannel方法即完成对服务端通道的注册。经过层层函数调用,最后是在InputDispatcher.cpp下的registerInputChannel函数完成具体的注册操作的。

        首先将通道封装在一个Connection对象中,然后获得该通道的读端与Connection一起保存在InputDispatcher中,然后将获得的读端加入到Looper中。

       在Looper类内部,会创建一个管道,然后Looper会睡眠在这个管道的读端,等待另外一个线程来往这个管道的写端写入新的内容,从而唤醒等待在这个管道读端的线程,除此之外,Looper还可以同时睡眠等待在其它的文件描述符上,因为它是通过Linux系统的epoll机制来批量等待指定的文件有新的内容可读的。这些其它的文件描述符就是通过Looper类的addFd成函数添加进去的了,在添加的时候,还可以指定回调函数,即当这个文件描述符所指向的文件有新的内容可读时,Looper就会调用这个hanldeReceiveCallback函数。

          在对服务端通道注册前,已经将在应用程序层创建的通道与客户端的通道关联起来。回到ViewRoot下的setView来注册客户端的通道。

        具 体的注册实现是在android_view_InputQueue.cpp的registerInputChannel来完成的,和服务端注册大致相同。 此时的looper是客户端应用层的looper。应用层创建Looper的时候,会用到一个线程局部变量,sThreadLocal.set(new  Looper()),意在保证每个线程内都有一个独立的Looper对象。因此此处的Looper.myQueue即为应用层的消息队列。       

           到此,服务端和客户端通道已注册完成。

 

 InputManager分发touch消息给应用程序的过程

           在前面的分析中,当InputManager启动完成以后,会在WindowManagerService的构造方法里面执行InputManager对象的start方法。

         最后会调用InputReaderThreadInputDispatcherThread线程类的run函数来启动两个线程。分别调用其threadLoop函数。

         InputReaderThread->threadLoop调用InputReader->loopOnce函数

         InputDispatcherThread->threadLoop调用InputDispatcher->dispatchOnce函数

         InputReader->loopOnce

         此处的mEventHub即创建InputManager对象的时候,传入的EventHub实例,而EventHub是一个手机设备的文件描述。当手机设备有事件发生时如有键盘按下时,可以通过其函数getEvents函数获得该事件的具体描述和详细信息。

         当有事件发生时,会调用processEventsLocked函数来处理,并将对事件信息的详细描述作为参数传入。

        首先得到所触发事件的类型以及触发此事件的设备Id,然后分别作为参数传入到processEventsForDeviceLocked函数中。

         processEventsForDeviceLocked

        通过deviceId来得到具体的描述当前设备的InputDevice的对象实例并调用其process函数。

         手机设备中都有一个和其相匹配的mapper,如果添加一个新的设备,同时会给该设备添加一个mapper的描述,这样当某个设备发生事件时,可以根据与其相匹配的mapper来调用相应的process来处理,在此是以touch事件为例,所以会执行类型为TouchInputMapperprocess函数处理相应的事件。

         在对应的process里面调用sync,在sync函数下会对touch事件做进一步的处理,然后调用dispatchTouches将触摸事件往上分发。在dispatchTouches里面会对touch事件进行判断,分为三种事件类型,包括DownMoveUp三种,不同的事件类型将以不同的参数调用dispatchMotion继续向上传递。

         dispatchMotion里面, touch事件封装在args里面并作为参数传给notifyMotion函数。

           这 里getListener得到的即为mQueuedListener,而在创建该实例时所传入的参数listener即为在创建InputReader对 象实例时,传入的InputDispatcher实例。因此此处调用notifyMotion函数,即调用InputDispatcher.cpp下的 notifyMotion函数。

       InputDispatcher下的notifyMotion函数:

         将封装事件的args里面的内容读出来,并重新封装到一个类型为newEntry的对象实例中,并通过调用enqueueInboundEventLocked将事件放入mInboundQueue队列里面。

         InputDispatcherThread启动时,调用的是InputDispatcherdispatchOnce,而此函数调用LooperpollOnce函数,而当没有输入事件发生时,线程会一直睡在Looper所创管道的读端,此时唤醒即唤醒InputDispatcherThead线程。唤醒后,可以继续执行dispatchOnce函数,

        调用该函数来完成进一步的操作,

         把事件从队列mInboundQueue中取出来,然后赋给mPendingEvent,即将事件封装在了mPendingEvent中,并根据其事件类型(以touch事件为例)来调用相应的函数进行处理。

        首先将mPendingEvent的类型转换,即将事件重新封装在一个类型为MotionEntry的对象实例中,并作为参数传入dispatchMotionLocked函数中去。

        首先判断当前激活窗口是否存在,若不存在,需要先找出激活窗口,然后调用dispatchEventToCurrentInputTargetsLocked该函数将事件分发给当前激活窗口。

        首先是得到当前的激活窗口,然后通过窗口所注册的inputChannel得到封装在Server端通道的Connection对象,最后调用prepareDispatchCycleLocked函数继续进行进一步处理,接着调用equeueDispatchEntriesLocked函数。

         将事件封装成一个DispatchEntry对象,并将其添加到connection的outboundQueue中,表明当前有一个待处理的touch事件。最后调用startDispatchCycleLocked函数来继续分发事件。

        首先将事件由队列中取出,将其封装到inputPublisher中,然后调用sendDispatchSignal函数来通知关联的Activity有事件需要处理。publishMotionEvent实际上是把事件的信息放入到一个共享内存中(mSharedMessage)这样该管道的反向读端和正向写端以及匿名内存信息都清楚了。

        前面在创建管道的时候,Server端和Client端是公用一个匿名内存,管道的前向和反向只是通知相互通知有事件发生了,而真正的事件内容需要去匿名内存里面读取。

         Server端通道写端有东西写入,则唤醒主线程进行读取。此时调用handleReceiveCallback函数。

         handleReceiveCallback

         首先得到Client端封装通道的Connection对象,然后得到InputConsumer对象并调用receiveDispatchSignal判断是否收到输入事件消息的通知。如果收到了通知,调用consume函数。

 前面已将事件的信息写入了匿名内存,调用consume把事件读出来然后保存在inputEvent

            将封装在Connection的InputHandle对象取出来。

         得到javadispatchMotionEvent函数的Id,把事件类型转换为本地类型然后调用CallStaticVoidMethod函数执行java层的dispatchMotionEvent

        InputQueue.java下的dispatchMotionEvent方法:

         将事件封装成一个消息,放入消息队列中进行处理( DISPATCH_POINTER)。在handleMessage方法中,若消息类型为DISPATCH_POINTER,则调用deliverPointerEvent方法进行处理。

         接着调用dispatchPointerEventevent事件传给当前view,然后调用该viewdispatchTouchEvent接口继续向上分发event事件。

         最后调用viewonTouchEvent()接口里面处理event事件,至此,event的传输流程已经讲完了。


本文转载自:http://blog.csdn.net/wlwl0071986/article/details/8247138

s
粉丝 42
博文 202
码字总数 0
作品 0
深圳
高级程序员
私信 提问
短信发送--短信的发送流程(framework)

短信的发送流程(framework) 一、主要文件 /packages/apps/Mms/com/android/mm/transaction/SmsSingleRecipientSender /framework/base/telephony/java/com/android/internal/telephony/ISm......

陈wei
2013/02/18
648
0
Android 4.0 事件输入(Event Input)系统

TouchScreen功能在Android4.0下不工作 原来在Android2.3.5下能正常工作的TouchScreen功能,移植到Android 4.0就不能正常工作了。凭直觉,Android4.0肯定有鬼。真是不看不知道,一看吓一跳。在...

sflfqx
2014/07/23
247
0
SylixOS调试方法详解——综合案例分析

1. SylixOS调试方法介绍 SylixOS实现了一个功能强大的调试stub(桩),可在设备或模拟器上在线调试应用程序,RealEvo-IDE也提供配套的调试插件。目前RealEvo-IDE既支持自动推送调试、也支持传...

Esc120
2018/06/26
0
0
SylixOS调试方法详解——综合案例分析

SylixOS调试方法介绍 SylixOS实现了一个功能强大的调试stub(桩),可在设备或模拟器上在线调试应用程序,RealEvo-IDE也提供配套的调试插件。目前RealEvo-IDE既支持自动推送调试、也支持传统...

Esc130
2017/10/25
15
0
详解键盘事件(keydown,keypress,keyup)

转载自:http://www.jianshu.com/p/8f839f558319 一、键盘事件基础 1、定义 顺序为:keydown -> keypress ->keyup 2、示例...

ahl123
2018/07/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

如何编写高质量的 JS 函数(1) -- 敲山震虎篇

本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/7lCK9cHmunvYlbm7Xi7JxQ 作者:杨昆 一千个读者,有一千个哈姆雷特。 此系列文章将会从函数的执行机制、鲁棒性、函...

vivo互联网技术
55分钟前
5
0
学会这5个Excel技巧,让你拒绝加班

在网上,随处都可以看到Excel技巧,估计已看腻了吧?但下面5个Excel技巧会让你相见恨晚。关键的是它们个个还很实用 图一 技巧1:快速删除边框 有时当我们处理数据需要去掉边框,按Ctrl+Shif...

干货趣分享
今天
11
0
JS基础-该如何理解原型、原型链?

JS的原型、原型链一直是比较难理解的内容,不少初学者甚至有一定经验的老鸟都不一定能完全说清楚,更多的"很可能"是一知半解,而这部分内容又是JS的核心内容,想要技术进阶的话肯定不能对这个...

OBKoro1
今天
10
0
高防CDN的出现是为了解决网站的哪些问题?

高防CDN是为了更好的服务网络而出现的,是通过高防DNS来实现的。高防CDN是通过智能化的系统判断来路,再反馈给用户,可以减轻用户使用过程的复杂程度。通过智能DNS解析,能让网站访问者连接到...

云漫网络Ruan
今天
15
0
OSChina 周一乱弹 —— 熟悉的味道,难道这就是恋爱的感觉

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @xiaoshiyue :好久没分享歌了分享张碧晨的单曲《今后我与自己流浪》 《今后我与自己流浪》- 张碧晨 手机党少年们想听歌,请使劲儿戳(这里)...

小小编辑
今天
3.3K
25

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部