Android 11 输入系统之InputDispatcher和应用窗口建立联系

InputDispatcher把输入事件传给应用之前,需要和应用窗口建立联系,了解了这个过程,就清楚了APP进程和InputDispatcher线程也就是SystemServer进程之间是如何传输数据了
我们向窗口addView的时候,都会调用到ViewRootImpl的setView方法,从这个方法开始分析(只关注和input有关的流程)

//frameworks\base\core\java\android\view\ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {synchronized (this) {if (mView == null) {//省略InputChannel inputChannel = null;if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {inputChannel = new InputChannel();//1}try {mOrigWindowType = mWindowAttributes.type;mAttachInfo.mRecomputeGlobalAttributes = true;collectViewAttributes();adjustLayoutParamsForCompatibility(mWindowAttributes);res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mDisplayCutout, inputChannel,mTempInsets, mTempControls);//2setFrame(mTmpFrame);//省略if (inputChannel != null) {if (mInputQueueCallback != null) {mInputQueue = new InputQueue();mInputQueueCallback.onInputQueueCreated(mInputQueue);}mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());//3}//省略}	}
}

注释1处新建一个InputChannel 对象。注释2处是一个远程调用,也就是服务端的addToDisplayAsUser方法,注意inputChannel参数在aidl文件中标记的是out,说明inputChannel是根据远端返回的数据初始化的。注释3处创建WindowInputEventReceiver对象。
先来看看addToDisplayAsUser方法

//frameworks\base\services\core\java\com\android\server\wm\Session.java@Overridepublic int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, int userId, Rect outFrame,Rect outContentInsets, Rect outStableInsets,DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,outInsetsState, outActiveControls, userId);}

直接调用WMS的addWindow方法

//frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq,LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,Rect outContentInsets, Rect outStableInsets,DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,int requestUserId) {//省略final WindowState win = new WindowState(this, session, client, token, parentWindow,appOp[0], seq, attrs, viewVisibility, session.mUid, userId,session.mCanAddInternalSystemWindow);//省略final boolean openInputChannels = (outInputChannel != null&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);if  (openInputChannels) {win.openInputChannel(outInputChannel);}//省略

调用WindowState的openInputChannel方法

//frameworks\base\services\core\java\com\android\server\wm\WindowState.java
void openInputChannel(InputChannel outInputChannel) {if (mInputChannel != null) {throw new IllegalStateException("Window already has an input channel.");}String name = getName();InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);//1mInputChannel = inputChannels[0];mClientChannel = inputChannels[1];mWmService.mInputManager.registerInputChannel(mInputChannel);//2mInputWindowHandle.token = mInputChannel.getToken();if (outInputChannel != null) {mClientChannel.transferTo(outInputChannel);//3mClientChannel.dispose();mClientChannel = null;} else {// If the window died visible, we setup a dummy input channel, so that taps// can still detected by input monitor channel, and we can relaunch the app.// Create dummy event receiver that simply reports all events as handled.mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);}mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);}

该方法主要完成以下工作:

  1. 创建socketpair,得到两个文件句柄,分别封装在InputChannel对象中
  2. 因为wms和inputDispatcher都是在SystemServer进程中,所以其中一个InputChannel即mInputChannel 只要直接注册就行了,不需要跨进程通信
  3. 将mClientChannel复制给outInputChannel,用于回传给APP应用进程

socketpair的创建过程

//frameworks\base\core\java\android\view\InputChannel.java
public static InputChannel[] openInputChannelPair(String name) {//省略return nativeOpenInputChannelPair(name);
}

java层的InputChannel只是一个壳,直接发起JNI调用

//frameworks\base\core\jni\android_view_InputChannel.cpp
static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,jclass clazz, jstring nameObj) {ScopedUtfChars nameChars(env, nameObj);std::string name = nameChars.c_str();sp<InputChannel> serverChannel;sp<InputChannel> clientChannel;status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);//1jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, nullptr);jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, serverChannel);jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, clientChannel);env->SetObjectArrayElement(channelPair, 0, serverChannelObj);//放入元素env->SetObjectArrayElement(channelPair, 1, clientChannelObj);//放入元素return channelPair;
}

注释1处创建socketpair,并创建两个C++层的InputChannel对象

//frameworks\native\libs\input\InputTransport.cpp
status_t InputChannel::openInputChannelPair(const std::string& name,sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {int sockets[2];if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {//创建socketpairstatus_t result = -errno;ALOGE("channel '%s' ~ Could not create socket pair.  errno=%d",name.c_str(), errno);outServerChannel.clear();outClientChannel.clear();return result;}/*设置buffer的大小为32k*/int bufferSize = SOCKET_BUFFER_SIZE;setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));sp<IBinder> token = new BBinder();//创建tokenstd::string serverChannelName = name + " (server)";android::base::unique_fd serverFd(sockets[0]);outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token);//创建服务端InputChannel对象std::string clientChannelName = name + " (client)";android::base::unique_fd clientFd(sockets[1]);outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token);//创建客户端InputChannel对象return OK;
}

可以看出,通过socketpair的两个fd,分别构造了Native层的InputChannel对象,同时,这个token也很重要,用户窗口中InputWindowInfo的token和这个是一致的
两个InputChannel构造完成后,其中一个需要通过binder回传给APP客户端(实际上就是写fd,然后客户端根据fd重新创建InputChannel),接下来分析服务端以及客户端的处理

注册InputChannel到InputDispatcher中

回到openInputChannel方法,InputChannel构造完成后,调用registerInputChannel,将服务端的InputChannel注册到InputDispatcher中

public void registerInputChannel(InputChannel inputChannel) {//省略nativeRegisterInputChannel(mPtr, inputChannel);
}

也是直接调用JNI方法

//frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp
static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,jlong ptr, jobject inputChannelObj) {NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,inputChannelObj);//取出InputChannelstatus_t status = im->registerInputChannel(env, inputChannel);//1//省略
}

注释1处,调用NativeInputManager的registerInputChannel方法

//frameworks\base\services\core\jni\com_android_server_input_InputManagerService.cpp
status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,const sp<InputChannel>& inputChannel) {ATRACE_CALL();return mInputManager->getDispatcher()->registerInputChannel(inputChannel);
}

继续调用InputDispatcher的registerInputChannel方法

//frameworks\native\services\inputflinger\dispatcher\InputDispatcher.cpp
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) {{ // acquire lock//省略sp<Connection> connection = new Connection(inputChannel, false /*monitor*/, mIdGenerator);int fd = inputChannel->getFd();mConnectionsByFd[fd] = connection;mInputChannelsByToken[inputChannel->getConnectionToken()] = inputChannel;mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);} // release lock// Wake the looper because some connections have changed.mLooper->wake();return OK;
}

首先根据inputChannel创建了一个Connection对象,然后取出inputChannel的fd,将该connection放入mConnectionsByFd数组,注意,数组的下标是fd,可以根据fd找到这个connection。同时把inputChannel放入mInputChannelsByToken数组
最后将该fd加到Looper中,Looper也是使用的epoll机制,当客户端写入事件时(主要是告知输入事件处理完毕),这个fd就表明有事件读入,就会调用handleReceiveCallback函数

客户端处理InputChannel

回到setView方法,客户端接收到服务端返回的InputChannel后,创建WindowInputEventReceiver对象,WindowInputEventReceiver继承自InputEventReceiver

//frameworks\base\core\java\android\view\InputEventReceiver.javapublic InputEventReceiver(InputChannel inputChannel, Looper looper) {//省略mInputChannel = inputChannel;mMessageQueue = looper.getQueue();mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),inputChannel, mMessageQueue);//1mCloseGuard.open("dispose");}

注释1处调用nativeInit方法

//frameworks\base\core\jni\android_view_InputEventReceiver.cpp
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,jobject inputChannelObj, jobject messageQueueObj) {sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,inputChannelObj);//取出//省略sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,receiverWeak, inputChannel, messageQueue);//创建NativeInputEventReceiver对象status_t status = receiver->initialize();//1//省略receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the objectreturn reinterpret_cast<jlong>(receiver.get());
}

注释1处调用NativeInputEventReceiver的initialize方法,在initialize方法中直接调用setFdEvents,将客户端inputChannel 中的fd加到Looper中

//frameworks\base\core\jni\android_view_InputEventReceiver.cpp
void NativeInputEventReceiver::setFdEvents(int events) {if (mFdEvents != events) {mFdEvents = events;int fd = mInputConsumer.getChannel()->getFd();if (events) {mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);//注意第4个参数为当前的NativeInputEventReceiver对象} else {mMessageQueue->getLooper()->removeFd(fd);}}
}

添加到Looper后,如果后续有事件到来,即InputDispatcher发送过来了输入事件,则会调用NativeInputEventReceiver自己的handleEvent方法

总结

可以看出InputDispatcher和客户端进程之间通讯是采用socket的方式,而因为这里已经明确是是它们两个之间通讯,所以这里使用了更加方便的socketpair,socketpair得到两个fd分别给InputDispatcher和客户端进程,其中使用binder将其中的一个fd回传给客户端。

InputDispatcher由于和wms是在同一个进程,所以可以直接使用这个fd。InputDispatcher会创建Connection对象,并维护两个数组。并将fd添加到Looper中。客户端拿到这个fd也同样是加到Looper中。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/324043.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

热爱电子值得做的电子制作实验

加我zkhengyang&#xff0c;进嵌入式音频系统研究开发交流答疑群(课题组) AM/FM收音机散件制作&#xff0c;磁带随声听散件&#xff0c;黑白电视机散件制作&#xff0c;功放散件制作&#xff0c;闪光灯散件制作&#xff0c;声控灯散件&#xff0c;等等&#xff0c;可提高动手能…

kali linux更新卡在libc6:amd64 (2.37-15)

适配于linux的windows子系统&#xff0c;wsl2&#xff0c;安装kali linux&#xff0c;运行 sudo apt update 卡在&#xff1a;Setting up libc6:amd64 (2.37-15) … 关机重启、重新修复执行也不行 解决办法&#xff1a;kill当前apt进程或者关机重启kali-linux&#xff0c;然…

【Linux】学习笔记

文章目录 [toc]第一章&#xff1a;基础篇01|课程介绍02|内容综述03|什么是Linux04|Linux的内核版本及常见发行版内核版本发行版本Red Hat Enterprise LinuxFedoraCentOSDebianUbuntu 05|安装VirtualBox虚拟机VirtualBox下载url 06|在虚拟机中安装Linux系统Linux安装镜像下载 07…

SpringBoot+vue实现右侧登录昵称展示

目录 1. 定义User数据 1.1.在created方法获取数据 1.2.头部导航栏绑定User数据 1.3.在data中定义User数据 2. 获取数据 2.1.接收父组件传递的值 2.2.展示数据 3. 页面效果 在SpringBoot和 Vue.js 结合的项目中实现右侧登录昵称展示&#xff0c;通常涉及到前端的用户界面…

ROS机器人入门:机器人系统仿真【学习记录】——2

承接上一篇博客&#xff1a; ROS机器人入门&#xff1a;机器人系统仿真【学习记录】——1-CSDN博客 我们先前结束了&#xff08;上一篇博客中&#xff09;&#xff1a; 1. 概述 2. URDF集成Rviz基本流程 3. URDF语法详解 4. URDF优化_xacro 下面让我们继续学习ROS机器人…

会话劫持攻击就在我们身边,我们要如何防范

会话劫持攻击&#xff08;Session Hijacking&#xff09;是一种网络攻击方式&#xff0c;攻击者通过某种手段获取到用户的会话标识&#xff08;Session ID&#xff09;&#xff0c;然后使用这个会话标识冒充合法用户进行恶意操作。这种攻击方式允许攻击者以合法用户的身份访问受…

FFmpeg常用命令详解与实战指南

下载地址&#xff1a;Releases BtbN/FFmpeg-Builds (github.com) 1. 获取视频信息 使用FFmpeg获取视频信息是最基本的操作之一。你可以使用-i选项指定输入文件&#xff0c;然后使用FFmpeg内置的分析器来获取视频的各种信息&#xff0c;包括视频编解码器、音频编解码器、分辨…

《Mask2Former》算法详解

文章地址&#xff1a;《Masked-attention Mask Transformer for Universal Image Segmentation》 代码地址&#xff1a;https://github.com/facebookresearch/Mask2Former 文章为发表在CVPR2022的一篇文章。从名字可以看出文章像提出一个可以统一处理各种分割任务&#xff08;…

智慧公厕,运用数据提升公共厕所管理水平!

随着城市人口的增加和生活水平的提高&#xff0c;公共厕所的管理变得越来越重要。传统的厕所管理方式已经无法满足人们对卫生、便利和舒适的需求。而智慧公厕作为新一代公厕管理方式&#xff0c;通过运用数据技术和大数据分析手段&#xff0c;彻底改变了公厕管理的模式&#xf…

远程桌面连接不上怎么连服务器,原因是什么?如何解决?

远程桌面连接不上怎么连服务器&#xff0c;原因是什么&#xff1f;如何解决&#xff1f; 面对远程桌面连接不上的困境&#xff0c;我们有办法&#xff01; 当你尝试通过远程桌面连接服务器&#xff0c;但遭遇连接失败的挫折时&#xff0c;不要慌张。这种情况可能由多种原因引起…

26、Qt使用QFontDatabase类加载ttf文件更改图标颜色

一、图标下载 iconfont-阿里巴巴矢量图标库 点击上面的链接&#xff0c;在打开的网页中搜索自己要使用的图标&#xff0c;如&#xff1a;最大化 找到一个自己想用图标&#xff0c;选择“添加入库” 点击“购物车”图标 能看到刚才添加的图标&#xff0c;点击“下载代码”(需要…

4D 成像毫米波雷达:新型传感器助力自动驾驶

1 感知是自动驾驶的首要环节&#xff0c;高性能传感器必不可少 感知环节负责对侦测、识别、跟踪目标&#xff0c;是自动驾驶实现的第一步。自动驾驶的实现&#xff0c;首先要能够准确理解驾驶环境信息&#xff0c;需要对交通主体、交通信号、环境物体等信息进行有效捕捉&#x…

数据可视化训练第二天(对比Python与numpy中的ndarray的效率并且可视化表示)

绪论 千里之行始于足下&#xff1b;继续坚持 1.对比Python和numpy的性能 使用魔法指令%timeit进行对比 需求&#xff1a; 实现两个数组的加法数组 A 是 0 到 N-1 数字的平方数组 B 是 0 到 N-1 数字的立方 import numpy as np def numpy_sum(text_num):"""…

一起深度学习(AlexNet网络)

AlexNet神经网络 代码实现&#xff1a; 代码实现&#xff1a; import torch from torch import nn from d2l import torch as d2lnet nn.Sequential(# 采用了11*11的卷积核来捕捉对象&#xff0c;因为原始输入数据比较大#步幅为4 &#xff0c;可减少输出的高度核宽度。#输出通…

全栈开发之路——前端篇(6)生命周期和自定义hooks

全栈开发一条龙——前端篇 第一篇&#xff1a;框架确定、ide设置与项目创建 第二篇&#xff1a;介绍项目文件意义、组件结构与导入以及setup的引入。 第三篇&#xff1a;setup语法&#xff0c;设置响应式数据。 第四篇&#xff1a;数据绑定、计算属性和watch监视 第五篇 : 组件…

基于springboot实现疾病防控综合系统项目【项目源码+论文说明】

基于springboot实现疾病防控综合系统演示 摘要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何一个企业或者个人会忽视&#xff0c;如何让信息急速传递&#xff0c;并且归档储存查询&#xff0c;采用之前的纸张记录模式已经不符合当前使用要求了。所以&…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-11.1,11.2-BSP文件目录组织

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

Labels and Databases for Mac:强大的标签与数据库管理工具

Labels and Databases for Mac是一款集标签制作与数据库管理于一体的强大工具&#xff0c;专为Mac用户打造&#xff0c;旨在提供高效、便捷的标签制作与数据管理体验。 这款软件拥有丰富的内置标签格式&#xff0c;用户可轻松创建各种标签、信封和卡片&#xff0c;满足个性化需…

【CSDN搜材料的小技巧】怎么快速查到高质量最新的内容

问题描述: 我最近搜CSDN已经搜累了&#xff0c;好多东西明显是有问题的&#xff0c;还有一堆人复制粘贴&#xff0c;从海量文章中提取出最新且高质量文章成了当务之急&#xff01; 解决方案: 我本来想写个爬虫按照文章的收藏或者点赞排序的&#xff0c;无意中看到了这篇文章…

基于Spring Cloud的房产销售平台设计与实现

基于Spring Cloud的房产销售平台设计与实现 开发语言&#xff1a;Java 框架&#xff1a;SpringCloud JDK版本&#xff1a;JDK1.8 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 前台首页界面&#xff0c;前台首页包括房源信息、…