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

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

相关文章

com.alibaba.nacos.api.exception.NacosException: Request nacos server failed

问题描述 安装nacos2.0以上版本&#xff0c;启动报错:com.alibaba.nacos.api.exception.NacosException: Request nacos server failed com.alibaba.nacos.api.exception.NacosException: Request nacos server failed: at com.alibaba.nacos.client.naming.remote.gprc.Nami…

React18源码: schedule任务调度messageChannel

React调度原理(scheduler) 在React运行时中&#xff0c;调度中心&#xff08;位于scheduler包&#xff09;是整个React运行时的中枢&#xff08;其实是心脏&#xff09;&#xff0c;所以理解了scheduler调度&#xff0c;就基本掌握了React的核心React两大循环&#xff1a;从宏…

[深度学习]yolov9+deepsort+pyqt5实现目标追踪

【YOLOv9DeepSORTPyQt5追踪介绍】 随着人工智能技术的飞速发展&#xff0c;目标追踪在视频监控、自动驾驶等领域的应用日益广泛。其中&#xff0c;YOLOv9作为先进的目标检测算法&#xff0c;结合DeepSORT多目标追踪算法和PyQt5图形界面库&#xff0c;能够为用户提供高效、直观…

【Docker】构建pytest-playwright镜像并验证

Dockerfile FROM ubuntu LABEL maintainer "langhuang521l63.com" ENV TZAsia/Shanghai #设置时区 #安装python3依赖与下载安装包 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone \&& apt update \&&…

每日一题(寻找奇数,寻找峰值)

寻找奇数_牛客题霸_牛客网 (nowcoder.com) #include <stdio.h> #include<stdlib.h> int main() {int n0;int num0;scanf("%d",&n);int* arr(int*)malloc(sizeof(int)*n);int i0;for(i0;i<n;i){scanf("%d",&arr[i]);//在循环内&…

图片Base64编码解码的优缺点及应用场景分析

title: 图片Base64编码解码的优缺点及应用场景分析 date: 2024/2/24 14:24:37 updated: 2024/2/24 14:24:37 tags: 图片Base64编码解码HTTP请求优化网页性能加载速度安全性缓存机制 随着互联网的迅猛发展&#xff0c;图片在网页和移动应用中的使用越来越广泛。而图片的传输和加…

从0开始python学习-53.python中flask创建简单接口

目录 1. 创建一个简单的请求,没有写方法时默认为get 2. 创建一个get请求 3. 创建一个post请求&#xff0c;默认可以使用params和表单传参 4. 带有参数的post请求 1. 创建一个简单的请求,没有写方法时默认为get from flask import Flask, request# 初始化一个flask的对象 ap…

无人机基础技术,固定翼无人机动力系统技术详解,无人机飞行控制系统技术

推重比选择 推重比&#xff0c;是指无人机发动机推力/拉力与无人机飞行重力之比。该参数是衡量动力系统乃至整机性能的重要参数&#xff0c;很大程度上影响飞行性能。固定翼无人机的动力系统在配置时选择的推重比必须达到或超出设计的推重比。 重量要求 翼载荷是无人机单位面…

Siamrpn论文中文翻译(详细!)

High Performance Visual Tracking with Siamese Region Proposal Network Siamese地区建议网络的高性能视觉跟踪 说明 建议对照siamrpn&#xff08;2018&#xff09;原文阅读&#xff0c;翻译软件翻译出来的效果不好&#xff0c;整体阅读体验不佳&#xff0c;所以我对译文重…

开源免费的NTFS for mac工具mounty

开源免费的NTFS for mac工具mounty 安装依赖 brew install gromgit/fuse/ntfs-3g-macbrew install --cask macfuse安装mounty 如果已经安装macFUSE和ntfs-3g-mac&#xff0c;可以直接点击下载的dmg安装包&#xff0c;安装升级。第一次启动mounty&#xff0c;你需要接受一系列…

《TCP/IP详解 卷一》第4章 地址解析协议ARP

目录 4.1 引言 4.2 一个例子 4.3 ARP缓存 4.4 ARP帧格式 4.5 ARP例子 4.6 ARP缓存超时 4.7 代理ARP 4.8 免费ARP和地址冲突检测 4.9 ARP命令 4.10 使用ARP设置嵌入式设备IPv4地址 4.11 与ARP相关攻击 4.12 总结 4.1 引言 地址解析&#xff1a; IPv4&#xff1a;AR…

Android LinearLayout 如何让子元素靠下居中对齐 center bottom

Android LinearLayout 如何让子元素靠下居中对齐 center bottom 首先你需要知道两个知识点&#xff1a; android:layout_gravity 指定的是当前元素在父元素中的位置android:gravity 指定的是当前元素子元素的排布位置 比如&#xff1a; 有这么一个布局&#xff0c;我需要让…