2024年Android最全android的窗口机制分析------事件处理,Android面试题2024

最后

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。

所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。

如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  1. getHostVisibility(), mAttachInfo.mContentInsets,

  2. mInputChannel);

  3. } catch (RemoteException e) {

在ViewRoot和WMS(WindowManagerService)建立起连接之前首先会创建一个InputChannel对象,同样的WMS端也会创建一个InputChannel对象,不过WMS的创建过程是在ViewRoot调用add()方法时调用的。InputChannel的构造不做任何操作,所以在ViewRoot中创建InputChannel时尚未初始化,它的初始化过程是在调用WMS方法add()时进行的,看到上面代码中将mInputChannel作为参数传递给WMS,目的就是为了初始化。下面转到WMS代码看看InputChannel的初始化过程。

addWindow()@WindowManagerService.java

[java]  view plain copy

  1. if (outInputChannel != null) {

  2. String name = win.makeInputChannelName();

  3. InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);

  4. win.mInputChannel = inputChannels[0];

  5. inputChannels[1].transferToBinderOutParameter(outInputChannel);

  6. mInputManager.registerInputChannel(win.mInputChannel);

  7. }

outInputChannel为ViewRoot传递来的InputChannel对象,上述代码主要的工作其实就是创建一对InputChannel,这一对InputChannel中实现了一组全双工管道。 在创建InputChannel对的同时,会申请共享内存,并向2个InputChannel对象中各自保存一个共享内存的文件描述符。InputChannel创建完成后,会将其中一个的native InputChannel 赋值给outInputChannel,也就是对ViewRoot端InputChannel对象的初始化,这样随着ViewRoot和WMS两端的InputChannel对象的创建,事件传输系统的管道通信也就建立了起来。

创建InputChannel pair的过程以及管道建立,共享内存申请的过程就不再列出它的代码了,请参考openInputChannelPair()@InputTransport.cpp。下图为ViewRoot和WMS两端创建InputChannel pair之后的结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    1.2 InputChannel的注册过程


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

明白了InputChannel对象需要注册的原因之后,我们再看ViewRoot和WMS端的InputChannel对象各自需要注册到哪里?其实也很好理解,两个InputChannel对象WMS端的是管道通信的sender, ViewRoot端的是Receiver(尽管创建的全双工,但是目前只使用到了它的一向的通信,另一方向的通信尚未使用),那么着两个InputChannel对象肯定需要被两个不同的管理者来管理。ViewRoot端的一般情况下会注册到一个NativeInputQueue对象中(这是一个Native的对象,而JAVA端的InputQueue类仅仅是提供了一些static方法与NativeInputQueue通信),只要当用到NativeActivity时,会是另外一种处理机制,这里我们不管它,NativeActivity毕竟很少用到;WMS端注册在InputManager对象中。其实从NativeInputQueue和InputManager的名字中也就能知道各自的功能了。

    1.2.1 注册到NativeInputQueue

ViewRoot端InputChannel对象在向NativeInputQueue注册时,需要注册3个参数:

  1. 将InputChannel对象对应的Native InputChannel传递给NativeInputQueue;

  2. 将ViewRoot的成员变量InputHandler传递给NativeInputQueue,这个InputHandler则是事件的处理函数,传递它的作用主要是明确当前ViewRoot的事件处理函数;

  3. 还有一个很重要的参数需要传递给NativeInputQueue,那就是当前Application的主进程的MessageQueue。

其实,android在实现事件传输时,很大程度上借用了线程Looper和MessageQueue的轮询(poll)机制,通过它的轮询机制来检测管道上是否有消息通知事件发生,借用Looper机制能够很大限度的保证事件能够第一时间被Application知晓, Looper这块会单独分析一下。

在注册过程中,android会将InputChannel对象中保存的管道的文件描述符交给MessageQueue的native looper去监听,同时向native looper指示一个回调函数,一旦有事件发生,native looper就会检测到管道上的数据,同时会去调用指示的回调函数。这个回调函数为handleReceiveCallback()@android_view_InputQueue.cpp.

当然了,NativeInputQueue对象,整个系统中只有这么一个,它为了负责管理这么多的Application的事件传递,android在NativeInputQueue类中定义了一个子类Connection,每个InputChannel对象在注册时都会创建一个自己的Connection对象。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这一块的代码在registerInputChannel()@android_view_InputQueue.cpp

    1.2.2 注册到InputManager

由于WMS端的对linux Input 系统的检测和ViewRoot对管道接收端的检测机制不同,前面分析过了,ViewRoot端很好的复用了Application 主线程的Looper轮询机制来实现对事件响应的实时性,而WMS尽管也有自己的Looper,WMS却没像ViewRoot一样复用自己的Looper机制,至于原因android的code上没有明确说明,我的猜测应该是WMS是整个系统的,不像ViewRoot一样每个Activity都有一套,为了不影响系统的整体性能,尽量不要去影响WMS。

不采用Looper来轮询是否有事件发生,InputManager启动了2个进程来管理事件发生与传递,InputReaderThread和InputDispatcherThread,InputReaderThread进程负责轮询事件发生; InputDispatcherThread负责dispatch事件。为什么需要2个进程来管理,用一个会出现什么问题?很明显,如果用一个话,在轮询input系统event的时间间隔会变长,有可能丢失事件。

虽然没有使用Looper来轮询事件的发生,但是InputDispatcher使用了native looper来轮询检查管道通信,这个管道通信表示InputQueue是否消化完成dispatch过去的事件。注意的是这个native looper并不是WMS线程的,而是线程InputDispatcher自定定义的,因此所有的轮询过程,需要InputDispatcher主动去调用,如

mLooper->pollOnce(timeoutMillis);或者mLooper->wake();。而不像NativeInputQueue一样,完全不用操心对looper的操作。

WMS在初始化时会创建这么一个InputManager实例,当然了,它也是系统唯一的。JAVA层的InputManager实例并没有实现太多的业务,真正实现Input Manager业务是Native的NativeInputManager实例,它在被创建时,建立起了整个WMS端事件传递系统的静态逻辑,如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

NativeInputManager的整个业务的核心其实是InputReader和InputDispatcher两个模块,下面简单介绍一下这两个模块。

    A. InputReader

InputReader从名称就可以看出主要任务是读事件,基本上它所有的业务都包含在了process()的函数中,

[cpp]  view plain copy

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

  2. switch (rawEvent->type) {

  3. case EventHubInterface::DEVICE_ADDED:

  4. addDevice(rawEvent->deviceId);

  5. break;

  6. case EventHubInterface::DEVICE_REMOVED:

  7. removeDevice(rawEvent->deviceId);

  8. break;

  9. case EventHubInterface::FINISHED_DEVICE_SCAN:

  10. handleConfigurationChanged(rawEvent->when);

  11. break;

  12. default:

  13. consumeEvent(rawEvent);

  14. break;

  15. }

  16. }

process()函数的输入参数时EventHub模块提供的,

1.当EventHub尚未打开input系统eventXX设备时,InputReader去向EventHub获取事件时,EventHub会首先去打开所有的设备,并将每个设备信息以RawEvent的形式返给InputReader,也就是process()中处理的EventHubInterface::DEVICE_ADDED类型,该过程会根据每个设备的deviceId去创建InputDevice,并根据设备的classes来创建对应的InputMapper。如上图所示。

2.当所有的设备均被打开之后,InputReader去向EventHub获取事件时,EventHub回去轮询event节点,如果有事件,InputReader则会消化该事件consumeEvent(rawEvent);

    B. InputDispatcher

数据传输管理的核心业务是在InputDispatcher中完成的,因此最终WMS端InputChannel对象会注册到InputDispatcher中,同样的由于整个系统中InputDispatcher实例只有一个,而WMS端InputChannel对象是和ViewRoot一一对应的,因此InputDispatcher类中也定义了一个内部类Connect来管理各自的InputChannel对象。不同于NativeInputQueue类中的Connect类,InputDispatcher中的Connect类的核心业务是由InputPublisher对象来实现的,该对象负责将发生的事件信息写入到共享内存。

相关代码在registerInputChannel()@InputDispatcher.cpp

2. 事件传递

======================================================================

经过分析事件处理系统的初始化过程之后,我们已经对事件处理系统的整体架构有了一定程度的理解,那么下面的事件传递过程就会显得很easy了。

    2.1 InputReaderThread线程操作


当input系统有事件发生时,会被InputReaderThread线程轮询到,InputReader会根据事件的device id来选择的InputDevice,然后再根据事件的类型来选择InputDevice中的InputMapper,InputMapper会将事件信息通知给InputDispatcher;

目前adroid在InputReader中实现了5种设备类型的InputMapper,分别为滑盖/翻盖SwitchInputMapper、键盘KeyboardInputMapper、轨迹球TrackballInputMapper、多点触屏MultiTouchInputMapper以及单点触屏SingleTouchInputMapper。

设备类型

InputManager

EventType

Notify InputDispatcher

滑盖/翻盖

SwitchInputMapper

EV_SW

notifySwitch()

键盘

KeyboardInputMapper

EV_KEY

notifyKey()

轨迹球

TrackballInputMapper

EV_KEY, EV_REL,

EV_SYN

notifyMotion()

单点触屏

SingleTouchInputMapper

EV_KEY, EV_ABS,

EV_SYN

notifyMotion()

多点触屏

MultiTouchInputMapper

EV_ABS,

EV_SYN

notifyMotion()

其中EV_REL为事件相对坐标,EV_ABS为绝对坐标,EV_SYN表示Motion的一系列动作结束。

Notify InputDispatcher表示不同的事件通知InputDispatcher的函数调用,这几个函数虽然是被InputReaderThread调用的,单却是在InputDispatcher定义的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    2.1.1 notifySwitch()

[cpp]  view plain copy

  1. void InputDispatcher::notifySwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,

  2. uint32_t policyFlags) {

  3. #if DEBUG_INBOUND_EVENT_DETAILS

  4. LOGD(“notifySwitch - switchCode=%d, switchValue=%d, policyFlags=0x%x”,

  5. switchCode, switchValue, policyFlags);

  6. #endif

  7. policyFlags |= POLICY_FLAG_TRUSTED;

  8. mPolicy->notifySwitch(when, switchCode, switchValue, policyFlags);

  9. }

Switch事件的处理是比较简单的,这是一个与Activity无关的事件,因此我们根本不需要将其dispatch到ViewRoot,所以在notifySwitch()方法中直接通知给PhoneWindowManager去处理即可。从上面的类图中我们其实可以发现mPolicy指向的就是NativeInputManager,

[cpp]  view plain copy

  1. void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode,

  2. int32_t switchValue, uint32_t policyFlags) {

  3. #if DEBUG_INPUT_DISPATCHER_POLICY

  4. LOGD(“notifySwitch - when=%lld, switchCode=%d, switchValue=%d, policyFlags=0x%x”,

  5. when, switchCode, switchValue, policyFlags);

  6. #endif

  7. JNIEnv* env = jniEnv();

  8. switch (switchCode) {

  9. case SW_LID:

  10. env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyLidSwitchChanged,

  11. when, switchValue == 0);

  12. checkAndClearExceptionFromCallback(env, “notifyLidSwitchChanged”);

  13. break;

  14. }

  15. }

NativeInputManager的notifySwitch()最终会调用到notifySwitch()@PhoneWindowManager.java

    2.1.2 notifyKey()

[cpp]  view plain copy

  1. void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,

  2. uint32_t policyFlags, int32_t action, int32_t flags,

  3. int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {

  4. #if DEBUG_INBOUND_EVENT_DETAILS

  5. LOGD("notifyKey - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "

  6. “flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld”,

  7. eventTime, deviceId, source, policyFlags, action, flags,

  8. keyCode, scanCode, metaState, downTime);

  9. #endif

  10. if (! validateKeyEvent(action)) {

  11. return;

  12. }

  13. policyFlags |= POLICY_FLAG_TRUSTED;

  14. mPolicy->interceptKeyBeforeQueueing(eventTime, deviceId, action, /*byref*/ flags,

  15. keyCode, scanCode, /*byref*/ policyFlags);

  16. bool needWake;

  17. { // acquire lock

  18. AutoMutex _l(mLock);

最后

题外话,我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料。

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

Android开发8年,阿里、百度一面惨被吊打!我是否应该转行了?

【Android进阶学习视频】、【全套Android面试秘籍】

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

脑图(技能树)】**

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-dB22x0xN-1715617759033)]

【Android进阶学习视频】、【全套Android面试秘籍】

希望我能够用我的力量帮助更多迷茫、困惑的朋友们,帮助大家在IT道路上学习和发展

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值