Android输入法IME(二)

2. IME初始化启动流程

2.1. IME客户端(IMM)初始化流程

涉及代码文件路径: frameworks/base/core/java/android/view/ViewRootImpl.java frameworks/base/core/java/android/view/WindowManagerGlobal.java frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java frameworks/base/core/java/com/android/internal/view/IInputMethodClient.aidl frameworks/base/core/java/com/android/internal/view/IInputContext.aidl frameworks/base/core/java/com/android/internal/view/IInputMethodManager.aidl frameworks/base/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java

2.1.1. 函数流程梳理

# 每次新增窗口window时,都会实例化ViewRootImpl,而ViewRootImpl在获取IWindowSession时会检查输入法是否已经初始化
ViewRootImpl.java -- 初始化构造函数,调用WindowManagerGlobal.getWindowSession()---> WindowManagerGlobal.java -- getWindowSession()调用InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary() 实例化全局调用InputMethodManager,即初始化IMM---> InputMethodManager.java -- ensureDefaultInstanceForDefaultDisplayIfNecessary()调用forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper()),入参默认displayID和looper# 此处也说明,对于APP层,IMM有且只有一个实例,每次创建ViewRootImpl都会检查IMM是否实例化完成---》 调用forContextInternal函数,先从缓存Map中查询是否有IMM实例,如果没有则创建IMM实例,并添加到Map中---》 调用createInstance创建实例,然后在三目运算中默认固定调用createRealInstance(displayId, looper)---》 调用createRealInstance函数,   (1)获取输入法服务service,即Context.INPUT_METHOD_SERVICE(service是AIDL接口文件IInputMethodManager.aidl);(2)new InputMethodManager(service, displayId, looper)创建实例---》 InputMethodManager构造函数---》 new IInputConnectionWrapper 创建虚拟的输入法上下文,主要用于监听输入法服务的激活状态,接受输入事件# 添加IMM实例到输入法service服务中# 此处两个入参都是AIDL接口类型的对象# (1)IInputMethodClient.aidl:输入法客户端, 主要用于报告输入法当前的状态, 让APP应用端的IMM做出相应的处理# (2)IInputContext.aidl:输入法上下文, 主要用于操作字符输入操作, 让当前接收字符的view进行处理(3)调用service.addClient(imm.mClient //[AIDL对象,即IInputMethodClient], imm.mIInputContext//[AIDL对象,IInputContext], displayId)---> IInputMethodManager.aidl -- 调用addClient(跨进程通信到IMMS)---> 服务端InputMethodManagerService.java "extends IInputMethodManager.Stub" --  调用addClient函数,创建ClientState对象---》 调用内部静态类ClientState的构造函数,保存client相关状态属性

综上代码流程梳理,可以看出:

  1. 对于每个APP应用,IMM有且只有一个实例,并且每次创建ViewRootImpl时,都会检查IMM是否已经实例化成功
  2. 实例化IMM对象时,会涉及到两个AIDL接口文件,一个用于应用端IMM处理输入法当前状态,一个用于输入法上下文,创建一个虚拟的InputContext代表输入空间,用于监听输入法激活状态
  3. 实例化过程中会有个displayid,用于多屏幕显示(通常情况下默认是default display=0)
  4. 实例化最后,会通过AIDL的addClient接口函数,将IMM添加到IMMS中,如此IMM实例化完成

2.1.2. 代码详细说明

//ViewRootImpl.javapublic ViewRootImpl(Context context, Display display) {this(context, display, WindowManagerGlobal.getWindowSession(),false /* useSfChoreographer */);}//WindowManagerGlobal.javapublic static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {if (sWindowSession == null) {try {//调用该函数,初始化IMMInputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();......} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return sWindowSession;}}//InputMethodManager.javapublic static void ensureDefaultInstanceForDefaultDisplayIfNecessary() {//默认default displayforContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());}private static InputMethodManager forContextInternal(int displayId, Looper looper) {final boolean isDefaultDisplay = displayId == Display.DEFAULT_DISPLAY;synchronized (sLock) {//从缓存Map中查找是否由default display的IMM实例InputMethodManager instance = sInstanceMap.get(displayId);//如果存在实例,则直接返回if (instance != null) {return instance;}//初始化创建实例instance = createInstance(displayId, looper);//如果是用于default display使用,则存储到sInstance中作为全局单例实例if (sInstance == null && isDefaultDisplay) {sInstance = instance;}//将IMM实例保存到Map中sInstanceMap.put(displayId, instance);return instance;}}private static InputMethodManager createInstance(int displayId, Looper looper) {//isInEditMode固定返回false,直接调用createRealInstancereturn isInEditMode() ? createStubInstance(displayId, looper): createRealInstance(displayId, looper);}private static InputMethodManager createRealInstance(int displayId, Looper looper) {//IInputMethodManager是AIDL接口文件,用于跨进程通信到IMMS(InputMethodManagerService)final IInputMethodManager service;try {//获取serviceservice = IInputMethodManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));} catch (ServiceNotFoundException e) {throw new IllegalStateException(e);}//创建IMM实例final InputMethodManager imm = new InputMethodManager(service, displayId, looper);//将PID/UID和每个IME客户端关联,然后作为跨进程服务端IPC使用梳理//如果作为同进程内调用梳理,则需要确保Binder.getCalling{Pid, Uid}()返回Process.my{Pid, Uid}()//无论哪种情况,都要调用Binder的{clear, restore}CallingIdentity()函数,对跨进程没有影响,对同进程可以满足需求实现final long identity = Binder.clearCallingIdentity();try {// 添加 IMM 实例到输入法服务// imm.mClient 是一个aidl对象, mClient即new IInputMethodClient.Stub(),AIDL接口// imm.mIInputContext 是一个aidl对象, IInputContext,AIDL接口service.addClient(imm.mClient, imm.mIInputContext, displayId);} catch (RemoteException e) {e.rethrowFromSystemServer();} finally {Binder.restoreCallingIdentity(identity);}return imm;}//InputMethodManagerService.java//由每个APP应用进程调用,作为输入法开始与交互的准备@Overridepublic void addClient(IInputMethodClient client, IInputContext inputContext,int selfReportedDisplayId) {//获取调用的uid和pid(即InputMethodManager实际运行所在的UID/PID)//两种情况下调用此方法://1.IMM正在另一个进程中实例化//2.IMM正在同一个进程中实例化,final int callerUid = Binder.getCallingUid();final int callerPid = Binder.getCallingPid();synchronized (mMethodMap) {// TODO: Optimize this linear search.final int numClients = mClients.size();for (int i = 0; i < numClients; ++i) {final ClientState state = mClients.valueAt(i);if (state.uid == callerUid && state.pid == callerPid&& state.selfReportedDisplayId == selfReportedDisplayId) {throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid+ "/displayId=" + selfReportedDisplayId + " is already registered.");}}//利用IBinder.deathRecipient监听client存活状态//如果client的Binder死亡,则将Client从缓存Map中移除final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);try {client.asBinder().linkToDeath(deathRecipient, 0);} catch (RemoteException e) {throw new IllegalStateException(e);}//此处不验证displayID,后续每当客户端需要与指定的交互时,就需要检查displayID//此处创建ClientState对象,将client和inputContext缓存进去,然后将该对象保存到缓存Map mClients中mClients.put(client.asBinder(), new ClientState(client, inputContext, callerUid,callerPid, selfReportedDisplayId, deathRecipient));}}

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

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

相关文章

【kubernetes】k8s的面试宝典,等你来拿哦

目录 一、pod的生命周期 二、创建 pod 的工作流程 三、ingres 有哪些组件并且描述出组件作用 &#xff1f; 四、ingress 的工作原理 五、ingress 暴露服务的方式 六、pod 的组成 七、pod的本身性质&#xff08;pod的种类与说明&#xff09; 八、k8s命令 8.1在k8s中如何…

零基础入门学习Python第二阶04SQL详解03

MySQL 新特性 JSON类型 很多开发者在使用关系型数据库做数据持久化的时候&#xff0c;常常感到结构化的存储缺乏灵活性&#xff0c;因为必须事先设计好所有的列以及对应的数据类型。在业务发展和变化的过程中&#xff0c;如果需要修改表结构&#xff0c;这绝对是比较麻烦和难…

AppStore搜索优化方法(ASO)

在竞争激烈的 App Store 中&#xff0c;如何让你的应用脱颖而出&#xff0c;吸引更多用户下载&#xff1f;其实从官方文档描述中可以总结一些优化技巧&#xff0c;这是官方描述地址&#xff1a;搜索优化 – App Store – Apple Developer。通过官方描述我们可以总结到影响搜索结…

commander.js 入门指南:构建强大的命令行界面 (全网最全教程)

在Node.js的世界里&#xff0c;创建用户友好的命令行界面&#xff08;CLI&#xff09;对于许多应用程序和工具来说至关重要。Commander.js 是一个广受欢迎的 Node.js 包&#xff0c;它为开发者提供了一套简洁而强大的 API&#xff0c;用于快速创建功能完备、用户友好的命令行界…

如何用TCC方案轻松实现分布式事务一致性

本文作者:小米,一个热爱技术分享的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! 哈喽,大家好!我是小米,一个热爱技术的活力小青年,今天要和大家分享的是一种在分布式系统中实现事务的一种经典方案——TCC(Try Confirm Canc…

【Ubuntu】超详细安装Ubuntu系统

鉴于有些小伙伴在安装Ubuntu系统的时候遇到很多问题&#xff0c;因此打算编写一篇记录一下安装Ubuntu系统的整个过程~互相学习&#xff01; 一、制作U盘启动 准备一个大于8G以上的U盘&#xff0c;这里我使用的是16G的U盘下载UltraISO工具 网站地址&#xff1a;UltraISO准备Ub…

C++ Primer 第五版 第15章 面向对象程序设计

面向对象程序设计基于三个基本概念&#xff1a;数据抽象、继承和动态绑定。 继承和动态绑定对编写程序有两方面的影响&#xff1a;一是我们可以更容易地定义与其他类相似但不完全相同的新类&#xff1b;二是在使用这些彼此相似的类编写程序时&#xff0c;我们可以在一定程度上…

HTML静态网页成品作业(HTML+CSS)—— 金宝贝儿童教育机构介绍网页(2个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有2个页面。 二、作品演示 三、代…

Stable diffusion prompts 使用语法、参数讲解、插件安装教程

Stable diffusion prompts 使用语法、参数讲解、插件安装教程 本文基于 Stable diffusion WebUI 进行讲解&#xff08;安装在 AutoDL 上&#xff0c;安装在本地电脑上的也同样适用本教程&#xff09;。 初始界面&#xff1a; 文件目录结构&#xff1a; 上图红框中的 4 个文件…

requests模块编写漏洞检测工具

#尝试使用python登录pikachu爆破模块 #发送post数据包&#xff0c;包含用户名密码&#xff0c;对接受到的响应进行判断&#xff0c;如何为登录成功 #爆破密码 with open(passwor.txt,r) as f: passwordf.readlines() for i in password: data {username: admin, password: i, …

数据结构——算法和算法效率的度量

目录 一、引言 二、算法 1 算法的基本概念 2 算法的复杂度 2.1 时间复杂度 2.1.1 概念 2.1.2 大O的渐进表示 3 算法的空间复杂度 3.1 概念 3.2 实例 4 实例分析 5 结论 一、引言 大家在写代码的时候有没有发现写同样功能的代码有多种不同的写法&#xff0c;而不同的代…

51种企业应用架构模式详解

01 什么是企业应用 我的职业生涯专注于企业应用&#xff0c;因此&#xff0c;这里所谈及的模式也都是关于企业应用的。&#xff08;企业应用还有一些其他的说法&#xff0c;如“信息系统”或更早期的“数据处理”。&#xff09;那么&#xff0c;这里的“企业应用”具体指的是什…

[原型资源分享]经典产品饿了么UI模版部件库

​部件库预览链接&#xff1a;https://f13gm0.axshare.com 支持版本: Axrure RP 8 文件大小: 3MB 文档内容介绍 基本部件&#xff1a;表单样式&#xff1a;12款、数据样式&#xff1a;10款、服务样式&#xff1a;6款、导航&#xff1a;5款、业务组件&#xff1a;7款、 模板…

python把简体中文转换为繁体中文

Python 可以使用第三方库来将简体中文&#xff08;简体中文&#xff09;转换为繁体中文&#xff08;繁体中文&#xff09;。一个常用的库是 opencc-python-reimplemented&#xff0c;它是 Open Chinese Convert (OpenCC) 的 Python 实现&#xff0c;OpenCC 是一个开源的中文简繁…

MySQL之查询性能优化(三)

查询性能优化 重构查询的方式 在优化有问题的查询时&#xff0c;目标应该是找到一个更优的方法获得实际需要的记过——而不是一定总是需要从MySQL获取一模一样的结果集。有时候&#xff0c;可以将查询转换一种写法让其返回一样的结果&#xff0c;但是性能更好。但也可以通过修…

Python魔法之旅-魔法方法(14)

目录 一、概述 1、定义 2、作用 二、应用场景 1、构造和析构 2、操作符重载 3、字符串和表示 4、容器管理 5、可调用对象 6、上下文管理 7、属性访问和描述符 8、迭代器和生成器 9、数值类型 10、复制和序列化 11、自定义元类行为 12、自定义类行为 13、类型检…

在Debian系统上赋予普通用户ping 权限

在Debian系统上&#xff0c;普通用户默认情况下没有权限使用 ping 命令&#xff0c;因为它需要发送 ICMP 包&#xff0c;这通常需要 root 权限。为了允许普通用户使用 ping&#xff0c;可以设置 ping 命令的 setuid 位。以下是具体的步骤&#xff1a; 查找 ping 命令的位置&am…

2024年度自贡市社会民生重大科技计划项目申报要求、时间流程

一、申报要求 申报项目需符合以下申报要求和申报指南要求&#xff0c;申报资料需在“自贡市科技综合业务服务平台”中的“自贡市重点科技计划项目管理系统”上传。 &#xff08;一&#xff09;项目申报单位要求。 1.项目申报单位包括项目牵头单位和项目合作单位。 2.多家单…

【Python】pyinstaller打包时添加详细信息

在要被打包的py文件同级目录新建version.txt&#xff0c;写入以下内容 # UTF-8 # # For more details about fixed file info ffi see: # http://msdn.microsoft.com/en-us/library/aa381058.aspx # VSVersionInfo(ffiFixedFileInfo(filevers(1, 4, 0, 5),prodvers(1, 4, 0, 5…

SpringBoot使用RabbitMQ实现延迟队列

SpringBoot使用RabbitMQ实现延迟队列 需求和目标名词解释实现方式引入依赖添加配置文件配置类死信队列消费者即时队列消费者延迟消息发送结果注意 需求和目标 商城系统&#xff0c;用户下单后若15分钟内仍未完成支付&#xff0c;则自动取消订单&#xff0c;若已支付&#xff0c…