Android输入法方法,Android的输入法系统框架原理
发布日期:2021-06-24 12:34:47 浏览次数:2 分类:技术文章

本文共 7267 字,大约阅读时间需要 24 分钟。

[TOC]

概述

eb4ab57393f3

image.png

我们从以下几个点看这张图:

两个过程:

按键消息,由客户端进程接收,如果客户端进程判断当前有输入法窗口,则需要跨进程转交给InputMethod进程

触屏消息(触摸在输入法窗口中),由输入法处理,结束后把结果跨进程提交给客户端进程

四个binder:

IInputContext:负责InputMethod进程和应用进程的编辑框的通信,如上屏、查询光标前后字符等

IInputMethodClient:IMMS使用该接口查找和IMS对应的客户端应用进程,并通知应用进程绑定/解绑输入法。

IInputMethodSession:定义了客户端可以调用输入法的相关方法,如updateCursor, finishInput等

IInputMethod:负责IMS和IMMS的通信,IMMS通知IMS进行startInput,bindInput等生命周期操作。

各个Binder的创建过程

Binder1: InputConnection

这个binder是编辑框做Server端,输入法进程做Client端,主要作用是负责InputMethod进程和应用进程的编辑框的通信,如上屏、查询光标前后字符等。下面一张图显示的是InputConnection模块的类图。IInputContext.aidl表征IMS进程和ClientApp进程的通信协议,两边的实现类分别是IInputConnectionWrapper(Server)和InputConnectionWrapper(Client);另一方面,两者都会在内部持有一个InputConnection对象,因此我们也使用InputConnection这个接口来表征两者的通信。

eb4ab57393f3

InputConnection结构图

每个应用程序进程都运行着对应的InputMethodMananger(IMM),IMM负责应用进程和IMMS的通信。应用程序和IMS进程的InputConnection连接正是借助于IMM和IMMS,在应用程序的窗口获得焦点时建立的。入口在InputMethodManager.startInputInner()中:

// InputMethodManager.startInputInner(这个方法是当前应用程序的窗口的focus状态改变的时候会调用)

boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode, int windowFlags) {

view = mServedView;

...

...

InputConnection ic = view.onCreateInputConnection(tba); // 回调当前焦点View的方法来创建inputConnection

synchronized (mH) {

...

ControlledInputConnectionWrapper servedContext;

if (ic != null) {

...

servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this); // 用这个InputConnection创建wrapper

} else {

servedContext = null;

}

try {

if (DEBUG) Log.v(TAG, "START INPUT: " + view + " ic="

+ ic + " tba=" + tba + " controlFlags=#"

+ Integer.toHexString(controlFlags));

InputBindResult res;

if (windowGainingFocus != null) {

res = mService.windowGainedFocus(mClient, windowGainingFocus,

controlFlags, softInputMode, windowFlags,

tba, servedContext); // 这个方法更新IMMS中关于焦点窗口的一些信息,如果需要的话,会启动输入法,所以这个wrapper要传进去

} else {

res = mService.startInput(mClient,

servedContext, tba, controlFlags); // 调用IMMS的startInput方法,将wrapper对象传过去**

}

...

} catch (RemoteException e) {

...

}

}

return true;

}

BINDER2: InputMethodClient

IMMS查找和IMS对应的客户端应用进程,并负责通知应用进程绑定/解绑输入法。

oneway interface IInputMethodClient {

void setUsingInputMethod(boolean state);

void onBindMethod(in InputBindResult res);

// unbindReason corresponds to InputMethodClient.UnbindReason.

void onUnbindMethod(int sequence, int unbindReason);

void setActive(boolean active, boolean fullscreen);

void setUserActionNotificationSequenceNumber(int sequenceNumber);

void reportFullscreenMode(boolean fullscreen);

}

类图如下:

eb4ab57393f3

Paste_Image.png

根据图中的示意,这个binder其实相当于一个媒介binder。其中,服务端对象mClient会跟随IMM的创建而创建。而客户端的对象其实在IMMS的一个map中。其实当系统认为一个客户进程(应用程序)如果有可能会需要使用输入法,就会把它注册到IMMS中,以便后续谁需要输入法服务的时候,IMMS可以找到对应的客户进程,那么什么时候它可能会使用输入法呢?当然是它展现窗口的时候了。所以从这个角度来说,就好理解为什么注册时机会是这个应用程序向WMS申请窗口的时候了。

Binder3:InputMethodSession

这个binder其实定义了客户端可以调用输入法的相关方法。先大概看看接口方法:

interface IInputMethodSession {

void finishInput();

void updateExtractedText(int token, in ExtractedText text);

void updateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd);

void viewClicked(boolean focusChanged);

void updateCursor(in Rect newCursor);

void displayCompletions(in CompletionInfo[] completions);

void appPrivateCommand(String action, in Bundle data);

void toggleSoftInput(int showFlags, int hideFlags);

void finishSession();

void updateCursorAnchorInfo(in CursorAnchorInfo cursorAnchorInfo);

}

然后看一下服务端的类图:

eb4ab57393f3

Paste_Image.png

我们主要先关心一下这个binder的服务端(在IMS中)是何时创建的,又是如何把客户端binder传给客户应用程序的。

这个binder其实对应于一个客户端应用进程,所以当有一个客户端应用程序接入的时候创建,创建以后又要把客户端binder传过去。不过这个交互的过程需要IMMS作为媒介,而和IMMS交互的过程中又牵涉到InputMethod这个接口。

大概的意思是,IMMS先调用bindService启动IMS,然后IMS会返回一个Binder给IMMS。然后有了这个binder,IMMS就会在需要的时候,调用IMS的createSession创建这个SessionBinder,然后传给客户端进程。

Binder4: IInputMethod

IInputMethod这个binder是IMS和IMMS交互用的。先看一下接口方法:

interface IInputMethod {

void attachToken(IBinder token);

void bindInput(in InputBinding binding);

void unbindInput();

void startInput(in IInputContext inputContext, in EditorInfo attribute);

void restartInput(in IInputContext inputContext, in EditorInfo attribute);

void createSession(in InputChannel channel, IInputSessionCallback callback);

void setSessionEnabled(IInputMethodSession session, boolean enabled);

void revokeSession(IInputMethodSession session);

void showSoftInput(int flags, in ResultReceiver resultReceiver);

void hideSoftInput(int flags, in ResultReceiver resultReceiver);

void changeInputMethodSubtype(in InputMethodSubtype subtype);

}

eb4ab57393f3

Paste_Image.png

这个binder其实是随着输入法启动,就一直不会变化的(也就是和IMS是拥有同一个生命周期的),所以创建一次就不用再创建了。创建时机自然是IMMS启动IMS的时候(bindService)。

输入法主要操作过程

总体过程

eb4ab57393f3

Paste_Image.png

启动输入法

从客户端应用程序的角度来说,它一般是不自己启动输入法的(只有两种开放的方法,即IMM.showSoftInput/hideSoftInput),大多数情况下输入法是系统自动启动的。启动时机,之前也提到,和窗口有关。

eb4ab57393f3

编辑框获得Focus后的处理流程

显示输入法

一般来讲,editText被点击自动获取焦点之后,会调用IMM的showSoftInput,然后转向IMMS的showSoftInput。再然后需要让IMS去显示输入法,这个过程大概是下面这个样子的:

eb4ab57393f3

IMS和IMMS模块的通信过程

输入法窗口内部显示过程

IMS.showWindow的过程:

// InputMethodService.InputMethodImpl

public void showSoftInput(int flags, ResultReceiver resultReceiver) {

boolean wasVis = isInputViewShown();

mShowInputFlags = 0;

if (onShowInputRequested(flags, false)) { // 这个方法内部会调用onEvaluateInputViewShown

try {

showWindow(true); // 重点在这里

} catch (BadTokenException e) {

mWindowVisible = false;

mWindowAdded = false;

}

}

clearInsetOfPreviousIme();

// If user uses hard keyboard, IME button should always be shown.

boolean showing = isInputViewShown();

mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ? IME_VISIBLE : 0),

mBackDisposition);

if (resultReceiver != null) { // 将此次showWindow的结果状态返回回去:Result_shown/.../...

resultReceiver.send(wasVis != isInputViewShown()

? InputMethodManager.RESULT_SHOWN

: (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN

: InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);

}

}

// InputMethodService

public void showWindow(boolean showInput) {

if (mInShowWindow) { // 正在显示窗口,直接返回

return;

}

try {

mWindowWasVisible = mWindowVisible;

mInShowWindow = true;

showWindowInner(showInput); // 显示窗口

} finally {

mWindowWasVisible = true;

mInShowWindow = false;

}

}

void showWindowInner(boolean showInput) {

boolean doShowInput = false;

final int previousImeWindowStatus =

(mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0);

mWindowVisible = true;

if (!mShowInputRequested) {

if (mInputStarted) {

if (showInput) {

doShowInput = true;

mShowInputRequested = true;

}

}

} else {

showInput = true;

}

initialize(); // 这里面有个标志判断是否要进行onInitializeInterface回调

updateFullscreenMode();

updateInputViewShown();

if (!mWindowAdded || !mWindowCreated) {

mWindowAdded = true;

mWindowCreated = true;

initialize();

View v = onCreateCandidatesView();

if (v != null) {

setCandidatesView(v);

}

}

if (mShowInputRequested) {

if (!mInputViewStarted) {

mInputViewStarted = true;

onStartInputView(mInputEditorInfo, false); // 回调onStartInputView

}

} else if (!mCandidatesViewStarted) {

mCandidatesViewStarted = true;

onStartCandidatesView(mInputEditorInfo, false);

}

if (doShowInput) {

startExtractingText(false);

}

final int nextImeWindowStatus = IME_ACTIVE | (isInputViewShown() ? IME_VISIBLE : 0);

if (previousImeWindowStatus != nextImeWindowStatus) {

mImm.setImeWindowStatus(mToken, nextImeWindowStatus, mBackDisposition);

}

if ((previousImeWindowStatus & IME_ACTIVE) == 0) {

onWindowShown();

mWindow.show();

mShouldClearInsetOfPreviousIme = false;

}

}

转载地址:https://blog.csdn.net/weixin_32767591/article/details/117499461 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:android点击屏幕中间播放,android上,实现直接在屏幕上显示点击位置,方便调试。...
下一篇:android两个选择按钮,Android按钮选择器

发表评论

最新留言

网站不错 人气很旺了 加油
[***.192.178.218]2024年04月24日 22时32分16秒