输入法,就是用来输入字符(包括英文,俄文,中文)的工具。输入法你可以看成是一种字符发生器,它将输入数据触摸事件或者按键事件转化为其他更丰富的字符。在PC时代,输入法的原始输入来自实体键盘,鼠标,然后输入法将这些事件对应的ASCII码转换为俄文,中文,当然如果是英文是不需要转换,直接发送即可。而在Android系统里,由于输入法dialog永远没法成为焦点window,所以输入法永远没法获取到按键事件,也就是说输入法的输入数据只能来自触摸事件,输入法显示出键盘(大家称之为软键盘),用户点击键盘UI, 然后输入法将触摸事件所在位置的字符当做原始字符输入,最后组装成更为丰富的字符(多个字符组成拼音,然后转化为中文),然后就是发送到对应的程序。
Android系统中,输入法可以是可以安装的,也就是说系统可以有多个输入法((sougou输入法,百度输入法),但是只有一个是激活的,当然用户可以切换输入法。同时,输入法是以service的方式运行的,输入法同一时间只能服务一个程序,只有最顶层的可见的程序才能接收到输入法的输入数据。
输入法系统的整个框架如下:
InputMethodManagerService(下文也称IMMS)负责管理系统的所有输入法,包括输入法service(InputMethodService简称IMS)加载及切换。程序获得焦点时,就会通过InputMethodManager向InputMethodManagerService通知自己获得焦点并请求绑定自己到当前输入法上。同时,当程序的某个需要输入法的view比如EditorView获得焦点时就会通过InputMethodManager向InputMethodManagerService请求显示输入法,而这时InputMethodManagerService收到请求后,会将请求的EditText的数据通信接口发送给当前输入法,并请求显输入法。输入法收到请求后,就显示自己的UI dialog,同时保存目标view的数据结构,当用户实现输入后,直接通过view的数据通信接口将字符传递到对应的View。接下来就来分析这些过程。
InputMethodManager创建
每个程序有一个InputMethodManager实例,这个是程序和InputMethodManagerService通信的接口,该实例在ViewRootImpl初始化的时候创建。
- public ViewRootImpl(Context context, Display display) {
- mContext = context;
- mWindowSession = WindowManagerGlobal.getWindowSession();
- }
-
- public static IWindowSession getWindowSession() {
- synchronized (WindowManagerGlobal.class) {
- if (sWindowSession == null) {
- try {
-
- InputMethodManager imm = InputMethodManager.getInstance();
- IWindowManager windowManager = getWindowManagerService();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to open window session", e);
- }
- }
- return sWindowSession;
- }
- }
-
- public static InputMethodManager getInstance() {
- synchronized (InputMethodManager.class) {
- if (sInstance == null) {
-
- IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
- IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
- sInstance = new InputMethodManager(service, Looper.getMainLooper());
- }
- return sInstance;
- }
- }
程序的Window获得焦点
程序的window获得焦点的时序图如下
系统WindowManagerService更新焦点window
哪个程序获得焦点是由系统决定的,是由WindowManagerService决定的,当系统的window状态发生变化时(比如window新增,删除)就会调用函数updateFocusedWindowLocked来更新焦点window。
上面的mClient.windowFocusChanged会调回到ViewRootImpl中的W实例:
程序获得焦点改变事件
-
- static class W extends IWindow.Stub {
- @Override
- public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
- final ViewRootImpl viewAncestor = mViewAncestor.get();
- if (viewAncestor != null) {
- viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
- }
- }
-
-
- public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
- Message msg = Message.obtain();
- msg.what = MSG_WINDOW_FOCUS_CHANGED;
- msg.arg1 = hasFocus ? 1 : 0;
- msg.arg2 = inTouchMode ? 1 : 0;
- mHandler.sendMessage(msg);
- }
-
-
-
- final class ViewRootHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_WINDOW_FOCUS_CHANGED: {
- if (mAdded) {
- boolean hasWindowFocus = msg.arg1 != 0;
- mAttachInfo.mHasWindowFocus = hasWindowFocus;
- mLastWasImTarget = WindowManager.LayoutParams
- .mayUseInputMethod(mWindowAttributes.flags);
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (mView != null) {
-
-
- mView.dispatchWindowFocusChanged(hasWindowFocus);
- mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(
- indowFocus);
- }
- if (hasWindowFocus) {
- if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
-
- imm.onWindowFocus(mView, mView.findFocus(),
- mWindowAttributes.softInputMode,
- !mHasHadWindowFocus, mWindowAttributes.flags);
- }
- }
- }
-
- } break;
- }
-
-
-
-
- @Override
- public void dispatchWindowFocusChanged(boolean hasFocus) {
- super.dispatchWindowFocusChanged(hasFocus);
- final int count = mChildrenCount;
- final View[] children = mChildren;
-
-
- for (int i = 0; i < count; i++) {
- children[i].dispatchWindowFocusChanged(hasFocus);
- }
- }
-
-
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (!hasWindowFocus) {
- } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
-
- imm.focusIn(this);
- }
- }
焦点View向IMMS请求绑定输入法
焦点view请求绑定输入法是通过调用InputMethodManager. focusIn实现的
- public void focusIn(View view) {
- synchronized (mH) {
- focusInLocked(view);
- }
- }
-
- void focusInLocked(View view) {
-
- mNextServedView = view;
- scheduleCheckFocusLocked(view);
- }
-
- static void scheduleCheckFocusLocked(View view) {
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
- if (viewRootImpl != null) {
- viewRootImpl.dispatchCheckFocus();
- }
- }
-
- public void dispatchCheckFocus() {
- if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
-
- mHandler.sendEmptyMessage(MSG_CHECK_FOCUS);
- }
- }
- case MSG_CHECK_FOCUS: {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- imm.checkFocus();
- }
- } break;
-
- public void checkFocus() {
- if (checkFocusNoStartInput(false, true)) {
- startInputInner(null, 0, 0, 0);
- }
- }
-
-
- boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,
- int windowFlags) {
- final View view;
- synchronized (mH) {
-
- view = mServedView;
- }
-
- EditorInfo tba = new EditorInfo();
- tba.packageName = view.getContext().getPackageName();
- tba.fieldId = view.getId();
-
-
- InputConnection ic = view.onCreateInputConnection(tba);
-
- synchronized (mH) {
- mServedInputConnection = ic;
- ControlledInputConnectionWrapper servedContext;
- if (ic != null) {
- mCursorSelStart = tba.initialSelStart;
- mCursorSelEnd = tba.initialSelEnd;
- mCursorCandStart = -1;
- mCursorCandEnd = -1;
- mCursorRect.setEmpty();
-
-
- servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);
- }
- mServedInputConnectionWrapper = servedContext;
-
- try {
- InputBindResult res;
- if (windowGainingFocus != null) {
-
- res = mService.windowGainedFocus(mClient, windowGainingFocus,
- controlFlags, softInputMode, windowFlags,
- tba, servedContext);
- } else {
-
-
- res = mService.startInput(mClient,
- servedContext, tba, controlFlags);
- }
- if (res != null) {
- if (res.id != null) {
- setInputChannelLocked(res.channel);
- mBindSequence = res.sequence;
-
- mCurMethod = res.method;
- mCurId = res.id;
- }
- }
- }
- }
-
- return true;
- }
IMMS处理view绑定输入法事件
为了讲解整个绑定过程,我们假设此时输入法service还没启动,这个情况下的输入法绑定是最长的,整个过程经历过如下过程:
1) 启动输入法service
2) 绑定输入法window的token
3) 请求输入法为焦点程序创建一个连接会话-
4) 将输入法的接口传递回程序client端
5) 绑定输入法和焦点view
1-4是和程序相关的,而5是和view相关的。所以你可以说1~4是用来绑定程序window和输入法,而5是用来绑定程序view和输入法。
输入法还没启动时,弹出输入法会经过1~5,输入法已经启动,但是焦点window发生变化时会经历3~5,焦点window没有变化,只是改变了焦点view,则只会经历5。整个流程如下:
启动输入法service
- @Override
- public InputBindResult startInput(IInputMethodClient client,
- IInputContext inputContext, EditorInfo attribute, int controlFlags) {
- synchronized (mMethodMap) {
- final long ident = Binder.clearCallingIdentity();
- try {
- return startInputLocked(client, inputContext, attribute, controlFlags);
- }
- }
- }
- InputBindResult startInputLocked(IInputMethodClient client,
- IInputContext inputContext, EditorInfo attribute, int controlFlags) {
-
- ClientState cs = mClients.get(client.asBinder());
- return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
- }
-
- InputBindResult startInputUncheckedLocked(ClientState cs,
- IInputContext inputContext, EditorInfo attribute, int controlFlags) {
-
- if (mCurClient != cs) {
-
- unbindCurrentClientLocked();
- }
-
-
- mCurClient = cs;
- mCurInputContext = inputContext;
- mCurAttribute = attribute;
-
- if (mCurId != null && mCurId.equals(mCurMethodId)) {
- if (cs.curSession != null) {
-
- return attachNewInputLocked(
- (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
- }
- if (mHaveConnection) {
-
- if (mCurMethod != null) {
- requestClientSessionLocked(cs);
- return new InputBindResult(null, null, mCurId, mCurSeq);
- }
- }
- }
-
- return startInputInnerLocked();
- }
-
- InputBindResult startInputInnerLocked() {
- InputMethodInfo info = mMethodMap.get(mCurMethodId);
-
- unbindCurrentMethodLocked(false, true);
-
-
- 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 (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
- | Context.BIND_NOT_VISIBLE | Context.BIND_SHOWING_UI)) {
- mHaveConnection = true;
- mCurId = info.getId();
-
-
- mCurToken = new Binder();
- try {
- mIWindowManager.addWindowToken(mCurToken,
- WindowManager.LayoutParams.TYPE_INPUT_METHOD);
- } catch (RemoteException e) {
- }
- return new InputBindResult(null, null, mCurId, mCurSeq);
- }
- return null;
- }
-
- private boolean bindCurrentInputMethodService(
- Intent service, ServiceConnection conn, int flags) {
- if (service == null || conn == null) {
- Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
- return false;
- }
- return mContext.bindServiceAsUser(service, conn, flags,
- new UserHandle(mSettings.getCurrentUserId()));
- }
-
-
- @Override
- final public IBinder onBind(Intent intent) {
- if (mInputMethod == null) {
- mInputMethod = onCreateInputMethodInterface();
- }
-
-
-
- return new IInputMethodWrapper(this, mInputMethod);
- }
-
- @Override
- public AbstractInputMethodImpl onCreateInputMethodInterface() {
- return new InputMethodImpl();
- }
-
-
-
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized (mMethodMap) {
- if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
-
- mCurMethod = IInputMethod.Stub.asInterface(service);
-
-
-
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
- MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
- if (mCurClient != null) {
-
-
- requestClientSessionLocked(mCurClient);
- }
- }
- }
- }
输入法Window token的绑定及使用分析
输入法Window token绑定
IMMS在输入法启动完成并回调onServiceConnected时会将一个Window token传递给输入法。
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized (mMethodMap) {
- if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
- mCurMethod = IInputMethod.Stub.asInterface(service);
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
- MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
- if (mCurClient != null) {
- clearClientSessionLocked(mCurClient);
- requestClientSessionLocked(mCurClient);
- }
- }
- }
- }
- case MSG_ATTACH_TOKEN:
- args = (SomeArgs)msg.obj;
- try {
-
- ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
- } catch (RemoteException e) {
- }
- args.recycle();
-
- public class InputMethodService extends AbstractInputMethodService {
- public class InputMethodImpl extends AbstractInputMethodImpl {
- public void attachToken(IBinder token) {
- if (mToken == null) {
-
- mToken = token;
-
- mWindow.setToken(token);
- }
- }
- }
输入法Window token使用
由于系统存在多个输入法,所以输入法要和IMMS通信,必须要个机制来标示自己是哪个输入法,这个就是通过上面的输入法Window token来实现的,比如输入法自己关闭自己:
-
- public void requestHideSelf(int flags) {
-
- mImm.hideSoftInputFromInputMethod(mToken, flags);
- }
-
- public void hideSoftInputFromInputMethod(IBinder token, int flags) {
- try {
- mService.hideMySoftInput(token, flags);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
-
- @Override
- public void hideMySoftInput(IBinder token, int flags) {
- if (!calledFromValidUser()) {
- return;
- }
- synchronized (mMethodMap) {
- if (token == null || mCurToken != token) {
- if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
- + Binder.getCallingUid() + " token: " + token);
- return;
- }
- long ident = Binder.clearCallingIdentity();
- try {
- hideCurrentInputLocked(flags, null);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
输入法连接会话创建
到此程序和输入法的session就建立了
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized (mMethodMap) {
- if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
- if (mCurClient != null) {
- clearClientSessionLocked(mCurClient);
- requestClientSessionLocked(mCurClient);
- }
- }
- }
- }
-
- void requestClientSessionLocked(ClientState cs) {
- if (!cs.sessionRequested) {
-
-
- InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
- cs.sessionRequested = true;
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
- MSG_CREATE_SESSION, mCurMethod, channels[1],
- new MethodCallback(this, mCurMethod, channels[0])));
- }
- }
- case MSG_CREATE_SESSION: {
- args = (SomeArgs)msg.obj;
- IInputMethod method = (IInputMethod)args.arg1;
- InputChannel channel = (InputChannel)args.arg2;
- try {
- method.createSession(channel, (IInputSessionCallback)args.arg3);
- } catch (RemoteException e) {
- }
-
- public abstract class AbstractInputMethodService extends Service
- implements KeyEvent.Callback {
- public abstract class AbstractInputMethodImpl implements InputMethod {
- public void createSession(SessionCallback callback) {
- callback.sessionCreated(onCreateInputMethodSessionInterface());
- }
- pre class="java" name="code"> }
} @Override public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() { //sesion的真正实现 return new InputMethodSessionImpl(); } //然后回到了IMMS void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) { synchronized (mMethodMap) { if (mCurMethod != null && method != null && mCurMethod.asBinder() == method.asBinder()) { if (mCurClient != null) { //将session相关的数据封装到SessionState对象里 mCurClient.curSession = new SessionState(mCurClient, method, session, channel); //这个会开始真正的绑定 InputBindResult res = attachNewInputLocked(true); return; } } } }
传递输入法接口给程序
- void onSessionCreated(IInputMethod method, IInputMethodSession session,
- InputChannel channel) {
- synchronized (mMethodMap) {
- if (mCurMethod != null && method != null
- && mCurMethod.asBinder() == method.asBinder()) {
- if (mCurClient != null) {
- InputBindResult res = attachNewInputLocked(true);
- if (res.method != null) {
- executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
- MSG_BIND_METHOD, mCurClient.client, res));
- }
- return;
- }
- }
- }
- channel.dispose();
- }
- case MSG_BIND_METHOD: {
- args = (SomeArgs)msg.obj;
- IInputMethodClient client = (IInputMethodClient)args.arg1;
- InputBindResult res = (InputBindResult)args.arg2;
- try {
-
- client.onBindMethod(res);
- }
- args.recycle();
- return true;
- }
输入法和view绑定
-
- InputBindResult attachNewInputLocked(boolean initial) {
- if (!mBoundToMethod) {
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
- MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
- mBoundToMethod = true;
- }
- final SessionState session = mCurClient.curSession;
- if (initial) {
- executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
- MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
- } else {
- executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
- MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
- }
- return new InputBindResult(session.session,
- session.channel != null ? session.channel.dup() : null, mCurId, mCurSeq);
- }
- case MSG_BIND_INPUT:
- args = (SomeArgs)msg.obj;
- try {
- ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
- } catch (RemoteException e) {
- }
- args.recycle();
- return true;
-
- case MSG_START_INPUT:
- args = (SomeArgs)msg.obj;
- try {
- SessionState session = (SessionState)args.arg1;
- session.method.startInput((IInputContext)args.arg2,
- (EditorInfo)args.arg3);
- } catch (RemoteException e) {
- }
- args.recycle();
- return true;
-
- @Override
- public void startInput(IInputContext inputContext, EditorInfo attribute) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
- inputContext, attribute));
- }
- case DO_START_INPUT: {
- SomeArgs args = (SomeArgs)msg.obj;
-
-
-
- IInputContext inputContext = (IInputContext)args.arg1;
- InputConnection ic = inputContext != null
- ? new InputConnectionWrapper(inputContext) : null;
- EditorInfo info = (EditorInfo)args.arg2;
- inputMethod.startInput(ic, info);
- args.recycle();
- return;
- }
- public class InputMethodImpl extends AbstractInputMethodImpl {
- public void startInput(InputConnection ic, EditorInfo attribute) {
- doStartInput(ic, attribute, false);
- }
- }
-
- void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
- if (!restarting) {
- doFinishInput();
- }
- mInputStarted = true;
- mStartedInputConnection = ic;
- mInputEditorInfo = attribute;
- initialize();
- onStartInput(attribute, restarting);
- if (mWindowVisible) {
- if (mShowInputRequested) {
- mInputViewStarted = true;
-
- onStartInputView(mInputEditorInfo, restarting);
- startExtractingText(true);
- } else if (mCandidatesVisibility == View.VISIBLE) {
- mCandidatesViewStarted = true;
- onStartCandidatesView(mInputEditorInfo, restarting);
- }
- }
- }
到此焦点view已经通过调用IMMS的startInput和输入法绑定了,但是此时输入法还没有显示。但是系统紧接着会调用windowGainFocus来显示输入法。
程序焦点获取事件导致输入法显示
请查看输入法框架下篇
输入法响应显示请求
请查看输入法框架下篇
用户单击输入框View导致输入法显示
请查看输入法框架下篇
输入法传递输入文本信息给view
请查看输入法框架下篇