Android 输入法框架简介

每种平台都有自己的输入法框架. GNU/Linux 桌面环境有多种输入法框架, 比如 ibus, fcitx 等. 但是 Android 操作系统只有一种, 是统一提供的输入法框架.


相关链接:

  • 《ibus 源代码阅读 (1)》 https://blog.csdn.net/secext2022/article/details/136099328
  • https://developer.android.google.cn/develop/ui/views/touch-and-input/creating-input-method

目录

  • 1 Android 输入法框架
  • 2 实现一个简单的 Android 输入法
  • 3 测试
  • 4 总结与展望
  • 附录 1 相关代码

1 Android 输入法框架

在这里插入图片描述

这个图看起来和 ibus 输入法框架差不多, 都有具体的输入法 (engine), 接受输入的应用, 以及系统服务 (输入法框架).

在 Android 系统中, 输入法, 以及接受输入的应用, 都以应用 (apk) 的形式存在. 可以很方便的安装新的输入法, 就和安装普通的应用一样.

2 实现一个简单的 Android 输入法

要想详细的了解 Android 系统的输入法接口, 最好的方法还是自己做一个输入法.

  • (1) 打开 Android Studio, 随意创建一个新的空白应用.

  • (2) 编写一个新的类, 继承 InputMethodService https://developer.android.google.cn/reference/android/inputmethodservice/InputMethodService

    比如创建文件 app/src/main/java/io/github/fm_elpac/pmim_apk/im/PmimService.kt (有省略):

    package io.github.fm_elpac.pmim_apk.imimport android.inputmethodservice.InputMethodServiceimport android.webkit.WebView
    import android.webkit.JavascriptInterfaceclass PmimService : InputMethodService() {// 生命周期函数override fun onCreate() {super.onCreate()// 用于调试 (服务生命周期), 下同println("PmimService.onCreate()")}override fun onCreateInputView(): View {println("PmimService.onCreateInputView()")// 创建 WebViewvar w = WebView(this)w.getSettings().setJavaScriptEnabled(true)class 接口 {@JavascriptInterfacefun commit(t: String) {im_commitText(t)}}w.addJavascriptInterface(接口(), "pmim")w.loadUrl("file:///android_asset/ui/index.html")return setH(w)}// 预留接口: 输入文本fun im_commitText(text: String) {currentInputConnection.commitText(text, 1)}fun sendKeyEvent(event: KeyEvent) {currentInputConnection.sendKeyEvent(event)}
    

    这个类就相当于自己实现的一个输入法了.

    其中重要函数 onCreateInputView() 创建软键盘, 就是显示在屏幕底部的触摸输入区域.

  • (3) 添加输入法相关信息.

    文件 app/src/main/res/xml/im.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <input-methodxmlns:android="http://schemas.android.com/apk/res/android"android:settingsActivity="io.github.fm_elpac.pmim_apk.MainActivity"android:icon="@mipmap/ic_launcher"><subtypeandroid:label="@string/im_label"android:name="@string/im_name"android:imeSubtypeLocale="zh_CN"android:imeSubtypeMode="keyboard"/><subtypeandroid:label="@string/im_label_en"android:name="@string/im_name"android:imeSubtypeLocale="en_US"android:imeSubtypeMode="keyboard"/>
    </input-method>
    

    文件 app/src/main/res/values/strings.xml:

    <resources><string name="app_name">胖喵拼音</string><string name="im_name">胖喵拼音</string><string name="im_label">中文 (中国)</string><string name="im_label_en">Chinese (zh_CN)</string>
    </resources>
    

    im.xml 里面是输入法的信息, 操作系统 (设置输入法) 需要使用.

  • (4) 清单文件 app/src/main/AndroidManifest.xml (有省略):

    <!-- Android 输入法服务 -->
    <serviceandroid:name=".im.PmimService"android:exported="true"android:label="@string/im_name"android:permission="android.permission.BIND_INPUT_METHOD"><intent-filter><action android:name="android.view.InputMethod" /></intent-filter><!-- 必须有此元数据, 输入法才能在系统设置中出现 --><meta-data android:name="android.view.im" android:resource="@xml/im" />
    </service>
    

    前面编写的类 PmimService 是传说中的 Android 四大组件 之一 (服务), 所以必须在清单文件中声明.

  • (5) 最后实现用户界面 (底部的软键盘).

    文件 app/src/main/assets/ui/index.html:

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>测试输入法键盘</title>
    <style>body {background-color: #FFF3E0;
    }img {width: 150px;height: 150px;
    }.b {position: fixed;top: 0;left: 0;width: 100%;height: 100%;box-sizing: border-box;border-top: solid 8px #FF9800;display: flex;align-items: center;justify-content: space-around;
    }
    </style>
    </head>
    <body><div class="b"><img id="m" src="./m.jpg" /><img id="q" src="./q.png" /></div><script>
    function 输入(t) {console.log(t);pmim.commit(t);
    }function 初始化() {const m = document.getElementById("m");const q = document.getElementById("q");m.addEventListener("click", () => 输入("喵"));q.addEventListener("click", () => 输入("穷"));
    }初始化();
    </script>
    </body>
    </html>
    

3 测试

又到了喜闻乐见的测试环节.

  • (1) 编译 apk (相关重要文件的完整代码请见 附录 1):

    JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleDebug
    

    编译生成的 apk 文件位于: app/build/outputs/apk/debug/app-debug.apk

  • (2) 安装 apk.

    使用 USB 数据线连接手机和 PC, 然后:

    > adb devices
    List of devices attached
    268bca3e	device> adb install app-debug.apk
    Performing Streamed Install
    Success
    
  • (3) 在手机的系统设置里, 启用新的输入法:

    在这里插入图片描述

  • (4) 找一个能输入的地方, 切换输入法:

    在这里插入图片描述

  • (5) 然后就可以愉快的输入啦 ~

    在这里插入图片描述

    嗯, 点击这俩图标分别可以输入一个汉字.


我们来分析一下, 点击图标的时候发生了什么.

  • (1) 用户界面 (网页) js 代码调用 pmim.commit()

  • (2) 其中 pmim 是 kotlin 代码调用 addJavascriptInterface() 添加的接口.

  • (3) 最后 kotlin 代码调用了 Android 输入法框架的接口 currentInputConnection.commitText(), 最终实现了文字的输入 (撒花 ~~)

4 总结与展望

各个平台的输入法框架的整体工作原理都差不多, 输入法框架在中间做管理, 一边是输入法, 一边是接受输入的应用.

和 ibus 相比, Android 输入法框架使用起来要简单容易很多, Android 官方文档写的也很清楚, 好评 !

今天实现了输入俩字, 距离实现完整的输入法还会远嘛 ?


彩蛋: 本文使用刚开发的 ibus 输入法编写. 编写本文的过程中顺便又修复了一个 BUG (输入 ).

在这里插入图片描述

附录 1 相关代码

  • app/src/main/java/io/github/fm_elpac/pmim_apk/im/PmimService.kt
package io.github.fm_elpac.pmim_apk.imimport android.inputmethodservice.InputMethodService
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.LinearLayoutimport android.webkit.WebView
import android.webkit.JavascriptInterface
import android.view.ViewGroup.LayoutParams// Android 输入法服务, 仅关注面向 Android 系统的接口部分
class PmimService : InputMethodService() {// 生命周期函数override fun onCreate() {super.onCreate()// 用于调试 (服务生命周期), 下同println("PmimService.onCreate()")}// 设置软键盘高度private fun setH(view: View): View {val h = 350f;// dp -> pxval d = resources.displayMetrics.densityval px = h * d + 0.5fprintln("  dp = " + h + "  d = " + d + "  px = " + px)view.setLayoutParams(LayoutParams(-1, px.toInt()))val l = LinearLayout(this)l.addView(view)return l}override fun onCreateInputView(): View {println("PmimService.onCreateInputView()")// 创建 WebViewvar w = WebView(this)w.getSettings().setJavaScriptEnabled(true);class 接口 {@JavascriptInterfacefun commit(t: String) {im_commitText(t)}}w.addJavascriptInterface(接口(), "pmim")w.loadUrl("file:///android_asset/ui/index.html")return setH(w)}override fun onBindInput() {super.onBindInput()println("PmimService.onBindInput()")}override fun onUnbindInput() {super.onUnbindInput()println("PmimService.onUnbindInput()")}// 软键盘显示override fun onStartInputView(info: EditorInfo, restarting: Boolean) {println("PmimService.onStartInputView()")}// 软键盘隐藏override fun onFinishInput() {println("PmimService.onFinishInput()")}override fun onDestroy() {super.onDestroy()println("PmimService.onDestroy()")}// 预留接口: 关闭软键盘fun im_hideKb() {// run on ui threadhideWindow()}// 预留接口: 输入文本fun im_commitText(text: String) {currentInputConnection.commitText(text, 1)}fun sendKeyEvent(event: KeyEvent) {currentInputConnection.sendKeyEvent(event)}// 预留接口: 发送编辑器默认动作 (比如: 搜索)fun im_sendDefaultEditorAction(fromEnterKey: Boolean) {sendDefaultEditorAction(fromEnterKey)}// 预留接口: 发送字符fun im_sendKeyChar(code: Char) {sendKeyChar(code)}// 预留接口: 获取选择的文本 (复制)fun im_getSelectedText(): String? {return currentInputConnection.getSelectedText(0)?.toString()}// 预留接口: 设置选择的文本 (比如: 全选)fun im_setSelection(start: Int, end: Int) {currentInputConnection.setSelection(start, end)}
}
  • app/src/main/res/xml/im.xml: 正文中已贴出完整代码.

  • app/src/main/res/values/strings.xml: 正文中已贴出完整代码.

  • app/src/main/AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.VIBRATE" /><uses-permission android:name="android.permission.FOREGROUND_SERVICE" /><uses-permission android:name="android.permission.INTERNET" /><applicationandroid:allowBackup="true"android:dataExtractionRules="@xml/data_extraction_rules"android:fullBackupContent="@xml/backup_rules"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.MyApp"tools:targetApi="31"><activityandroid:name=".MainActivity"android:exported="true"android:label="@string/app_name"android:theme="@style/Theme.MyApp"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><!-- Android 输入法服务 --><serviceandroid:name=".im.PmimService"android:exported="true"android:label="@string/im_name"android:permission="android.permission.BIND_INPUT_METHOD"><intent-filter><action android:name="android.view.InputMethod" /></intent-filter><!-- 必须有此元数据, 输入法才能在系统设置中出现 --><meta-data android:name="android.view.im" android:resource="@xml/im" /></service></application>
</manifest>
  • app/src/main/assets/ui/index.html: 正文中已贴出完整代码.

本文使用 CC-BY-SA 4.0 许可发布.

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

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

相关文章

2024年 最新python调用ChatGPT实战教程

2024年 最新python调用ChatGPT实战教程 文章目录 2024年 最新python调用ChatGPT实战教程一、前言二、具体分析1、简版程序2、多轮对话3、流式输出4、返回消耗的token 一、前言 这个之前经常用到&#xff0c;简单记录一下,注意目前chatgpt 更新了&#xff0c;这个是最新版的&am…

MIT-BEVFusion系列九--CUDA-BEVFusion部署4 c++解析pytorch导出的tensor数据

目录 创建流打印 engine 信息打印结果内部流程 启动计时功能加载变换矩阵并更新数据&#xff08;重要&#xff09;内部实现 该系列文章与qwe一同创作&#xff0c;喜欢的话不妨点个赞。 在create_core方法结束后&#xff0c;我们的视角回到了main.cpp中。继续来看接下来的流程。…

挑战杯 基于大数据的时间序列股价预测分析与可视化 - lstm

文章目录 1 前言2 时间序列的由来2.1 四种模型的名称&#xff1a; 3 数据预览4 理论公式4.1 协方差4.2 相关系数4.3 scikit-learn计算相关性 5 金融数据的时序分析5.1 数据概况5.2 序列变化情况计算 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &…

【操作系统】14.I/O设备怎么分配和回收?

5.2 I/O设备怎么分配和回收&#xff1f; 5.2.1 I/O核心子系统 I/O调度 设备保护 假脱机技术&#xff08;SPOOLing技术&#xff09; ​ 输入井和输出井 ​ 输入进程和输出进程 ​ 输入缓冲区和输出缓冲区 设备分配与回收 ​ 设备分配应考虑的因素 ​ 静态分配与动态分配 ​ 设备…

Runaway Queries 管理:提升 TiDB 稳定性的智能引擎

在数字化系统扮演重要角色的今天&#xff0c;数据库稳定性成为企业关注的核心问题。对于重要计算机系统而言&#xff0c;突发的性能下降可能对业务造成不可估量的损失。为了稳定数据库性能&#xff0c;用户可以从管理流程入手规范变更的测试&#xff0c;或者利用产品手段减少预…

3DIoUMatch: Leveraging IoU Prediction for Semi-Supervised 3D Object Detection

3DIoUMatch: Leveraging IoU Prediction for Semi-Supervised 3D Object Detection 论文链接&#xff1a;https://arxiv.org/pdf/2012.04355.pdf 代码链接&#xff1a;https://github.com/yezhen17/3DIoUMatch 作者单位&#xff1a;Stanford University等 发表平台&#xff1a;…

【JavaEE】_Servlet程序的编写方法

目录 1. 创建项目 2. 引入依赖 3. 创建目录结构 3.1 在main目录下创建一个webapp目录 3.2 在webapp目录下创建一个WEB-INF目录 3.3 在WEB-INF目录下创建一个web.xml文件 3.4 在web.xml中进行代码编写 4. 编写代码 4.1 在java目录下创建类 4.2 打印"hello world&…

无人机精准定位技术,GPS差分技术基础,RTK原理技术详解

差分GPS的基本原理 差分GPS&#xff08;Differential GPS&#xff0c;简称DGPS&#xff09;的基本原理是利用一个或多个已知精确坐标的基准站&#xff0c;与用户&#xff08;移动站&#xff09;同时接收相同的GPS卫星信号。由于GPS定位时会受到诸如卫星星历误差、卫星钟差、大…

uniapp离线打包(使用Android studio打包)

一、准备工作 安装HbuilderX&#xff0c;记住版本号下载对应HbuilderX版本的Android离线SDK&#xff0c;如我使用3.6.18版本打包&#xff0c;则对应应下载3.6.18版本的SDK&#xff08;官网不提供旧版本的SDK&#xff0c;有些需要自己找&#xff09;官网下载地址&#xff1a;ht…

2、Web攻防-SQL注入-联合查询注入

用途&#xff1a;个人学习笔记&#xff0c;有所借鉴&#xff0c;欢迎指正&#xff01; 声明&#xff1a;只用于学习交流&#xff0c;点到为止&#xff0c;请勿非法测试。 概念&#xff1a; 联合查询注入&#xff1a;联合注入是回显注入的一种&#xff0c;也就是说联合注入的前…

【Python如何求出水仙花数】

1、求水仙花数Python代码如下&#xff1a; # 求水仙花数&#xff1a;只需要个十百位的3次幂之和与原数相等 for i in range(100, 1000): # 循环100-999整数i1 i % 10 # 取个位 “%”表示除以后取余数i2 i // 10 % 10 # 取十位i3 i // 100 # 取百位 “//”表示除以后取整…

二十六、直方图均衡化

项目功能实现&#xff1a;对灰度图片进行直方图均衡化操作 按照之前的博文结构来&#xff0c;这里就不在赘述了 一、头文件 histogram_equal.h #pragma once#include<opencv2/opencv.hpp>using namespace cv;class HISTOGRAM_EQUAL { public:void histogram_equal(Mat…

Android 解决后台服务麦克风无法录音问题

Android 解决后台无法录音问题 问题分析问题来源解决方案1. 修改清单文件:`AndroidManifest.xml`2. 修改启动服务方式3. 服务启动时创建前台通知并且指定前台服务类型参考文档最后我还有一句话要说我用心为你考虑黄浦江的事情,你心里想的却只有苏州河的勾当 问题分析 安卓9.…

抖音数据挖掘软件|视频内容提取

针对用户获取抖音视频的需求&#xff0c;我们开发了一款功能强大的工具&#xff0c;旨在解决用户在获取抖音视频时需要逐个复制链接、下载的繁琐问题。我们希望用户能够通过简单的关键词搜索&#xff0c;实现自动批量抓取视频&#xff0c;并根据需要进行选择性批量下载。因此&a…

【Flink精讲】Flink任务调度机制

Graph 的概念 Flink 中的执行图可以分成四层&#xff1a; StreamGraph -> JobGraph -> ExecutionGraph -> 物理执 行图。 StreamGraph&#xff1a;是根据用户通过 Stream API 编写的代码生成的最初的图。用来表示程序的拓扑结构。JobGraph&#xff1a; StreamGraph …

[更新]ARCGIS之土地耕地占补平衡、进出平衡系统报备坐标txt格式批量导出工具(定制开发版)

序言 之前开发的耕地占补平衡报备格式&#xff0c;现在之前的基础上集成了耕地进出平衡报备格式导出。 之前版本软件详见&#xff1a;软件介绍 一、软件简介 本软件是基于arcgis二次开发的工具&#xff08;插件&#xff09;&#xff0c;需要授权后才能使用&#xff1b; 本软件…

设计模式——抽象工厂模式

定义: 抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;提供一个创建一系列或相互依赖对象的接口&#xff0c;而无须指定它们具体的类。 概述:一个工厂可以提供创建多种相关产品的接口&#xff0c;而无需像工厂方法一样&#xff0c;为每一个产品都提供一个具体…

open3d 连接两个点云

连接两个点云 一、连接两个点云二、代码三、结果1.coloud1点云2.cloud2点云3.cloud1 和 colud2 合并4.生成连接字段&#xff08;拼接颜色&#xff09; 四、相关链接五、问题与解决方案1.问题2.解决方案 一、连接两个点云 看代码吧。。。 二、代码 import numpy as np import…

数据结构-二叉树深度详解(附带源码)

目录 一、基本概念&#xff1a; 二、实现二叉树的数据结构&#xff1a; 三、二叉树性质&#xff1a; 四、相关计算 五、搜索二叉树&#xff1a;任何一颗树的左子树都比它小&#xff0c;右子树都比它大 六、二叉树的存储结构 七、二叉树基本操作 八、源码&#xff08;有需…

使用pytest和allure框架实现自动化测试报告优化

pytest&#xff1a; 需要安装pytest和pytest-html(生成html测试报告&#xff09; pip install pytest 和 pip install pytest-html 命名规则 Pytest单元测试中的类名和方法名必须是以test开头,执行中只能找到test开头的类和方法&#xff0c;比unittest更加严谨 unittest&#x…