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方法

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中

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.hqwc.cn/news/681572.html

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

相关文章

用户行为分析与内容创新:Kompas.ai的数据驱动策略

在数字化营销的今天&#xff0c;用户行为数据分析已成为内容创新和策略调整的核心。通过深入理解用户的行为模式和偏好&#xff0c;品牌能够创造出更具吸引力和相关性的内容&#xff0c;从而实现精准营销。本文将探讨用户行为数据分析在内容创新和策略调整中的价值&#xff0c;…

洗地机什么品牌好?洗地机怎么选?618洗地机选购指南

随着科技的飞速发展&#xff0c;洗地机以其高效的清洁能力、稳定的性能和用户友好的设计而闻名&#xff0c;不仅可以高效吸尘、拖地&#xff0c;还不用手动洗滚布&#xff0c;已经逐渐成为现代家庭不可或缺的清洁助手。然而&#xff0c;在众多品牌和型号中&#xff0c;如何选择…

【前端热门框架【vue框架】】——事件处理与表单输入绑定以及学习技巧,让学习如此简单

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;程序员-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

DEV--C++小游戏(吃星星(0.2))

目录 吃星星&#xff08;0.2&#xff09; 简介 分部代码 头文件&#xff08;增&#xff09; 命名空间变量&#xff08;增&#xff09; 副函数&#xff08;新&#xff0c;增&#xff09; 清屏函数 打印地图函数&#xff08;增&#xff09; 移动函数 选择颜色&#xff…

Docker Desktop 修改容器的自启动设置

Docker Desktop 允许用户控制容器的自启动行为。如果你不希望某个容器在 Docker 启动时自动启动&#xff0c;你可以通过以下步骤来更改设置&#xff1a; 1. 打开 Docker Desktop 应用。 2. 点击右上角的设置&#xff08;Settings&#xff09;按钮&#xff0c;或者使用快捷键 Cm…

win11环境下,Idea 快捷键shift+f6重命名无法使用

微软自带输入法导致 单击win弹出搜索框

连续31年稳健增长,73.25%分红率再创新高,伊利的实力是什么?

文 | 螳螂观察 作者 | 易不二 4月29日&#xff0c;伊利股份发布2023年年报及2024年一季报。 年报显示&#xff0c;2023年&#xff0c;伊利实现营业总收入1261.79亿元&#xff0c;归母净利润104.29亿元&#xff0c;双创历史新高&#xff0c;实现连续31年稳健增长。公司拟每10…

「网络流 24 题」餐巾计划【费用流】

「网络流 24 题」餐巾计划 思路 我们先建立超级源点 S S S 和超级汇点 T T T&#xff0c;对于每一天&#xff0c;我们将其拆分成两个点 A i A_i Ai​ 和 B i B_i Bi​&#xff0c;其中 A i A_i Ai​ 表示这一天实际消耗的餐巾&#xff0c;连边 S → ∞ A i S \stackrel{…

基于OceanBase+Flink CDC,云粒智慧实时数仓演进之路

摘要&#xff1a;本文整理自云粒智慧高级技术专家付大伟在 4 月 20 日的 2024 OceanBase 开发者大会上的分享&#xff0c;讲述了其数据中台在传统数仓技术框架下做的一系列努力后&#xff0c;跨进 FlinkCDC 结合 OceanBase 的实时数仓演进过程。 内容主要分为以下几个部分: 业务…

HuggingFace烧钱做了一大批实验,揭示多模态大模型哪些trick真正有效

构建多模态大模型时有很多有效的trick&#xff0c;如采用交叉注意力机制融合图像信息到语言模型中&#xff0c;或直接将图像隐藏状态序列与文本嵌入序列结合输入至语言模型。 但是这些trick为什么有效&#xff0c;其计算效率如何&#xff0c;往往解释得很粗略或者或者缺乏充分…

SpringBoot自动配置源码解析

SpringBootApplication Spring Boot应用标注 SpringBootApplication 注解的类说明该类是Spring Boot 的主配置类&#xff0c;需要运行该类的main方法进行启动 Spring Boot 应用 SpringBootConfiguration 该注解标注表示标注的类是个配置类 EnableAutoConfiguration 直译&#…

Linux Ubuntu(玩客云) qBittorrent docker BT下载(qbittorrent 密码错误无法登录 ip地址被禁止登录等)

提示&#xff1a; 需要提前安装Docker 根据qBittorrent官网的更新日志https://www.qbittorrent.org/news &#xff0c;4.6.1.0包含一个重大更新。可以看到自4.6.1.0开始&#xff0c;qBittorrent将弃用adminadmin默认密码&#xff0c;采用随机密码&#xff0c;将在终端控制台输出…