本篇是图形系列的第五篇文章,在之前的几篇文章中,我们分别了解了 Android 系统[渲染/合成的底层原理]和[自定义 View / ViewGroup 的流程]
https://juejin.cn/post/7132777622487957517
https://juejin.cn/post/7140332948485570596
前排提醒:全文 1.5w 字,建议阅读时长 30 分钟
从硬件到内核
图片来源:【集微拆评】小米10拆解:内部布局与iPhone相似,1亿像素主摄吸睛
用来和 CPU 进行通信的。
图片是重绘版,参考自:
I²C 是硬件之间常用的一种通信协议,它规定了什么表示起始、停止、应答和非应答等一系列信号。
内核创建设备文件
达尔优 键盘上报的是:0010 罗技 键盘上报的是:0001
在 2001 年发布的 *[2.4.0*] 版本,Linux 首次加入了 Input 子系统的代码,为的就是将输入设备的共性抽象出来,制定统一的输入规则。
https://elixir.bootlin.com/linux/2.4.0/source/drivers/input
首发版本只支持 手柄、鼠标 和 键盘 这三种硬件,在随后 2002 年发布的 *[2.5.25*] 版本中,加入了对 触摸屏 的支持。
https://elixir.bootlin.com/linux/v2.5.25/source/drivers/input
除了规范输入内容,Input 子系统还为应用程序提供了 操作/读取 输入设备的接口,来看框架图:
最下层:输入设备驱动层,drivers/input/xxx,这里就是各大厂商需要遵循的协议规范,向内核层报告输入的内容。 中间层:输入核心层,input.c 属于这一层。这是 Linux 核心逻辑,用来管理设备添加、卸载等操作,事件提供给应用前的准备工作。 最上层:输入事件驱动层,到这里硬件驱动已经抽象为设备文件了,对应 /dev/input/xxx ,硬件驱动发送的数据就保存在该路径下的各个设备文件中,等待应用读取。
/drivers/input/input.c
class input { //Linux input 框架的核心层,为驱动层提供设备注册和操作接口
/* 设备注册 */
int input_register_device(input_dev *dev); // 注册一个 input 设备到内核
void input_unregister_device(input_dev *dev); // 从内核注销掉一个 input 设备
/* 设备连接 */
int input_attach_handler(input_dev *dev, input_handler *handler);
void input_disconnect_device(input_dev *dev);
/* 事件上报 */
void input_handle_event(input_dev *dev, type, code, value);
/* 应用程序数据读取 */
int input_event_to_user(input_dev *dev, type, code, value);
}
接着,我们还可以用 'getevent' 命令打开这个设备文件,获取它发送的原始数据。
接下来我们看 Android 系统的框架层(Framework)是如何处理触摸事件的。
ps:本章节的 '事件分发' 探讨的是,如何将触摸事件从设备文件传递到 APP 进程,和 View 的事件分发不是一回事儿,注意别搞混了。
初识 InputManagerService
native 层,这是 IMS 的核心层,负责 读取/分发 事件,EventHub.cpp、InputReader.cpp、InputDispatcher.cpp 三员大将都在这。 jni 层,主要是对 natvie 做转发,另外负责创建对象啥的,不怎么需要关注。 Java 层,主要负责通信部分,和 WMS 同步窗口数据啦,和 APP 跨进程通信啦等等。
1、EventHub
//frameworks/native/services/inputflinger/EventHub.cpp
class EventHub {
EventHub::EventHub(void) {
mEpollFd = epoll_create(EPOLL_SIZE_HINT); // 创建 epoll,用于监听设备文件是否有可读事件
mINotifyFd = inotify_init(); // 创建 inotify ,用于监听文件系统是否变化,有变化说明发生设备插拔
}
size_t EventHub::getEvents( timeoutMillis, buffer, bufferSize) {
//getEvents() 是 IMS 的核心,该方法一共做了两件事
// 1. 监听设备插拔动作,执行对应的设备的打开/卸载操作,并生成 RawEvent 结构通知调用者
// 2. 监听输入设备文件的事件变化
//如果没有任何事件发生,调用 epoll_wait() 函数执行等待
return event - buffer; // 返回读取到的事件数量
}
}
EventHub 整个过程如下图:
2、InputReader
/frameworks/native/services/inputflinger/InputReader.cpp
class InputReader {
class InputReaderThread : Thread { //内部类
/*InputReaderThread 线程启动后,循环将不断地执行 InputReader#loopOnce()函数*/
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
}
void InputReader::loopOnce(); // 核心方法,负责读取事件,解析事件
}
/frameworks/native/services/inputflinger/InputReader.cpp
class InputReader {
// 读取事件,解析事件
void InputReader::loopOnce() {
int count = mEventHub->getEvents(); // 读消息,有消息返回,没消息阻塞到 epoll()。由于事件分发需要时间,所以单次读取的事件可能是多个
if(count) processEventsLocked();//解析原始事件、提交到队列等待分发
}
}
/frameworks/native/services/inputflinger/InputReader.cpp
class InputReader {
void InputReader::processEventsLocked(rawEvents, count) {
// 遍历所有事件,解析、分发
for (const RawEvent* rawEvent = rawEvents; count;) {
int32_t type = rawEvent->type; // 获取事件类型
switch (type){ // 源码不包含此 switch 逻辑,这是 InputDevice 中的内容,为了方便理解我才搬了过来
case EV_KEY; // 按键类型的事件。能够上报这类事件的设备有键盘、鼠标、手柄、手写板等一切拥有按钮的设备(包括手机上的实体按键)
case EV_ABS; // 绝对坐标类型的事件。这类事件描述了在空间中的一个点,触控板、触摸屏等使用绝对坐标的输入设备可以上报这类事件
case EV_REL; // 相对坐标类型的事件。这类事件描述了事件在空间中相对于上次事件的偏移量。鼠标、轨迹球等基于游标指针的设备可以上报此类事件
case EV_SW; // 开关类型的事件。这类事件描述了若干固定状态之间的切换。手机上的静音模式开关按钮、模式切换拨盘等设备可以上报此类事件
...
}
// 我们只专注 touch 触摸事件
dispatchTouches(when, policyFlags);
}
}
void dispatchTouches(){
// 判断是否只是单指事件,或是多指触摸等等等
// 解析完成后,调用 dispatchMotion() 分发
dispatchMotion();
}
void dispatchMotion(){
// 最终生成 NotifyMotionArgs 结构,交给 InputDispatcher 执行最后的分发
NotifyMotionArgs args;
InputDispatcher::notifyMotion(args);//提交到 InputDispatcher
}
}
ps:为了方便理解,我把其他分支整合到主方法来,中间还省略了许多代码。所以建议不要对照源码看这篇文章,不然你可能会因为找不到某个方法回来骂我的~
接下来就看事件是怎么分发的了,InputReader 整个过程如下图:
3、InputDispatcher
/frameworks/native/services/inputflinger/InputDispatcher.cpp
class InputDispatcher {
void notifyMotion(const NotifyMotionArgs* args) {
MotionEntry* newEntry = new MotionEntry(args);//封装成entry
enqueueInboundEventLocked(newEntry);//入列一个节点,等待分发被执行,逻辑在 dispatchOnce()
}
}
/frameworks/native/services/inputflinger/InputDispatcher.cpp
class InputDispatcher {
class InputDispatcherThread : Thread {
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
}
void dispatchOnce() {
if(!queue.isEmpty()) dispatchOnceInnerLocked();//有消息就执行分发
}
}
/frameworks/native/services/inputflinger/InputDispatcher.cpp
class InputDispatcher {
void dispatchOnceInnerLocked() {
mPendingEvent = queue.dequeue(); // 从派发队列取出一个事件,简略写法
switch (mPendingEvent->type) { // 判断消息的类型:配置更改、插拔消息、key事件、触摸事件等等
case EventEntry::TYPE_MOTION:
dispatchMotionLocked(); // 我们只专注 触摸事件
}
}
void dispatchMotionLocked() {
int32_t injectionResult = findTouchedWindowTargetsLocked(); // 为 Motion 事件寻找合适的目标窗口
if (injectionResult) dispatchEventLocked(); // 如果成功地找到了可以接收事件的目标窗口,则通过dispatchEventLocked()函数完成实际的派发工作
}
}
/frameworks/native/services/inputflinger/InputDispatcher.cpp
class InputDispatcher {
int findTouchedWindowTargetsLocked(){
size_t numWindows = mWindowHandles.size(); // 获取窗口集合
for (size_t i = 0; i < numWindows; i++);//从前向后遍历所有的window以找出触摸的window,将满足条件的放入inputTargets,没找到返回第一个前台window
}
// 合适的目标窗口被确定下来之后,便可以开始将实际的事件发送给窗口了
void dispatchEventLocked(Vector<InputTarget>& inputTargets) {
InputChannel channel = inputTarget.inputChannel;//删减过的流程
channel->sendMessage(&msg);//给能够被触摸的window发送跨进程消息
}
}
代码稍微有点长,这里就不展开讨论了,感兴趣的朋友可以阅读《Window Touchable Region》这篇文章。
https://xianzhu21.space/developer/window_touchable_region/
到这里,InputDispatcher 所有的分发工作就全部结束了,整个过程如下图:
/frameworks/native/services/inputflinger/InputDispatcher.cpp
class InputDispatcher {
void dispatchMotionLocked() {
int32_t injectionResult = findTouchedWindowTargetsLocked(); // 为 Motion 事件寻找合适的目标窗口
if (injectionResult) dispatchEventLocked(); // 如果成功地找到了可以接收事件的目标窗口,则通过dispatchEventLocked()函数完成实际的派发工作
}
int findTouchedWindowTargetsLocked(){
size_t numWindows = mWindowHandles.size(); // 获取窗口集合
for (size_t i = 0; i < numWindows; i++)
...
}
void dispatchEventLocked(Vector<InputTarget>& inputTargets) {
InputChannel channel = inputTarget.inputChannel;
channel->sendMessage(&msg);//给能够被触摸的window发送跨进程消息
}
}
负责查找窗口的 findTouchedWindowTargetsLocked() 方法中,mWindowHandles 所持有的窗口集合,是从哪里来的? 负责通信的 dispatchEventLocked() 方法中,InputChannel 是什么?IMS 是什么时候和 APP 建立通信的?
启动 InputManagerService
了解 InputManagerService 大致的启动流程。 了解 InputDispatcher 的窗口集合从哪里来,以及 IMS 如何建立跟 APP 通信的?
1、IMS 窗口集合从哪里来?
/frameworks/base/services/java/com/android/server/SystemServer.java
class SystemServer {
private void startOtherServices() {
inputManager = new InputManagerService(context); // 创建 IMS 对象
...
//将 InputMonitor 对象保存到 IMS 对象
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
}
}
/frameworks/native/services/inputflinger/InputDispatcher.cpp
class InputDispatcher {
void InputDispatcher::setInputWindows(inputWindowHandles) {
mWindowHandles = inputWindowHandles;
}
}
图片来源:aospxref.com/android-7.1.2
http://www.aospxref.com/android-7.1.2_r39/search?page-nav=android-7.1.2_r39&full=setInputWindows&project=frameworks
/frameworks/base/services/java/com/android/server/SystemServer.java
class SystemServer {
private void startOtherServices() {
inputManager = new InputManagerService(context);
inputManager.start();
}
}
/frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
class InputManagerService {
// 【step 1.0】初始化流程
public InputManagerService(Context context) {
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
LocalServices.addService(InputManagerInternal.class, new LocalService());
}
// 【step 2.0】 启动流程
public void start() {
nativeStart(mPtr); // 详见 【2.1】
Watchdog.getInstance().addMonitor(this);
}
}
2、IMS 的初始化工作
/frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
class NativeInputManager {
static jlong nativeInit(env, jclass, serviceObj, contextObj, messageQueueObj) {
NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,messageQueue->getLooper());
}
NativeInputManager::NativeInputManager(contextObj, serviceObj, looper) {
sp<EventHub> eventHub = new EventHub(); // 创建 EventHub 对象
mInputManager = new InputManager(eventHub, this, this); // 创建 InputManager 对象
}
}
/frameworks/native/services/inputflinger/EventHub.cpp
class EventHub {
EventHub::EventHub(void) {
mEpollFd = epoll_create(EPOLL_SIZE_HINT); // 创建 epoll,用于监听设备文件是否有可读事件
mINotifyFd = inotify_init(); // 创建 inotify ,用于监听文件系统是否变化,有变化说明发生设备插拔
}
}
/frameworks/native/services/inputflinger/InputManager.cpp
class InputManager {
InputManager::InputManager(eventHub, readerPolicy, dispatcherPolicy) {
mDispatcher = new InputDispatcher(dispatcherPolicy);
mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
initialize();
}
void InputManager::initialize() {
mReaderThread = new InputReaderThread(mReader); //创建线程 “InputReader”
mDispatcherThread = new InputDispatcherThread(mDispatcher); //创建线程 ”InputDispatcher“
}
}
3、IMS 的启动流程
/frameworks/base/services/java/com/android/server/SystemServer.java
class SystemServer {
private void startOtherServices() {
inputManager.start();
}
}
/frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
class InputManagerService {
public InputManagerService(Context context); // 初始化工作已完成
public void start() {
nativeStart(mPtr); // 启动服务
Watchdog.getInstance().addMonitor(this);
}
}
/frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
class NativeInputManager {
static void nativeStart(env, jclass , ptr) {
getInputManager()->start(); // 调用 InputManager 的 start() 方法
}
}
/frameworks/native/services/inputflinger/InputManager.cpp
class InputManager {
status_t InputManager::start() {
result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY); // 启动线程“InputReader”
result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); // 启动线程”InputDispatcher“
...
return OK;
}
}
到这里, IMS 的启动工作就全部结束了,整个过程如下图。
IMS 启动流程稍微有那么一点点长,完整的启动分析我放在了 GitHub ,点击[这里]跳转查看。
https://github.com/yibaoshan/Blackboard/blob/master/Blog/src/main/java/com/android/blog/android/graphics/code/v5/%E5%90%AF%E5%8A%A8%E6%B5%81%E7%A8%8B.cpp
启动 APP 进程
1、为 Activity 分配窗口
/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl {
InputChannel mInputChannel; // 保存的是 client 端的 socket
void setView(View view){
mInputChannel = new InputChannel(); // 创建了空的 InputChannel ,下面代码将会生成真实的 InputChannel
Session.addToDisplay(view,mInputChannel);//向wms添加窗口
if(mInputChannel!=null) mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());
}
}
创建了属于该视图的 InputChannel 空对象,先不用管。 向 WMS 添加窗口,并将刚刚创建的 InputChannel 对象一并传递过去,重要逻辑。 创建了 WindowInputEventReceiver ,将该视图的 InputChannel 保存起来,也不用管。
/frameworks/base/services/core/java/com/android/server/wm/Session.java
class Session {
void addToDisplay(InputChannel inputChannel){
WindowManagerService.addWindow(inputChannel);
}
}
/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
class WindowManagerService {
int addWindow(InputChannel outInputChannel){
WindowState win = new WindowState(); // 首次添加视图时创建,用于描述一个window
win.openInputChannel(outInputChannel); // 创建通信的关键代码,打开一对已连接的 socket
}
}
/frameworks/base/services/core/java/com/android/server/wm/WindowState.java
class WindowState {
InputChannel mInputChannel;
InputChannel mClientChannel;
void openInputChannel(InputChannel outInputChannel) {
InputChannel[] inputChannels = InputChannel.openInputChannelPair(); // 返回一对已连接的 socket
// 这是一对已连接的管道,将 socket 两端分别保存到服务端和客户端即可进行通信
mInputChannel = inputChannels[0]; // 下标为0的传递给 IMS 服务端,服务端可通过该 socket 向窗口发送消息
mClientChannel = inputChannels[1]; // 下标为1的回传给 client 端
// 1. Client 端 InputChanenl 调用 transforTo() 方法传给 ViewRootImpl 的 mInputChannel
mClientChannel.transferTo(outInputChannel);
// 2. Server 端 InputChannel 存在 WindowState 的 mInputChannel 变量
InputManagerService.registerInputChannel(mInputChannel);
}
}
/frameworks/native/libs/input/InputTransport.cpp
status_t InputChannel::openInputChannelPair(name,outServerChannel,outClientChannel) {
int sockets[2] = socketpair(sockets);
serverChannelName.append(" (server)");
outServerChannel = new InputChannel(serverChannelName, sockets[0]);
clientChannelName.append(" (client)");
outClientChannel = new InputChannel(clientChannelName, sockets[1]);
return OK;
}
2、sockets[1] 回传给 APP
/frameworks/base/services/core/java/com/android/server/wm/WindowState.java
class WindowState {
void openInputChannel(InputChannel outInputChannel) {
// 1. Client 端 InputChanenl 调用 transforTo() 方法传给 ViewRootImpl 的 mInputChannel
mClientChannel.transferTo(outInputChannel);
// 2. Server 端 InputChannel 存在 WindowState 的 mInputChannel 变量
InputManagerService.registerInputChannel(mInputChannel);
}
}
/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl {
InputChannel mInputChannel; // 保存的是 client 端的 socket
void setView(View view){
mInputChannel = new InputChannel(); // 创建了空的 InputChannel
try {
Session.addToDisplay(view,mInputChannel);
} catch (RemoteException e) {
mInputChannel = null;
}
...
if(mInputChannel != null ) mInputEventReceiver = new InputEventReceiver(mInputChannel, Looper.myLooper());
}
}
/frameworks/base/core/java/android/view/InputEventReceiver.java
class InputEventReceiver {
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),inputChannel, looper.getQueue());
}
}
/frameworks/base/core/jni/android_view_InputEventReceiver.cpp
class NativeInputEventReceiver {
static jlong nativeInit(env, clazz, receiverWeak, inputChannelObj, messageQueueObj) { // 简略写法
int fd = inputChannelObj->getFd();
messageQueueObj->getLooper()->addFd(fd, 0, events, this, NULL);
...
}
}
在《Android组件系列:再谈Handler机制(Native篇)》[10]这篇文章中,我们了解到:在 native 层同样拥有一套 Looper 机制。这套 Looper 不但可以处理 native 层消息,还支持监听 自定义 fd,这是本小节的重点。
https://juejin.cn/post/7146239048191836190
3、sockets[0] 传递到 IMS
/frameworks/base/services/core/java/com/android/server/wm/WindowState.java
class WindowState {
void openInputChannel(InputChannel outInputChannel) {
// 1. Client 端 InputChanenl 调用 transforTo() 方法传给 ViewRootImpl 的 mInputChannel
mClientChannel.transferTo(outInputChannel);
// 2. Server 端 InputChannel 存在 WindowState 的 mInputChannel 变量
InputManagerService.registerInputChannel(mInputChannel);
}
}
/frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
class InputManagerService {
void registerInputChannel(){
nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);
}
}
/frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
class NativeInputManager {
void nativeRegisterInputChannel(){
InputManager->getDispatcher()->registerInputChannel(inputChannel, inputWindowHandle, monitor);
}
}
/frameworks/native/services/inputflinger/InputDispatcher.cpp
class InputDispatcher {
status_t InputDispatcher::registerInputChannel(inputChannel,inputWindowHandle,monitor){
// 将代表窗口通信的 inputChannel ,以及代表窗口信息的 inputWindowHandle 封装成 Connection 对象
sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
mConnectionsByFd.add(fd, connection);
... // 省略 IMS 监听来自 client 的代码,这部分是处理 ANR 的逻辑
}
}
整个过程如下:
触摸事件到达 ViewRootImpl
/frameworks/base/core/jni/android_view_InputEventReceiver.cpp
class NativeInputEventReceiver {
static jlong nativeInit(env, clazz, receiverWeak, inputChannelObj, messageQueueObj) { // 简略写法
int fd = inputChannelObj->getFd();
messageQueueObj->getLooper()->addFd(fd, 0, events, this, NULL);
...
}
}
/frameworks/base/core/jni/android_view_InputEventReceiver.cpp
class NativeInputEventReceiver {
int NativeInputEventReceiver::handleEvent( receiveFd, events, data) {
switch (type) { // 简略写法,这是 consumeEvents() 方法中的逻辑
case AINPUT_EVENT_TYPE_KEY: {
inputEventObj = factory->createKeyEvent(); // 转换为按键类型事件 KeyEvent
}
case AINPUT_EVENT_TYPE_MOTION: {
inputEventObj = factory->createMotionEvent(); // 转转为触摸类型事件 MotionEvent
}
}
env->CallVoidMethod(receiverObj.get(),dispatchInputEvent, seq, inputEventObj); // 回调到 Java
}
}
/frameworks/base/core/java/android/view/InputEventReceiver.java
abstract class InputEventReceiver {
// Called from native code.
private void dispatchInputEvent(int seq, InputEvent event) {
onInputEvent(event);
}
}
/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl extends InputEventReceiver {
class WindowInputEventReceiver extends InputEventReceiver {
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
}
void enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {
...
// 执行消息入列以后,接着还有一个比较复杂的流水线过程,我们这里先不关心,直接来看 processPointerEvent() 方法
// input 消息到达 ViewRootImpl 后,Google 使用责任链的模式,将输入事件拆分为 KeyEvent 和 MotionEvent 两种类型,做进一步的处理
//
if(event.getType == input) processPointerEvent(event);
if(event.getType == key) processKeyEvent(event);
}
// 责任链模式,每个 InputStage 负责不同的功能,链中的某个 InputStage 的结果会影响对下一节点的执行,或停止继续分发等
// 在 ViewRootImpl#setView() 函数中指定责任链的前后顺序,这里不展开讨论,请查看参考资料列表中《这一次,带你彻底弄懂 Android 事件分发机制(外/内层责任链)》
class InputStage {
// 在 ViewRootImpl 中有好几个同名 processPointerEvent() 方法, eventTarget 通常是 ViewRootImpl 保存的 DecorView 对象,也就是会调用到 View#dispatchPointerEvent() 方法
private int processPointerEvent(QueuedInputEvent q) {
MotionEvent event = (MotionEvent)q.mEvent;
final View eventTarget = mView; // 通常是 DecorView
boolean handled = eventTarget.dispatchPointerEvent(event);
...
return handled ? FINISH_HANDLED : FORWARD;
}
private int processKeyEvent(QueuedInputEvent q) {
KeyEvent event = (KeyEvent)q.mEvent;
mView.dispatchKeyEvent(event)
final View eventTarget = mView; // 通常是 DecorView
boolean handled = eventTarget.dispatchPointerEvent(event);
...
return handled ? FINISH_HANDLED : FORWARD;
}
}
}
/frameworks/base/core/java/android/view/ViewRootImpl.java
class ViewRootImpl extends InputEventReceiver {
void enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {
if(event.getType == input) processPointerEvent(event);
if(event.getType == key) processKeyEvent(event);
}
class InputStage {
private int processPointerEvent(QueuedInputEvent q) {
MotionEvent event = (MotionEvent)q.mEvent;
final View eventTarget = mView; // 通常是 DecorView
boolean handled = eventTarget.dispatchPointerEvent(event);
...
return handled ? FINISH_HANDLED : FORWARD;
}
private int processKeyEvent(QueuedInputEvent q);// 处理 key 事件,忽略
}
}
ViewRootImpl#setView() 函数中指定了责任链执行的前后顺序,我们这里不展开讨论,感兴趣的同学可以查看参考资料列表中《这一次,带你彻底弄懂 Android 事件分发机制(外/内层责任链)》
触摸事件到达 DecorView
/frameworks/base/core/java/android/view/View.java
class View {
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
}
/frameworks/base/core/java/com/android/internal/policy/DecorView.java
class DecorView extends FrameLayout {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Window.Callback cb = mWindow.getCallback(); // 给 Activity 和 Dialog 拦截事件的机会
return cb != null ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
}
Window.Callback 是个接口,而 Activity 和 Dialog 都实现了这个接口。 DecorView 持有的 mWindow 的赋值路径是这样的:PhoneWindow#setContentView() -> installDecor() -> generateDecor() -> DecorView#setWindow(),不展开讨论了。
/frameworks/base/core/java/android/app/Activity.java
class Activity {
public boolean dispatchTouchEvent(MotionEvent ev) {
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
public boolean onTouchEvent(MotionEvent event) {
...
}
}
/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
class PhoneWindow {
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
}
/frameworks/base/core/java/com/android/internal/policy/DecorView.java
class DecorView extends FrameLayout {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
接下来的一整章,我们来复习 View / ViewGroup 事件分发的流程。
ACTION_DOWN:按下屏幕。 ACTION_MOVE:手指滑动。 ACTION_UP:抬起手指,离开屏幕。 ACTION_CANCEL:非正常抬起,几乎等同于 ACTION_UP ,通常是父视图拦截。 ACTION_POINTER_DOWN:多指触摸,表示已经有一只手指按下时,另有一只手指再次按下。 ACTION_POINTER_UP:多指触摸,表示屏幕上已经有多个手指,抬起其中一只手指后触发的事件。
ViewGroup 的消费、分发、拦截与放行
1、ViewGroup 四种场景
但是用户在按下 Button 后,接着又开始滑动屏幕了。
2、事件的放行和拦截
/frameworks/base/core/java/android/view/ViewGroup.java
class ViewGroup extends View {
public boolean dispatchTouchEvent(MotionEvent ev) {
int actionMasked = ev.getAction() & MotionEvent.ACTION_MASK;
TouchTarget newTouchTarget = null;
boolean intercepted;
boolean handled = false;
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
// 检查子视图是否调用了 requestDisallowInterceptTouchEvent(true) 请求放行
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev); // 子视图未请求放行,询问 ViewGroup 自身是否需要消费
} else {
intercepted = false;
}
} else {
intercepted = true; // 如果 mFirstTouchTarget 为空,并且事件类型不为 DOWN ,表示先前的事件也是 ViewGroup 自己消费的,无需执行分发,再次交给自己执行即可
}
return handled;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
//询问 ViewGroup 自身是否需要处理事件
return false;
}
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
}
}
3、事件的分发
/frameworks/base/core/java/android/view/ViewGroup.java
class ViewGroup extends View {
public boolean dispatchTouchEvent(MotionEvent ev) {
...
// 子视图未请求放行,ViewGroup 自身也不消费,进入分发流程
if (!intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN) {
for (int i = mChildrenCount - 1; i >= 0; i--) {
...// 检查子 View 是否可触摸,是否在触摸区域内等等,过程略
// 找到合适的子 View 后,调用 dispatchTransformedTouchEvent() 执行事件分发,如果返回 true,记录本次分发
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
newTouchTarget = addTouchTarget(child, idBitsToAssign); // 生成一个新的 TouchTarget 对象,用于记录消费的 View
break;
}
}
// 没有新的需要触摸事件的视图,那么,把链表尾部的 TouchTarget 拿出来,在下一步把事件分发给它
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
}
}
}
return handled;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {
boolean handled;
if (child == null) {
handled = super.dispatchTouchEvent(event); // ViewGroup 继承自 View ,这里调用的是 View#dispatchTouchEvent()
} else {
handled = child.dispatchTouchEvent(event);
}
return handled;
}
}
4、事件的消费
/frameworks/base/core/java/android/view/ViewGroup.java
class ViewGroup extends View {
public boolean dispatchTouchEvent(MotionEvent ev) {
...
// 跑到这,如果 mFirstTouchTarget 还是为空 ,表示这个事件 ViewGroup 自身要消费
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev,canceled,null,TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget target = mFirstTouchTarget;
// 遍历 TouchTarget 链表,执行事件分发,代码有去重操作,被我删了,即之前分发过的新添加的节点,不再执行分发
while (target != null) {
final TouchTarget next = target.next;
if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {
handled = true;
}
target = next;
}
}
return handled;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {
boolean handled;
if (child == null) {
handled = super.dispatchTouchEvent(event); // ViewGroup 继承自 View ,这里调用的是 View#dispatchTouchEvent()
} else {
handled = child.dispatchTouchEvent(event);
}
return handled;
}
}
View 的消费
//frameworks/base/core/java/android/view/View.java
class View {
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = onTouchEvent(event);
return result;
}
public boolean onTouchEvent(MotionEvent event) {
return false;
}
}
因为 View / ViewGroup 的事件分发比较简单,不像 Framework 的逻辑,绕来绕去的,事件分发的逻辑大部分时间都在内部做跳转,感兴趣的朋友自己去跟一遍源码很快就了解清楚了。
本篇文章稍微有点长,从硬件驱动,系统内核,到 Framework 都有涉及。其中,了解 IMS 的实现原理,APP 和 IMS 通信的建立,以及 ViewGroup 的 dispatchTouchEvent() 方法的事件派发逻辑,是本篇文章比较重要的内容。
在文章的最后,我们用张大伟老师《深入理解Android 卷III》书中的一段话作为本文总结:
https://book.douban.com/subject/26598458/
参考资料
电阻屏已经被智能手机抛弃,还有哪些应用场景?: https://rohm.eefocus.com/article/id-317
手机全贴合屏幕技术解析: https://blog.csdn.net/weixin_51554164/article/details/124965131
【Linux驱动】I2C子系统与触摸屏驱动 - @hongZ: https://blog.csdn.net/qq_39797956/article/details/118863217
【Linux驱动】input子系统与按键驱动 - @hongZ: https://blog.csdn.net/qq_39797956/article/details/117898095
Linux驱动开发|input子系统 - 安迪西: https://blog.csdn.net/Chuangke_Andy/article/details/122181549
从 0 开始学 Linux 驱动开发 - Hcamael:
https://paper.seebug.org/779/
Android(Linux) 输入子系统解析 - Andy Lee: http://huaqianlee.github.io/2017/11/23/Android/Android-Linux-input-system-analysis/
Android 如何上报 Touchevent 给应用层 - 董刚: https://dqdongg.com/c/touch/android/2014/07/10/Touch-inputevent.html
这一次,带你彻底弄懂 Android 事件分发机制(外/内层责任链) - 伤心的猪大肠: https://juejin.cn/post/6925336861137174541
Android 触摸事件分发机制(一)从内核到应用 一切的开始 - 吴迪: https://www.viseator.com/2017/09/14/android_view_event_1/
Android 触摸事件分发机制(二)原始事件消息传递与分发的开始 - 吴迪: https://www.viseator.com/2017/10/07/android_view_event_2/
Android事件分发机制二:核心分发逻辑源码解析 - 一只修仙的猿: https://juejin.cn/post/6920883974952714247
InputChannel and InputDispatcher in Android: https://xianzhu21.space/developer/inputchannel_inputdispatcher
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
点击 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!