(笔记)输入法框架协作机制深度分析
概述
Android输入法框架(IMF - Input Method Framework)是Android系统中负责管理虚拟键盘和文本输入的核心组件。该框架协调输入法服务(IME)、应用程序和系统输入系统之间的复杂交互,为用户提供灵活高效的文本输入体验。本文深入分析Android 7.0中IME如何与输入系统协作,包括架构设计、生命周期管理、事件处理和窗口管理等关键机制。
输入法框架整体架构
┌─────────────────────────────────────────────────────────────┐
│ 应用层 │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
│ │ EditText │ │ WebView │ │ Custom │ │
│ │ (文本编辑) │ │ (Web输入) │ │ Input │ │
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ InputMethodManager │ │
│ │ (客户端输入法管理器) │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘│▼ (Binder IPC)
┌─────────────────────────────────────────────────────────────┐
│ 系统服务层 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ InputMethodManagerService │ │
│ │ (输入法管理服务) │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ IME生命周期管理 │ │ │
│ │ │ ┌─────────────┐ ┌─────────────────────────┐ │ │ │
│ │ │ │ IME绑定 │ │ 窗口目标管理 │ │ │ │
│ │ │ │ 和启动 │ │ mCurMethodTarget │ │ │ │
│ │ │ └─────────────┘ └─────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 输入连接管理 │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │
│ │ │ │InputBinding │ │InputConnection│ │InputContext │ │ │ │
│ │ │ │ 会话 │ │ 文本接口 │ │ 上下文 │ │ │ │
│ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ WindowManagerService │ │
│ │ (窗口管理服务) │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ IME窗口管理 │ │ │
│ │ │ ┌─────────────┐ ┌─────────────────────────┐ │ │ │
│ │ │ │ 输入法目标 │ │ IME窗口层级 │ │ │ │
│ │ │ │ 窗口跟踪 │ │ 和焦点管理 │ │ │ │
│ │ │ └─────────────┘ └─────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘│▼ (进程间通信)
┌─────────────────────────────────────────────────────────────┐
│ 输入法进程 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ InputMethodService │ │
│ │ (输入法服务基类) │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ UI组件管理 │ │ │
│ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │
│ │ │ │ InputView │ │CandidatesView│ │ExtractView │ │ │ │
│ │ │ │ (键盘界面) │ │ (候选词) │ │ (提取界面) │ │ │ │
│ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 事件处理 │ │ │
│ │ │ ┌─────────────┐ ┌─────────────────────────┐ │ │ │
│ │ │ │ 键盘事件 │ │ 文本提交 │ │ │ │
│ │ │ │ 处理 │ │ 和编辑操作 │ │ │ │
│ │ │ └─────────────┘ └─────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘│▼ (输入事件)
┌─────────────────────────────────────────────────────────────┐
│ 输入系统 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ InputManagerService │ │
│ │ (输入管理服务) │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ 事件分发 │ │ │
│ │ │ InputDispatcher → IME窗口 → 应用窗口 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
1. 输入法框架核心组件
1.1 InputMethodManagerService (IMMS)
文件路径: frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java
/*** InputMethodManagerService是输入法框架的核心系统服务* 负责管理所有输入法的生命周期、绑定和切换*/
public class InputMethodManagerService extends IInputMethodManager.Stubimplements ServiceConnection, Handler.Callback {static final String TAG = "InputMethodManagerService";// === 核心管理对象 ===/** 当前绑定的输入法 */@NullableInputMethodInfo mCurMethodId;/** 当前输入法的客户端连接 */@NullableClientState mCurClient;/** 当前输入法会话 */@NullableIInputMethodSession mCurMethod;/** 当前的输入法目标窗口 */@NullableIBinder mCurMethodTarget;/** 输入法窗口令牌 */@NullableIBinder mCurToken;/** 输入法服务接口 */@NullableIInputMethod mCurMethod;/** 输入法意图(用于绑定服务) */@NullableIntent mCurIntent;/** 所有已安装的输入法列表 */final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();/** 所有输入法的映射表 */final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<>();/** 客户端状态管理 */final HashMap<IBinder, ClientState> mClients = new HashMap<>();// === 窗口管理相关 ===/** 窗口管理服务接口 */final WindowManagerInternal mWindowManagerInternal;/** 当前显示的输入法窗口 */final ArrayList<WindowState> mImeWindowVis = new ArrayList<>();/** 输入法窗口是否可见 */boolean mInputShown;/** 输入法窗口显示模式 */int mImeWindowVis;// 构造函数public InputMethodManagerService(Context context) {mContext = context;mRes = context.getResources();mHandler = new Handler(this);mCaller = new HandlerCaller(context, null, this, false /*asyncHandler*/);mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);// 初始化输入法设置mSettings = new InputMethodSettings(mRes, context.getContentResolver(), mMethodMap, mMethodList, context.getUserId(), !mSystemReady);// 更新输入法列表updateInputMethodsFromSettingsLocked(true /* enabledChanged */);// 注册包管理器监听IntentFilter packageFilter = new IntentFilter();packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);packageFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);packageFilter.addDataScheme("package");mContext.registerReceiver(new ImmsBroadcastReceiver(), packageFilter);}
}// 客户端状态
final class ClientState {final IInputMethodClient client; // 客户端接口final IInputContext inputContext; // 输入上下文final int uid; // 用户IDfinal int pid; // 进程IDfinal InputBinding binding; // 输入绑定final HashMap<IBinder, SessionState> sessions = new HashMap<>(); // 会话状态ClientState(IInputMethodClient _client, IInputContext _inputContext,int _uid, int _pid) {client = _client;inputContext = _inputContext;uid = _uid;pid = _pid;binding = new InputBinding(null, inputContext.asBinder(), uid, pid);}
}// 会话状态
final class SessionState {final ClientState client; // 所属客户端final IInputMethod method; // 输入法接口final IInputMethodSession session; // 输入法会话final InputChannel channel; // 输入通道SessionState(ClientState _client, IInputMethod _method,IInputMethodSession _session, InputChannel _channel) {client = _client;method = _method;session = _session;channel = _channel;}
}
1.2 InputMethodManager (IMM)
文件路径: frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java
/*** InputMethodManager是客户端应用程序与输入法框架交互的主要接口* 每个应用进程都有一个IMM实例*/
public final class InputMethodManager {static final String TAG = "InputMethodManager";// === 核心状态 ===/** 输入法管理服务的Binder接口 */final IInputMethodManager mService;/** 主线程Looper */final Looper mMainLooper;/** 主线程Handler */final H mH;/** 当前绑定的输入法服务 */IInputMethodSession mCurMethod;/** 当前的输入连接 */InputConnection mCurrentTextBoxAttribute;/** 当前获得焦点的View */View mCurRootView;/** 当前活动的View */View mServedView;/** 编辑器信息 */EditorInfo mCurrentTextBoxAttribute;/** 输入连接包装器 */IInputConnectionWrapper mServedInputConnectionWrapper;/** 完成事件计数器 */int mRequestUpdateCursorAnchorInfoMonitorMode;// === 客户端状态 ===/** 当前客户端 */final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {@Overrideprotected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {// 客户端状态转储}@Overridepublic void onBindMethod(InputBindResult res) {// 绑定输入法回调mH.obtainMessage(MSG_BIND, res).sendToTarget();}@Overridepublic void onUnbindMethod(int sequence, int unbindReason) {// 解绑输入法回调mH.obtainMessage(MSG_UNBIND, sequence, unbindReason).sendToTarget();}@Overridepublic void setActive(boolean active, boolean fullscreen) {// 设置活动状态回调mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, fullscreen ? 1 : 0).sendToTarget();}@Overridepublic void scheduleStartInputIfNecessary() {// 调度输入启动回调mH.obtainMessage(MSG_START_INPUT_IF_NECESSARY).sendToTarget();}@Overridepublic void reportFullscreenMode(boolean enabled) {// 全屏模式报告回调mH.obtainMessage(MSG_REPORT_FULLSCREEN_MODE, enabled ? 1 : 0, 0).sendToTarget();}};// 构造函数InputMethodManager(IInputMethodManager service, Looper looper) {mService = service;mMainLooper = looper;mH = new H(looper);mIInputContext = new IInputConnectionWrapper(mMainLooper,mDummyInputConnection, this);if (sInstance == null) {sInstance = this;}}/*** 显示输入法*/public boolean showSoftInput(View view, int flags) {return showSoftInput(view, flags, null);}public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {checkFocus();synchronized (mH) {if (mServedView != view && (mServedView == null|| !mServedView.checkInputConnectionProxy(view))) {return false;}try {return mService.showSoftInput(mClient, flags, resultReceiver);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}}/*** 隐藏输入法*/public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {return hideSoftInputFromWindow(windowToken, flags, null);}public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,ResultReceiver resultReceiver) {checkFocus();synchronized (mH) {if (mServedView == null || mServedView.getWindowToken() != windowToken) {return false;}try {return mService.hideSoftInput(mClient, flags, resultReceiver);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}}
}
1.3 InputMethodService (IMS)
文件路径: frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
/*** InputMethodService提供了输入法的标准实现基类* 具体的输入法应用继承此类并实现相关方法*/
public class InputMethodService extends AbstractInputMethodServiceimplements KeyEvent.Callback {static final String TAG = "InputMethodService";// === UI相关常量 ===static final int BACK_DISPOSITION_DEFAULT = 0;static final int BACK_DISPOSITION_WILL_NOT_DISMISS = 1;static final int BACK_DISPOSITION_WILL_DISMISS = 2;static final int BACK_DISPOSITION_ADJUST_NOTHING = 3;// === 核心组件 ===/** 输入法管理器 */InputMethodManager mImm;/** 当前输入绑定 */InputBinding mInputBinding;/** 当前输入连接 */InputConnection mInputConnection;/** 当前输入启动状态 */boolean mInputStarted;/** 当前输入视图启动状态 */boolean mInputViewStarted;/** 候选词视图启动状态 */boolean mCandidatesViewStarted;// === UI组件 ===/** 输入法窗口 */SoftInputWindow mWindow;/** 输入视图 */View mInputView;/** 候选词视图 */View mCandidatesView;/** 提取文本视图 */View mExtractView;/** 提取文本编辑器 */ExtractEditText mExtractEditText;/** 提取访问点 */ViewGroup mExtractAccessories;/** 提取操作 */View mExtractAction;/** 全屏模式 */boolean mIsFullscreen;/** 输入区域显示 */boolean mInputViewShown;/** 候选词区域显示 */boolean mCandidatesViewShown;/** 状态栏显示 */boolean mStatusIcon;// === 生命周期方法 ===/*** 初始化界面,在配置变化时调用*/public void onInitializeInterface() {// 子类可重写此方法进行界面初始化}/*** 绑定到新的输入目标*/@Overridepublic void onBindInput() {// 子类可重写此方法处理绑定事件}/*** 解除输入绑定*/@Overridepublic void onUnbindInput() {// 子类可重写此方法处理解绑事件}/*** 开始新的输入会话*/public void onStartInput(EditorInfo attribute, boolean restarting) {// 子类可重写此方法处理输入启动}/*** 重启当前输入会话*/public void onRestartInput(EditorInfo attribute, boolean restarting) {// 子类可重写此方法处理输入重启}/*** 输入视图启动*/public void onStartInputView(EditorInfo info, boolean restarting) {// 子类可重写此方法处理输入视图启动}/*** 输入视图结束*/public void onFinishInputView(boolean finishingInput) {// 子类可重写此方法处理输入视图结束}/*** 候选词视图启动*/public void onStartCandidatesView(EditorInfo info, boolean restarting) {// 子类可重写此方法处理候选词视图启动}/*** 候选词视图结束*/public void onFinishCandidatesView(boolean finishingInput) {// 子类可重写此方法处理候选词视图结束}// === UI创建方法 ===/*** 创建输入视图*/@CallSuperpublic View onCreateInputView() {return null;}/*** 创建候选词视图*/@CallSuper public View onCreateCandidatesView() {return null;}/*** 创建提取文本视图*/@CallSuperpublic View onCreateExtractTextView() {return null;}// === 输入处理方法 ===/*** 按键处理*/public boolean onKeyDown(int keyCode, KeyEvent event) {int c = event.getUnicodeChar();if (c > 0) {onKey(c, null);return true;}return false;}/*** 字符输入处理*/public void onKey(int primaryCode, int[] keyCodes) {InputConnection ic = getCurrentInputConnection();if (ic == null) return;ic.commitText(String.valueOf((char) primaryCode), 1);}/*** 文本提交*/public void commitText(CharSequence text, int newCursorPosition) {InputConnection ic = getCurrentInputConnection();if (ic == null) return;ic.commitText(text, newCursorPosition);}/*** 发送按键事件*/public void sendKeyChar(char charCode) {switch (charCode) {case '\n': // 回车sendDefaultEditorAction(false);break;default:// 发送字符if (charCode >= '0' && charCode <= '9') {sendDownUpKeyEvents(charCode - '0' + KeyEvent.KEYCODE_0);} else {InputConnection ic = getCurrentInputConnection();if (ic != null) {ic.commitText(String.valueOf(charCode), 1);}}break;}}
}
2. 输入法生命周期管理
2.1 输入法绑定流程
public class InputMethodManagerService {/*** 开始输入法绑定流程* 当应用获得焦点或输入法需要切换时调用*/InputBindResult startInputLocked(@NonNull IInputMethodClient client,IInputContext inputContext, @NonNull EditorInfo attribute,int startInputFlags) {if (DEBUG) Slog.v(TAG, "startInputLocked: client=" + client.asBinder()+ " inputContext=" + inputContext + " attribute=" + attribute);// 检查客户端状态ClientState cs = mClients.get(client.asBinder());if (cs == null) {throw new IllegalArgumentException("unknown client " + client.asBinder());}// 检查输入法是否需要重新绑定if (mCurMethodId == null) {return mNoBinding;}// 检查是否需要切换客户端ClientState oldClient = mCurClient;mCurClient = cs;mCurInputContext = inputContext;mCurAttribute = attribute;// 如果输入法未绑定,开始绑定流程if (mCurMethod == null) {if (DEBUG) Slog.v(TAG, "Binding to method " + mCurMethodId);return startInputInnerLocked();}// 输入法已绑定,直接启动输入return attachNewInputLocked(startInputFlags);}/*** 内部输入启动逻辑*/InputBindResult startInputInnerLocked() {if (mCurMethodId == null) {return mNoBinding;}if (!mSystemReady) {// 系统还未准备好return new InputBindResult(InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,null, null, mCurMethodId, mCurSeq, mCurUserActionNotificationSequenceNumber);}InputMethodInfo info = mMethodMap.get(mCurMethodId);if (info == null) {throw new IllegalArgumentException("Unknown id: " + mCurMethodId);}// 解绑当前输入法unbindCurrentMethodLocked(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, IME_CONNECTION_BIND_FLAGS)) {mLastBindTime = SystemClock.uptimeMillis();mHaveConnection = true;mCurId = info.getId();mCurToken = new Binder();try {if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);mIWindowManager.addWindowToken(mCurToken, TYPE_INPUT_METHOD, DEFAULT_DISPLAY);} catch (RemoteException e) {Slog.w(TAG, "Failed adding window token", e);}return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,null, null, mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);}return mNoBinding;}/*** 服务连接回调 - 输入法服务已连接*/@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {synchronized (mMethodMap) {if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {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);executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(MSG_ATTACH_TOKEN, mCurMethod, mCurToken));if (mCurClient != null) {clearClientSessionLocked(mCurClient);requestClientSessionLocked(mCurClient);}}}}/*** 服务连接断开回调*/@Overridepublic void onServiceDisconnected(ComponentName name) {synchronized (mMethodMap) {if (DEBUG) Slog.v(TAG, "Service disconnected: " + name+ " mCurIntent=" + mCurIntent);if (mCurMethod != null && mCurIntent != null&& name.equals(mCurIntent.getComponent())) {clearCurMethodLocked();// 重新绑定输入法mLastBindTime = SystemClock.uptimeMillis();mShowRequested = mInputShown;mInputShown = false;unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);}}}
}
2.2 输入会话管理
public class InputMethodManagerService {/*** 请求客户端会话*/void requestClientSessionLocked(ClientState cs) {if (!cs.sessionRequested) {if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());cs.sessionRequested = true;final IInputMethodSession session = new IInputMethodSessionWrapper(mContext, channels[1], cs.client);final InputChannel clientChannel = channels[0];executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(MSG_CREATE_SESSION, mCurMethod, clientChannel, new MethodCallback(cs, session)));}}/*** 输入法会话创建回调*/private class MethodCallback extends IInputSessionCallback.Stub {private final ClientState mClientState;private final IInputMethodSession mSession;MethodCallback(ClientState clientState, IInputMethodSession session) {mClientState = clientState;mSession = session;}@Overridepublic void sessionCreated(IInputMethodSession session) {long ident = Binder.clearCallingIdentity();try {synchronized (mMethodMap) {if (mClientState.client != null) {clearClientSessionLocked(mClientState);mClientState.sessionRequested = false;SessionState sessionState = new SessionState(mClientState, mCurMethod, session, channel);mClientState.curSession = sessionState;sessionState.method = SessionMethodWrapper.newInstance(session);try {sessionState.method.initializeSession(sessionState.session,mClientState.binding.getConnectionToken(),mClientState.binding.getUid(),mClientState.binding.getPid());} catch (RemoteException e) {Slog.w(TAG, "Session initialization failed", e);}}}} finally {Binder.restoreCallingIdentity(ident);}}}
}
3. 窗口管理与输入系统协作
3.1 输入法目标窗口管理
// WindowManagerService中的输入法窗口管理
public class WindowManagerService {/** 当前输入法目标窗口 */WindowState mInputMethodTarget = null;/** 上一个输入法目标窗口 */WindowState mLastInputMethodTargetWindow = null;/** 输入法窗口 */WindowState mInputMethodWindow = null;/** 输入法对话框窗口列表 */final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<>();/*** 更新输入法目标窗口* 在窗口焦点变化时调用*/boolean updateInputMethodTargetLocked(WindowState targetWin) {WindowState newTarget = null;if (targetWin == null) {// 重新计算输入法目标newTarget = computeInputMethodTargetLocked();} else {newTarget = targetWin;}if (DEBUG_INPUT_METHOD) {Slog.v(TAG_WM, "Proposed new IME target: " + newTarget);}// 检查目标是否发生变化if (mInputMethodTarget == newTarget) {return false;}if (newTarget != null) {AppWindowToken token = newTarget.mAppToken;if (token != null) {// 检查应用是否准备好接收输入法if (token.mAppAnimator.freezingScreen) {newTarget = null;}}}if (DEBUG_INPUT_METHOD) {Slog.v(TAG_WM, "Final new IME target: " + newTarget);Slog.v(TAG_WM, "Last IME target: " + mInputMethodTarget);}if (newTarget == mInputMethodTarget) {return false;}final WindowState oldTarget = mInputMethodTarget;mInputMethodTarget = newTarget;// 通知输入法管理器if (mInputMethodManagerInternal != null) {mInputMethodManagerInternal.updateInputMethodTargetWindow(oldTarget, newTarget);}return true;}/*** 计算输入法目标窗口* 通常是当前焦点窗口,但也考虑特殊情况*/private WindowState computeInputMethodTargetLocked() {// 首先检查当前焦点窗口if (mCurrentFocus != null && mCurrentFocus.canReceiveKeys() &&!mCurrentFocus.mAttrs.isModal()) {return mCurrentFocus;}// 如果焦点窗口是模态的,寻找其父窗口WindowState target = mCurrentFocus;while (target != null) {if (target.canBeImeTarget()) {return target;}target = target.mParentWindow;}// 最后的备选方案return null;}/*** 设置输入法窗口状态*/public void setInputMethodWindowState(IBinder token, boolean visible, boolean touchable) {synchronized (mWindowMap) {WindowState imWindow = null;if (token != null) {imWindow = mTokenMap.get(token);}if (imWindow != null) {if (DEBUG_INPUT_METHOD) {Slog.v(TAG_WM, "Setting IM window state: visible=" + visible + " touchable=" + touchable + " " + imWindow);}imWindow.mPolicyVisibility = visible;if (touchable) {imWindow.mAttrs.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;} else {imWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;}scheduleNotifyWindowTransitionIfNeededLocked(imWindow,imWindow.mPolicyVisibility ? TRANSIT_ENTER : TRANSIT_EXIT);updateInputMethodTargetLocked(null);scheduleAnimationLocked();}}}
}// WindowState中的输入法相关方法
public final class WindowState {/*** 检查窗口是否可以成为输入法目标*/boolean canBeImeTarget() {if (mIsImWindow) {// 输入法窗口本身不能成为目标return false;}final boolean windowsAreFocusable = mAppToken == null || mAppToken.windowsAreFocusable();if (!windowsAreFocusable) {// 如果应用窗口不可聚焦,则不能成为输入法目标return false;}final int fl = mAttrs.flags & (FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);if (fl != 0 && fl != FLAG_ALT_FOCUSABLE_IM) {return false;}if (DEBUG_INPUT_METHOD) {Slog.i(TAG_WM, "isVisibleOrAdding " + this + ": " + isVisibleOrAdding());if (!isVisibleOrAdding()) {Slog.i(TAG_WM, " mSurface=" + mWinAnimator.mSurfaceControl+ " relayoutCalled=" + mRelayoutCalled + " viewVis=" + mViewVisibility+ " policyVis=" + mPolicyVisibility + " attachHid=" + mAttachedHidden+ " exiting=" + mAnimatingExit + " destroying=" + mDestroying);if (mAppToken != null) {Slog.i(TAG_WM, " mAppToken.hiddenRequested=" + mAppToken.hiddenRequested);}}}return isVisibleOrAdding();}
}
3.2 输入事件分发协作
// InputMonitor中的输入法相关事件分发
final class InputMonitor implements InputManagerService.WindowManagerCallbacks {/*** 更新输入窗口信息,考虑输入法窗口*/public void updateInputWindowsLocked(boolean force) {if (!force && !mUpdateInputWindowsNeeded) {return;}mUpdateInputWindowsNeeded = false;// 清空现有窗口句柄mInputWindowHandles.clear();final ArrayList<WindowState> windows = mService.mWindows;// 首先添加输入法窗口(如果存在且可见)if (mService.mInputMethodWindow != null && mService.mInputMethodWindow.isVisibleLw()) {addInputMethodWindowToListLocked(mService.mInputMethodWindow);}// 添加输入法对话框窗口for (int i = mService.mInputMethodDialogs.size() - 1; i >= 0; i--) {WindowState dialog = mService.mInputMethodDialogs.get(i);if (dialog.isVisibleLw()) {addInputWindowHandleLw(dialog.mInputWindowHandle, dialog);}}// 添加其他窗口for (int i = windows.size() - 1; i >= 0; i--) {final WindowState child = windows.get(i);if (child == mService.mInputMethodWindow || mService.mInputMethodDialogs.contains(child)) {// 跳过已经添加的输入法窗口continue;}final InputChannel inputChannel = child.mInputChannel;final InputWindowHandle inputWindowHandle = child.mInputWindowHandle;if (inputChannel == null || inputWindowHandle == null || child.mRemoved) {continue;}final boolean hasFocus = (child == mInputFocus);final boolean isVisible = child.isVisibleLw();addInputWindowHandleLw(inputWindowHandle, child, hasFocus, isVisible);}// 发送输入窗口信息给InputManagermService.mInputManager.setInputWindows(mInputWindowHandles.toArray(new InputWindowHandle[mInputWindowHandles.size()]));}/*** 添加输入法窗口到输入窗口列表*/private void addInputMethodWindowToListLocked(WindowState imWindow) {final InputWindowHandle inputWindowHandle = imWindow.mInputWindowHandle;if (inputWindowHandle == null || imWindow.mRemoved) {return;}// 设置输入法窗口特殊属性inputWindowHandle.name = imWindow.toString();inputWindowHandle.layoutParamsType = imWindow.mAttrs.type;inputWindowHandle.layoutParamsFlags = imWindow.mAttrs.flags;inputWindowHandle.visible = imWindow.isVisibleLw();inputWindowHandle.canReceiveKeys = imWindow.canReceiveKeys();inputWindowHandle.hasFocus = false; // 输入法窗口通常不获得焦点inputWindowHandle.hasWallpaper = false;inputWindowHandle.paused = false;inputWindowHandle.layer = imWindow.mLayer;inputWindowHandle.ownerPid = imWindow.mSession.mPid;inputWindowHandle.ownerUid = imWindow.mSession.mUid;inputWindowHandle.inputFeatures = imWindow.mAttrs.inputFeatures;// 设置输入法窗口的触摸区域final Rect frame = imWindow.mFrame;inputWindowHandle.frameLeft = frame.left;inputWindowHandle.frameTop = frame.top;inputWindowHandle.frameRight = frame.right;inputWindowHandle.frameBottom = frame.bottom;inputWindowHandle.touchableRegion.set(frame);mInputWindowHandles.add(inputWindowHandle);}
}
4. 输入连接和文本操作
4.1 InputConnection接口
/*** InputConnection接口定义了输入法与应用程序之间的文本操作协议*/
public interface InputConnection {/*** 获取光标周围的文本*/CharSequence getTextBeforeCursor(int n, int flags);CharSequence getTextAfterCursor(int n, int flags);/*** 获取当前选中的文本*/CharSequence getSelectedText(int flags);/*** 获取光标位置*/int getCursorCapsMode(int reqModes);/*** 获取提取的文本*/ExtractedText getExtractedText(ExtractedTextRequest request, int flags);/*** 删除光标周围的文本*/boolean deleteSurroundingText(int beforeLength, int afterLength);boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);/*** 设置文本选择*/boolean setSelection(int start, int end);/*** 设置组合文本*/boolean setComposingText(CharSequence text, int newCursorPosition);boolean setComposingRegion(int start, int end);/*** 完成组合文本*/boolean finishComposingText();/*** 提交文本*/boolean commitText(CharSequence text, int newCursorPosition);/*** 提交补全信息*/boolean commitCompletion(CompletionInfo text);boolean commitCorrection(CorrectionInfo correctionInfo);/*** 发送按键事件*/boolean sendKeyEvent(KeyEvent event);/*** 清除元数据状态*/boolean clearMetaKeyStates(int states);/*** 报告全屏模式*/boolean reportFullscreenMode(boolean enabled);/*** 执行私有命令*/boolean performPrivateCommand(String action, Bundle data);/*** 请求光标更新*/boolean requestCursorUpdates(int cursorUpdateMode);/*** 获取Handler用于回调*/Handler getHandler();/*** 关闭连接*/void closeConnection();/*** 提交内容*/boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts);
}// BaseInputConnection - InputConnection的基础实现
public class BaseInputConnection implements InputConnection {final View mTargetView; // 目标视图final ComposingText mComposingText; // 组合文本管理private Object[] mDefaultComposingSpans; // 默认组合文本样式Editable mEditable; // 可编辑文本KeyCharacterMap mKeyCharacterMap; // 按键字符映射BaseInputConnection(View targetView, boolean fullEditor) {mTargetView = targetView;mComposingText = fullEditor ? new ComposingText() : null;}@Overridepublic boolean commitText(CharSequence text, int newCursorPosition) {if (DEBUG) Log.v(TAG, "commitText " + text);replaceText(text, newCursorPosition, false);sendCurrentText();return true;}@Overridepublic boolean setComposingText(CharSequence text, int newCursorPosition) {if (DEBUG) Log.v(TAG, "setComposingText " + text);replaceText(text, newCursorPosition, true);return true;}/*** 替换文本的核心方法*/private void replaceText(CharSequence text, int newCursorPosition, boolean composing) {final Editable content = getEditable();if (content == null) {return;}beginBatchEdit();// 删除当前组合文本removeComposingSpans(content);// 计算新的光标位置int a, b;if (mComposingTextStart < 0) {a = Selection.getSelectionStart(content);b = Selection.getSelectionEnd(content);} else {a = mComposingTextStart;b = mComposingTextEnd;}if (a < 0) a = 0;if (b < 0) b = 0;if (b < a) {int tmp = a;a = b;b = tmp;}// 替换文本if (text != null) {content.replace(a, b, text);if (composing) {// 标记为组合文本Spannable sp = (Spannable) content;setComposingSpans(sp, a, a + text.length());mComposingTextStart = a;mComposingTextEnd = a + text.length();} else {mComposingTextStart = mComposingTextEnd = -1;}// 设置新的光标位置if (newCursorPosition > 0) {newCursorPosition += a + text.length() - 1;} else {newCursorPosition += a;}if (newCursorPosition < 0) newCursorPosition = 0;if (newCursorPosition > content.length()) newCursorPosition = content.length();Selection.setSelection(content, newCursorPosition);}endBatchEdit();}
}
4.2 IInputContext包装器
// IInputConnectionWrapper - 跨进程InputConnection包装器
final class IInputConnectionWrapper extends IInputContext.Stub {private static final String TAG = "IInputConnectionWrapper";private final WeakReference<InputMethodManager> mInputMethodManager;private final WeakReference<View> mServedView;private final WeakReference<InputConnection> mInputConnection;private final Looper mMainLooper;private final Handler mH;// 消息处理private static final int DO_GET_TEXT_BEFORE_CURSOR = 10;private static final int DO_GET_TEXT_AFTER_CURSOR = 20;private static final int DO_GET_SELECTED_TEXT = 25;private static final int DO_GET_CURSOR_CAPS_MODE = 30;private static final int DO_GET_EXTRACTED_TEXT = 40;private static final int DO_COMMIT_TEXT = 50;private static final int DO_COMMIT_COMPLETION = 55;private static final int DO_COMMIT_CORRECTION = 56;private static final int DO_SET_SELECTION = 57;private static final int DO_PERFORM_EDITOR_ACTION = 58;private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 59;private static final int DO_SET_COMPOSING_TEXT = 60;private static final int DO_SET_COMPOSING_REGION = 63;private static final int DO_FINISH_COMPOSING_TEXT = 65;private static final int DO_SEND_KEY_EVENT = 70;private static final int DO_DELETE_SURROUNDING_TEXT = 80;private static final int DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 81;private static final int DO_BEGIN_BATCH_EDIT = 90;private static final int DO_END_BATCH_EDIT = 95;private static final int DO_REPORT_FULLSCREEN_MODE = 100;private static final int DO_PERFORM_PRIVATE_COMMAND = 120;private static final int DO_CLEAR_META_KEY_STATES = 130;private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;private static final int DO_CLOSE_CONNECTION = 150;private static final int DO_COMMIT_CONTENT = 160;class MyHandler extends Handler {MyHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {executeMessage(msg);}}IInputConnectionWrapper(Looper mainLooper, InputConnection conn, InputMethodManager inputMethodManager, View servedView) {mInputConnection = new WeakReference<>(conn);mMainLooper = mainLooper;mH = new MyHandler(mMainLooper);mInputMethodManager = new WeakReference<>(inputMethodManager);mServedView = new WeakReference<>(servedView);}@Overridepublic void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) {dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback));}@Overridepublic void commitText(CharSequence text, int newCursorPosition, int seq,IInputContextCallback callback) {dispatchMessage(obtainMessageIOSC(DO_COMMIT_TEXT, newCursorPosition,text, seq, callback));}@Overridepublic void setComposingText(CharSequence text, int newCursorPosition, int seq,IInputContextCallback callback) {dispatchMessage(obtainMessageIOSC(DO_SET_COMPOSING_TEXT, newCursorPosition,text, seq, callback));}void executeMessage(Message msg) {switch (msg.what) {case DO_GET_TEXT_BEFORE_CURSOR: {SomeArgs args = (SomeArgs) msg.obj;try {InputConnection ic = getInputConnection();if (ic == null || !isActive()) {Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");args.callback.setTextBeforeCursor(null, args.seq);return;}args.callback.setTextBeforeCursor(ic.getTextBeforeCursor(args.argi1, args.argi2), args.seq);} catch (RemoteException e) {Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e);}break;}case DO_COMMIT_TEXT: {SomeArgs args = (SomeArgs) msg.obj;try {InputConnection ic = getInputConnection();if (ic == null || !isActive()) {Log.w(TAG, "commitText on inactive InputConnection");args.callback.commitText(args.seq);return;}ic.commitText((CharSequence) args.arg1, args.argi1);args.callback.commitText(args.seq);} catch (RemoteException e) {Log.w(TAG, "Got RemoteException calling commitText", e);}break;}case DO_SET_COMPOSING_TEXT: {SomeArgs args = (SomeArgs) msg.obj;try {InputConnection ic = getInputConnection();if (ic == null || !isActive()) {Log.w(TAG, "setComposingText on inactive InputConnection");args.callback.setComposingText(args.seq);return;}ic.setComposingText((CharSequence) args.arg1, args.argi1);args.callback.setComposingText(args.seq);} catch (RemoteException e) {Log.w(TAG, "Got RemoteException calling setComposingText", e);}break;}// ... 其他消息处理}}private InputConnection getInputConnection() {InputConnection ic = mInputConnection.get();if (ic == null) {return null;}return ic;}private boolean isActive() {InputMethodManager imm = mInputMethodManager.get();View servedView = mServedView.get();return imm != null && servedView != null && imm.isActive(servedView) && imm.hasActiveConnection();}
}
5. 输入法窗口管理
5.1 SoftInputWindow实现
// InputMethodService中的窗口管理
public class InputMethodService {/*** 软输入窗口实现*/public class SoftInputWindow extends Dialog {final String mName;final Callback mCallback;final KeyEvent.Callback mKeyEventCallback;final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();final int mWindowType;final int mGravity;final boolean mTakesFocus;private final Rect mBounds = new Rect();SoftInputWindow(Context context, String name, int theme, Callback callback, KeyEvent.Callback keyEventCallback,KeyEvent.DispatcherState dispatcherState, int windowType,int gravity, boolean takesFocus) {super(context, theme);mName = name;mCallback = callback;mKeyEventCallback = keyEventCallback;mWindowType = windowType;mGravity = gravity;mTakesFocus = takesFocus;initDockWindow();}@Overridepublic void onWindowFocusChanged(boolean hasFocus) {super.onWindowFocusChanged(hasFocus);mDispatcherState.reset();}@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {if (mKeyEventCallback != null && mKeyEventCallback.onKeyDown(event.getKeyCode(), event)) {return true;}return super.dispatchKeyEvent(event);}@Overridepublic boolean dispatchKeyShortcutEvent(KeyEvent event) {return super.dispatchKeyShortcutEvent(event);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {// 处理触摸事件getWindow().getDecorView().dispatchTouchEvent(ev);return true;}/*** 初始化停靠窗口*/void initDockWindow() {WindowManager.LayoutParams lp = getWindow().getAttributes();lp.type = mWindowType;lp.setTitle(mName);if (mTakesFocus) {lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;} else {lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;}lp.gravity = mGravity;updateWidthHeight(lp);getWindow().setAttributes(lp);}/*** 更新窗口尺寸*/void updateWidthHeight(WindowManager.LayoutParams lp) {if (lp.width != ViewGroup.LayoutParams.WRAP_CONTENT) {if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {lp.width = mBounds.width();}}if (lp.height != ViewGroup.LayoutParams.WRAP_CONTENT) {if (lp.height == ViewGroup.LayoutParams.MATCH_PARENT) {lp.height = mBounds.height();}}}}/*** 显示输入法窗口*/public void showWindow(boolean showInput) {if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput+ " mShowInputRequested=" + mShowInputRequested+ " mWindowVisible=" + mWindowVisible+ " mInputStarted=" + mInputStarted+ " mShowInputFlags=" + mShowInputFlags);if (mInShowWindow) {Log.w(TAG, "Re-entrance in to showWindow");return;}try {mWindowVisible = true;mInShowWindow = true;showWindowInner(showInput);} catch (BadTokenException e) {// 令牌可能已经无效,隐藏窗口mWindowVisible = false;mWindowAdded = false;hideWindow();throw e;} finally {mInShowWindow = false;}}/*** 显示窗口内部实现*/void showWindowInner(boolean showInput) {boolean doShowInput = false;final boolean wasVisible = mWindowVisible;mWindowVisible = true;mShowInputRequested = showInput;if (!mWindowAdded || !mWindowCreated) {mWindowAdded = true;mWindowCreated = true;initialize();onCreateCandidatesView();onCreateInputView();}if (DEBUG) Log.v(TAG, "showWindow: updating UI");onConfigureWindow(mWindow, mInputEditorInfo, mShowInputRequested);if (!mWindowVisible) {mWindow.show();mShouldClearInsetOfPreviousIme = true;}onWindowShown();mWindowVisible = true;if ((mShowInputRequested || mInputViewStarted) && mInputView != null) {if (DEBUG) Log.v(TAG, "showWindow: showing input");doShowInput = true;}if (doShowInput) {showInputViewInner();}}/*** 隐藏输入法窗口*/public void hideWindow() {if (DEBUG) Log.v(TAG, "hideWindow");finishViews();if (mWindowVisible) {mWindow.hide();mWindowVisible = false;onWindowHidden();mShouldClearInsetOfPreviousIme = false;}}/*** 配置窗口属性*/public void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly) {final int currentHeight = mWindow.getWindow().getDecorView().getHeight();final int newHeight = onComputeInsets(mTmpInsets);if (currentHeight != newHeight) {if (DEBUG) {Log.v(TAG, "onConfigureWindow: height changed " + currentHeight + " -> " + newHeight);}mWindow.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, newHeight);}}
}
5.2 输入法窗口层级管理
// InputMethodManagerService中的窗口显示控制
public class InputMethodManagerService {/*** 显示当前输入法*/boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {mShowRequested = true;mShowRequestedFlags = flags;mShowForced = (flags & InputMethodManager.SHOW_FORCED) != 0;mShowExplicitlyRequested = (flags & InputMethodManager.SHOW_IMPLICIT) == 0;if (mCurMethod != null) {if (DEBUG) Slog.v(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod, resultReceiver));mInputShown = true;if (mHaveConnection && !mVisibleBound) {bindCurrentInputMethodService(mCurIntent, mVisibleConnection, VISIBLE_SERVICE_BIND_FLAGS);mVisibleBound = true;}res = true;} else if (mHaveConnection && SystemClock.uptimeMillis()>= (mLastBindTime + TIME_TO_RECONNECT)) {// 重新绑定输入法服务EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,SystemClock.uptimeMillis() - mLastBindTime, 0);Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");mContext.unbindService(this);bindCurrentInputMethodService(mCurIntent, this, IME_CONNECTION_BIND_FLAGS);} else {if (DEBUG) {Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "+ ((mLastBindTime + TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));}}return res;}/*** 隐藏当前输入法*/boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {if ((flags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0&& (mShowExplicitlyRequested || mShowForced)) {if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");return false;}if (mShowForced && (flags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) {if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");return false;}boolean res;if (mInputShown && mCurMethod != null) {executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));res = true;} else {res = false;}if (mHaveConnection && mVisibleBound) {mContext.unbindService(mVisibleConnection);mVisibleBound = false;}mInputShown = false;mShowRequested = false;mShowExplicitlyRequested = false;mShowForced = false;return res;}/*** 获取输入法显示标志*/private int getImeShowFlags() {int flags = 0;if (mShowForced) {flags |= InputMethod.SHOW_FORCED| InputMethod.SHOW_EXPLICIT;} else if (mShowExplicitlyRequested) {flags |= InputMethod.SHOW_EXPLICIT;}return flags;}/*** 设置输入法窗口状态*/public void setImeWindowStatus(IBinder token, IBinder startInputToken, int vis,int backDisposition) {if (DEBUG) Slog.v(TAG, "setImeWindowStatus: vis=" + vis+ " backDisposition=" + backDisposition);synchronized (mMethodMap) {if (!calledWithValidToken(token)) {return;}mImeWindowVis = vis;mBackDisposition = backDisposition;updateSystemUiLocked(vis, backDisposition);}final long ident = Binder.clearCallingIdentity();try {// 通知窗口管理器输入法窗口状态变化if (mCurToken == token) {mWindowManagerInternal.setInputMethodWindowState(token,(vis & InputMethodService.IME_VISIBLE) != 0,(vis & InputMethodService.IME_VISIBLE) != 0 &&(vis & InputMethodService.IME_INPUT_SHOWN) != 0);}} finally {Binder.restoreCallingIdentity(ident);}}
}
6. 性能优化和调试
6.1 输入法性能监控
性能指标 | 监控方法 | 优化目标 |
---|---|---|
绑定延迟 | SERVICE_CONNECT耗时 | < 200ms |
显示延迟 | showSoftInput耗时 | < 100ms |
隐藏延迟 | hideSoftInput耗时 | < 50ms |
文本提交延迟 | commitText响应时间 | < 16ms |
内存使用 | InputConnection对象数量 | 合理范围 |
6.2 调试工具和日志
// InputMethodManagerService中的调试支持
public class InputMethodManagerService {private static final boolean DEBUG = false;private static final boolean DEBUG_RESTORE = DEBUG || false;private static final boolean DEBUG_FLOW = DEBUG || false;/*** 转储输入法状态*/@Overrideprotected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;final Printer p = new PrintWriterPrinter(pw);synchronized (mMethodMap) {p.println("Current Input Method Manager state:");int N = mMethodList.size();p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);for (int i = 0; i < N; i++) {InputMethodInfo info = mMethodList.get(i);p.println(" InputMethod #" + i + ":");info.dump(p, " ");}p.println(" Clients:");for (ClientState ci : mClients.values()) {p.println(" Client " + ci + ":");p.println(" sessions=" + ci.sessions.size());p.println(" uid=" + ci.uid + " pid=" + ci.pid);}p.println(" mCurMethodId=" + mCurMethodId);p.println(" mCurClient=" + mCurClient);p.println(" mCurMethod=" + mCurMethod);p.println(" mCurToken=" + mCurToken);p.println(" mCurIntent=" + mCurIntent);p.println(" mShowRequested=" + mShowRequested+ " mShowExplicitlyRequested=" + mShowExplicitlyRequested+ " mShowForced=" + mShowForced+ " mInputShown=" + mInputShown);p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mScreenOn);p.println(" mSettingsObserver=" + mSettingsObserver);}}/*** 调试输入连接状态*/private void dumpInputConnection(PrintWriter pw, InputConnection ic) {pw.println(" InputConnection:");if (ic != null) {try {CharSequence before = ic.getTextBeforeCursor(10, 0);CharSequence after = ic.getTextAfterCursor(10, 0);CharSequence selected = ic.getSelectedText(0);pw.println(" textBefore='" + before + "'");pw.println(" textAfter='" + after + "'");pw.println(" selectedText='" + selected + "'");} catch (Exception e) {pw.println(" Exception: " + e);}} else {pw.println(" null");}}
}// InputMethodManager中的调试支持
public final class InputMethodManager {private static final boolean DEBUG = false;/*** 转储客户端状态*/public void dumpDebug(ProtoOutputStream proto, long fieldId) {final long token = proto.start(fieldId);synchronized (mH) {proto.write(IMM_DISPLAY_ID, mDisplayId);proto.write(IMM_CUR_ID, Objects.toString(mCurId));proto.write(IMM_FULLSCREEN_MODE, mFullscreenMode);proto.write(IMM_ACTIVE, mActive);proto.write(IMM_SERVED_CONNECTING, mServedConnecting);}proto.end(token);}/*** 检查焦点一致性*/void checkFocus() {if (checkFocusNoStartInput(false)) {startInputInner(StartInputReason.CHECK_FOCUS, null, 0, 0, 0);}}boolean checkFocusNoStartInput(boolean forceNewSequenceNumber) {// This is just checking that the current focus is the same as the last// focus reported by the current active view. This is really only needed// to catch bugs in the view hierarchy that would otherwise cause us to// think that the focus has changed when it really hasn't -- the end result// being that we start input on a view that doesn't really have focus.if (mServedView == mNextServedView && !forceNewSequenceNumber) {return false;}if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView+ " next=" + mNextServedView+ " forceNewSequenceNumber=" + forceNewSequenceNumber+ " package=" + (mServedView != null? mServedView.getContext().getPackageName() : "<none>"));if (mNextServedView == null) {finishInputLocked();// In this case, we used to have a focused view on the window,// but no longer do. We should stop any input method (note that// hasWindowFocus() should have been true and thus we should// be stopping input as needed via the setWindowFocus() path).closeCurrentInput();return false;}final View servedView = mNextServedView;// Focus has moved to a different view.mServedView = servedView;mCompletions = null;mServedConnecting = true;return true;}
}
7. 总结
7.1 输入法框架核心价值
- 统一输入接口: 为所有应用提供标准化的文本输入接口
- 灵活输入法切换: 支持多种输入法的动态切换和管理
- 高效进程间通信: 通过Binder实现输入法与应用的高效交互
- 智能窗口管理: 与窗口系统深度集成的输入法窗口管理
- 丰富文本操作: 支持组合文本、自动补全、纠错等高级功能
7.2 协作机制特点
- 三层架构: 应用层、系统服务层、输入法进程的清晰分层
- 事件驱动: 基于焦点变化和用户交互的事件驱动机制
- 异步通信: 通过Handler和Binder实现的异步消息传递
- 状态同步: 多组件间的状态一致性维护
- 资源管理: 智能的输入法生命周期和资源管理
7.3 系统集成优势
输入法框架作为Android UI系统的重要组成部分:
- 与WindowManager集成: 智能的窗口焦点和层级管理
- 与InputManager协作: 高效的输入事件分发和处理
- 与ActivityManager协调: 应用生命周期感知的输入法管理
- 与PackageManager联动: 输入法应用的安装、更新和权限管理
相关文件路径
系统服务文件
frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java
- 输入法管理服务frameworks/base/services/java/com/android/server/SystemServer.java
- 系统服务启动
客户端框架
frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java
- 客户端输入法管理器frameworks/base/core/java/android/view/inputmethod/InputConnection.java
- 输入连接接口frameworks/base/core/java/android/inputmethodservice/InputMethodService.java
- 输入法服务基类
窗口集成
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
- 窗口管理服务frameworks/base/core/java/android/view/ViewRootImpl.java
- 视图根实现
输入系统集成
frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
- 输入监控器frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
- 输入管理服务