Android输入法IMS流程详解
The following article is from 躬行之 Author jzman
PS:尝试从稀缺的角度看问题。
文本输入是输入法和编辑器协同作用的结果,输入法可以是软键盘、语音等,扩展了 InputMethodService 来实现,编辑器主要是接受文本并显示文本的组件,一般是 EditText,输入法与编辑器的交互一般是当输入框获得焦点,弹出当前系统默认输入法,输入字符,选择候选词,更新输入字符到编辑框,本文将从如下几个方面介绍 Android 输入法机制,如下:
IMF 框架 输入法拉起流程 输入法管理服务(IMMS) 输入法(IMS) 客户端(IMM) IMS、IMMS和IMM之间的交互
客户端,当前输入内容的 App,主要是 InputMethodManager和 EditText/TextView。 输入法管理服务,主要是 InputMethodManagerService。 输入法:主要是 InputMethodService。
其中客户端 IMM 与 输入法 IMS 之间的交互已经通过 EditText 及其父类 TextView 实现,如果是自定义的编辑框,需要自行实现与输入法的交互。
// InputBindResult.java
// 请求输入法成功
ResultCode.SUCCESS_WITH_IME_SESSION,
// 输入法已经启动,等待IME创建session
ResultCode.SUCCESS_WAITING_IME_SESSION,
// 输入法还未启动,等待输入法绑定
ResultCode.SUCCESS_WAITING_IME_BINDING,
// InputMethodManagerService.java
InputBindResult startInputInnerLocked() {
// ...
// 绑定输入法
// InputMethod.SERVICE_INTERFACE(android.view.InputMethod)
mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
mCurIntent.setComponent(info.getComponent());
mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.input_method_binding_label);
mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
// 绑定输入法
if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
// ...
// 绑定成功
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
null, null, mCurId, mCurSeq,
mCurUserActionNotificationSequenceNumber);
}
// 绑定失败
return InputBindResult.IME_NOT_CONNECTED;
}
private boolean bindCurrentInputMethodServiceLocked(
Intent service, ServiceConnection conn, int flags) {
// ...
// 绑定输入法
return mContext.bindServiceAsUser(service, conn, flags,
new UserHandle(mSettings.getCurrentUserId()));
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
// 绑定输入法成功后获得操作输入法的接口IInputMethod
mCurMethod = IInputMethod.Stub.asInterface(service);
if (mCurToken == null) {
Slog.w(TAG, "Service connected without a token!");
unbindCurrentMethodLocked(false);
return;
}
if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
// 发送MSG_ATTACH_TOKEN生成与系统服务IMMS会话的token
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
// 如果当前绑定到给输入法的客户端存在则重新创建session
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
requestClientSessionLocked(mCurClient);
}
}
}
}
到此,系统输入法从编辑器 EditText 获得焦点开始到输入法 IMS 服务绑定成功的流程结束,这里注意下 startInputUncheckedLocked 方法,后续 IMM、IMMS 和 IMS 三者之间的交互关系都在其中,最终调用 showSoftInput 显示输入法。
/**
* Java proxy for a native IBinder object.
* Allocated and constructed by the native javaObjectforIBinder function. Never allocated
* directly from Java code.
*/
final class BinderProxy implements IBinder {
// ...
}
IInputMethodManager.aidl:定义了操作输入法的公共接口对外提供给客户端 IMM,最常用的比如显示、隐藏、切换软键盘等。 IInputSessionCallback.aidl:用于允许输入法创建会话的时候通知 IMMS,之后 IMMS 就可以绑定输入法 IMS 了。
void onSessionCreated(IInputMethod method, IInputMethodSession session,
InputChannel channel) {
synchronized (mMethodMap) {
if (mCurMethod != null && method != null
&& mCurMethod.asBinder() == method.asBinder()) {
if (mCurClient != null) {
// 清理客户端的session
clearClientSessionLocked(mCurClient);
// 初始化SessionState
mCurClient.curSession = new SessionState(mCurClient,
method, session, channel);
// 准备显示输入法
InputBindResult res = attachNewInputLocked(
InputMethodClient.START_INPUT_REASON_SESSION_CREATED_BY_IME, true);
if (res.method != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
MSG_BIND_CLIENT, mCurClient.client, res));
}
return;
}
}
}
// Session abandoned. Close its associated input channel.
channel.dispose();
}
如上 onSessionCreated 会绑定客户端到此 IMMS 的关键流程结束。
public class PinyinIME extends InputMethodService {
<service android:name=".PinyinIME"
android:label="@string/ime_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/method" />
</service>
IInputMethod.aidl:输入法 IMM 是一个Service,其核心实现就是 IInputMethod.aidl 的实现IInputMethodWrapper,提供了操作输入法的核心方法。 IInputMethodSession.aidl:提供了客户端 IMM 操作输入法的接口,IMM 中存在 IInputMethodSession 类型的实例 mCurMethod 供其调用。
onBindInput:客户端 IMM 绑定该输入法 IMS 的时候调用。 onStartInput:用户在编辑框开始输入文字的时候调用。 onCreateInputView:创建并返回输入区域的视图,第一次显示输入区域视图的时候调用,默认实现返回 null,可实现 onEvaluateInputViewShown 来控制候显示输入区域视图,更改输入区域视图使用 setInputView 进行更改。 onCreateCandidatesView:创建并返回显示候选词的视图,第一次显示候选词视图的时候调用,默认实现返回 null,可使用 setCandidatesViewShown 方法控制候选词视图的显示,更改候选视图使用 setCandidatesView 进行更改。 onStartInputView:当前页面的输入框获取了焦点时调用。
IInputMethodClient.aidl:简单理解为 IMMS 绑定 IMS 的时候通知 IMM 输入法已经准备好了可以进行绑定了,具体是由 IMM 内部实现,对应 mClient。 IInputContext.aidl:定义操作输入法编辑器 IME 的方法,默认实现是 EditableInputConnection。
IMM 与 IMMS 的交互主要是 IInputMethodManager.aidl,使用时通过 getSystemService 获取 input_method 系统服务,也就是 IMMS,之后就可以调用输入法管理器 IMMS 提供的公共接口了。 IMM 与 IMS 的交互主要是 IInputMethodSession.aidl,定义供客户端操作输入法的方法,IMMS 请求 IInputMethod 创建 IInputMethodSession,IMMS 绑定客户端 IMM 的时候将 IInputMethodSession 传入 IMM。
IMM 使用 IInputMethodManager 请求 IMMS。 IMMS 绑定 IMS 获得操作输入法的相关接口 IInputMethod。 IMMS 请求 IMS 创建 IInputMethodSession。 IMMS 通过 IInputMethodClient 告知 IMM 当前 IInputMethodSession。 IMM 和 IMS 通过 IInputMethodSession 和 IInputContext 交互。
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!