ANR基础 - Input系统

news/2025/2/12 8:46:06/

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用


文章目录

  • 系列文章目录
  • 前言
  • 一、Input系统概述
  • 二、整体框架
    • 1.整体框架类图
    • 2.核心启动过程
      • 2.1 initialize
      • 2.1 InputManager.start
  • 三、InputReader线程
    • 3.1 EventHuab
    • 3.2 InputReader核心流程
  • 四、InputDispatcher线程
    • 4.1 核心方法
    • 4.2 小节
  • 五、Input系统之UI线程
  • 六、Input事件处理全过程
    • 6.1 整体框架图
    • 6.2 交互过程
  • 总结


前言


一、Input系统概述

当用户触摸屏幕或者按键操作,首次触发的是硬件驱动,驱动收到事件后,将该相应事件写入到输入设备节点, 这便产生了最原生态的内核事件。接着,输入系统取出原生态的事件,经过层层封装后成为KeyEvent或者MotionEvent ;最后,交付给相应的目标窗口(Window)来消费该输入事件。可见,输入系统在整个过程起到承上启下的衔接作用。

Input模块的主要组成:
Native层的InputReader负责从EventHub取出事件并处理,再交给InputDispatcher;
Native层的InputDispatcher接收来自InputReader的输入事件,并记录WMS的窗口信息,用于派发事件到合适的窗口;
Java层的InputManagerService跟WMS交互,WMS记录所有窗口信息,并同步更新到IMS,为InputDispatcher正确派发事件到ViewRootImpl提供保障;

二、整体框架

1.整体框架类图

在这里插入图片描述

2.核心启动过程

InputManager.cpp 核心代码如下:

2.1 initialize

void InputManager::initialize() {//创建线程“InputReader”mReaderThread = new InputReaderThread(mReader);//创建线程”InputDispatcher“mDispatcherThread = new InputDispatcherThread(mDispatcher);
}InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :Thread(/*canCallJava*/ true), mReader(reader) {
}InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
}

初始化的主要工作就是创建两个能访问Java代码的native线程:

创建线程“InputReader”
创建线程”InputDispatcher“

2.1 InputManager.start

status_t InputManager::start() {result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);...return OK;
}

该方法的主要功能是启动两个线程:

启动线程“InputReader”
启动线程”InputDispatcher“

三、InputReader线程

3.1 EventHuab

EventHub采用INotify + epoll机制实现监听目录/dev/input下的设备节点

3.2 InputReader核心流程

在这里插入图片描述
InputReader整个过程涉及多次事件封装转换,其主要工作核心是以下三大步骤:

  1. getEvents:通过EventHub(监听目录/dev/input)读取事件放入mEventBuffer,而mEventBuffer是一个大小为256的数组, 再将事件input_event转换为RawEvent;
  2. processEventsLocked: 对事件进行加工, 转换RawEvent -> NotifyKeyArgs(NotifyArgs)
  3. QueuedListener->flush:将事件发送到InputDispatcher线程, 转换NotifyKeyArgs -> KeyEntry(EventEntry)

InputReader线程不断循环地执行InputReader.loopOnce(), 每次处理完生成的是EventEntry(比如KeyEntry, MotionEntry), 接下来的工作就交给InputDispatcher线程。

四、InputDispatcher线程

4.1 核心方法

用一张图来整体概况InputDispatcher线程的主要工作:
在这里插入图片描述
图解:
1.dispatchOnceInnerLocked(): 从InputDispatcher的mInboundQueue队列,取出事件EventEntry。另外该方法开始执行的时间点(currentTime)便是后续事件dispatchEntry的分发时间(deliveryTime)
2.dispatchKeyLocked():满足一定条件时会添加命令doInterceptKeyBeforeDispatchingLockedInterruptible;
3.enqueueDispatchEntryLocked():生成事件DispatchEntry并加入connection的outbound队列
4.startDispatchCycleLocked():从outboundQueue中取出事件DispatchEntry, 重新放入connection的waitQueue队列;
5.InputChannel.sendMessage通过socket方式将消息发送给远程进程;
6.runCommandsLockedInterruptible():通过循环遍历地方式,依次处理mCommandQueue队列中的所有命令。而mCommandQueue队列中的命令是通过postCommandLocked()方式向该队列添加的。

InputDispatcher中有一个线程,死循环执行dispatchOnce方法,该方法负责分发消息给APP侧,以及接受APP侧的返回并执行相关以后,最后执行ANR的判断。所以dispatchOnce属于整个的流程的核心,也是ANR处理流程的核心,所以我们重点了解下dispatchOnce这个方法。如下图所示:
在这里插入图片描述
dispatchOnce中主要完成四件事:

  1. dispatchOnceInnerLocked()方法负责把收到的输入信号分发给APP处理,发送成功会加入到InputDispatcher里的waitQueue队列和AnrTracker.cpp里的mAnrTimeouts。
  2. haveCommandsLocked()中查看队列中是否有任务,如果有就执行任务。这些任务就是执行doDispatchCycleFinishedCommand方法,该方法中,会根据收到的完成信号,完成对应的事件从waitQueue和mAnrTimeouts中移除的处理。
  3. processAnrsLocked中会进行一些逻辑判断,如果符合条件,则会触发ANR流程。
  4. pollOnce进入休眠,等待下一次的循环。

4.2 小节

InputReader读到输入事件后,就会传递到InputDispatcher,整个超时流程也都是由其负责的。InputDispatcher中主要是dispatchOnce方法来负责,它跑在单独的线程上。
首先它把收到的输入信号分发给APP一侧并记录到mAnrTimeouts集合上;
然后查看是否有APP侧传递过来的任务,如果有就执行,该任务就是把对应的输入事件从mAnrTimeouts集合中移除;
再然后判断mAnrTimeouts集合中是否有超时的事件,如果有就走ANR逻辑;
最后,一轮逻辑走完了,进入休眠,等待下一轮的唤醒。

五、Input系统之UI线程

在InputDispatcher的过程调用到InputChanel通过socket与远程进程通信,这里看下这个socket是如何建立的。

对于InputReader和InputDispatcher都是运行在system_server进程; 用户点击的界面往往可能是某一个app,而每个app一般地都运行在自己的进程,这里就涉及到跨进程通信,app进程是如何与system进程建立通信。

要解答这些问题,就要从Activity最基本的创建过程寻找。我们都知道一般地Activity对应一个应用窗口, 每一个窗口对应一个ViewRootImpl。

ViewRootImpl的setView()过程:
创建socket pair,作为InputChannel:

  • socket服务端保存到system_server中的WindowState的mInputChannel;
  • socket客户端通过binder传回到远程进程的UI主线程ViewRootImpl的mInputChannel;

IMS.registerInputChannel()注册InputChannel,监听socket服务端:

  • Loop便是“InputDispatcher”线程的Looper;
  • 回调方法handleReceiveCallback。

用一张图来整体概况UI线程跨进程通信的主要工作:
在这里插入图片描述
首先,通过openInputChannelPair来创建socket pair,作为InputChannel:

  • socket服务端保存到system_server中的WindowState的mInputChannel;
  • socket客户端通过binder传回到远程进程的UI主线程ViewRootImpl的mInputChannel;

紧接着,完成了两个线程的epoll监听工作:

  • IMS.registerInputChannel(): “InputDispatcher”线程监听socket服务端,收到消息后回调InputDispatcher.handleReceiveCallback();
  • setFdEvents(): UI主线程监听socket客户端,收到消息后回调NativeInputEventReceiver.handleEvent().

有了这些“InputDispatcher”和“UI”主线程便可以进行跨进程通信与交互。

六、Input事件处理全过程

6.1 整体框架图

在这里插入图片描述

6.2 交互过程

用一张图展示交互过程,主要是通过一对socket方式来通信。 当input时间分发到app端, 便进入了InputEventReceiver.dispatchInputEvent()过程.
在这里插入图片描述
图解:

  1. InputDispatcher线程调用InputPublisher的publishKeyEvent向UI主线程发送input事件;
  2. UI主线程接收到该事件后,调用InputConsumer的consumeEvents来处理该事件, 一路执行到ViewRootImpl.deliverInputEvent()方法;
  3. UI主线程经过一系列的InputStage来处理, 当事件分发完成,则会执行finishInputEvent()方法.再进一步调用InputConsumer::sendFinishedSignal 告知InputDispatcher线程该时事件已处理完成.
  4. InputDispatcher线程收到该事件后, 执行InputDispatcher::handleReceiveCallback();最终会调用doDispatchCycleFinishedLockedInterruptible()方法 ,将dispatchEntry事件从等待队列(waitQueue)中移除.

总结

简单总结和回顾以上文章的内容:

  • InputReader线程:通过EventHub从/dev/input节点获取事件,转换成EventEntry事件加入到InputDispatcher的mInboundQueue。
  • InputDispatcher线程:从mInboundQueue队列取出事件,转换成DispatchEntry事件加入到connection的outboundQueue队列。再然后开始处理分发事件,取出outbound队列,放入waitQueue.
  • UI线程:创建socket pair,分别位于”InputDispatcher”线程和focused窗口所在进程的UI主线程,可相互通信:
  1. UI主线程:通过setFdEvents(), 监听socket客户端,收到消息后回调NativeInputEventReceiver();
  2. “InputDispatcher”线程: 通过IMS.registerInputChannel(),监听socket服务端,收到消息后回调handleReceiveCallback;

参考:
http://gityuan.com/2016/12/31/input-ipc/
Input类型ANR产生原理讲解


http://www.ppmy.cn/news/71722.html

相关文章

【软件测试】Python自动化软件测试算是程序员吗?

今天早上一觉醒来&#xff0c;突然萌生一个念头&#xff0c;【软件测试】软件测试算是程序员吗&#xff1f;左思右想&#xff0c;总感觉哪里不对。做了这么久的软件测试&#xff0c;还真没深究过这个问题。 基于&#xff0c;内事问百度的准则&#xff1a; 结果…… 我刚发出软…

【linux】图文并茂,让你轻松掌握Linux基本指令

目录 一&#xff0c;前提 二&#xff0c; 在root身份下&#xff0c;管理用户 1. 判断身份 2. 创建用户 3. 销毁用户 三&#xff0c;文件增&#xff0c;删&#xff0c;移动指令 1. pwd——查看路径 2. ps ——打开文件目录 3. touch——创建文件 4. nano——打开文件 5.…

软件测试实验:loadrunner的高级使用

目录 前言实验目的实验内容实验要求实验过程loadrunner中插入事务与集合点loadrunner中插入检查点loadrunner中参数化-table分析报告功能loadrunner手动设置场景loadrunner监视图标 总结 前言 本实验主要介绍了loadrunner这一强大的性能测试工具的高级使用方法&#xff0c;包括…

英文文本情感分析textblob模块sentiment方法

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 英文文本情感分析 textblob模块 sentiment方法 [太阳]选择题 关于下列代码说法错误的是&#xff1f; from textblob import TextBlob myText"Textblob is amazingly simple to us…

BLE解调

BLE解调前奏 如果不太了解IQ、FSK、GFSK的话&#xff0c;可以看上一篇&#xff0c;有一些关于这些内容的东西&#xff0c;写的应该还算好理解点吧&#xff0c;给出了自己学习时候的参考&#xff0c;具体的看他们写的。 调频与调相之间的关系 IQ调制中&#xff0c;调频是表现…

YOLOv5、YOLOv7独家原创改进:独家首发最新EfficiCLNMS改进点,改进有效可以直接当做自己的原创改进点来写,新的增强预测帧

💡该教程为属于《芒果书》📚系列,包含大量的原创首发改进方式, 所有文章都是全网首发原创改进内容🚀 💡本篇文章为YOLOv5、YOLOv7改进:独家首发最新EfficiCL-NMS改进点,新的增强预测帧率。 💡对自己数据集改进有效的话,可以直接当做自己的原创改进点来写!!!改…

【Jmeter第三章】Jmeter给请求添加请求头

给请求加上请求头最常见的场景就是在请求头上添加token了&#xff0c;这里也拿添加token来举例 1、添加某个请求的请求头 1、选中HTTP请求&#xff0c;右键添加 2、添加请求头 2、添加公共的请求头信息 其实步骤和上面是一样的&#xff0c;只不过是选择&#xff1a;线程组…

okhttp篇3:RealCall

Call Call一般代表一个已经准备好的Request&#xff0c;Request的包装类&#xff0c;可执行&#xff0c;它一般有两个主要的方法&#xff1a; execute(立即执行&#xff0c;并阻塞线程&#xff0c;直到Response返回)enqueue(将Request放入队列&#xff0c;等待线程池调度执行…