概述

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 输入法框架核心价值

  1. 统一输入接口: 为所有应用提供标准化的文本输入接口
  2. 灵活输入法切换: 支持多种输入法的动态切换和管理
  3. 高效进程间通信: 通过Binder实现输入法与应用的高效交互
  4. 智能窗口管理: 与窗口系统深度集成的输入法窗口管理
  5. 丰富文本操作: 支持组合文本、自动补全、纠错等高级功能

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 - 输入管理服务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/94884.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/94884.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/94884.shtml

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

相关文章

解开 Ansible 任务复用谜题:过滤器用法、Include/Import 本质差异与任务文件价值详解

1. 什么是变量过滤器&#xff08;Variable Filters&#xff09;&#xff1f;请列举几个常用的Jinja2过滤器及其用途。变量过滤器是在Jinja2模板中用于修改或格式化变量输出的工具。常用过滤器&#xff1a;to_json/to_yaml&#xff1a;将数据结构&#xff08;如字典、列表&#…

LangGraph-笑话评估器 应用实战

场景&#xff1a;用户指定冷笑话主题&#xff0c;生成冷笑话后&#xff0c;进行评估&#xff0c;如果不搞笑就需要重新生成以下代码实现了一个基于LangGraph的冷笑话自动生成与评估工作流。系统包含两个核心节点&#xff1a;生成器根据用户主题创作冷笑话&#xff0c;评估器对笑…

Paimon——官网阅读:Flink 引擎

Flink 引擎 快速入门 本文档是在Flink中使用Paimon的指南。 相关JAR包 Paimon目前支持Flink 1.20、1.19、1.18、1.17、1.16、1.15 。为获得更好的体验&#xff0c;我们推荐使用最新的Flink版本。 下载对应版本的JAR文件。 目前&#xff0c;Paimon提供两种类型的JAR包&…

2025.8.28总结

工作日精进&#xff1a;今天终于把SPN控制器对接成功了&#xff0c;之前对接出现各种各样的问题&#xff0c;搞得自己都有些心力交瘁。感觉自己明明很忙&#xff0c;也在努力的去推动进度&#xff0c;但还是阻塞了两三天。最后求助了另一个同事&#xff0c;结果在他的指导和帮忙…

使用 Action 自动部署 VuePress 到 GitHub Pages

⚙️ 成果&#xff1a; 框架&#xff1a;VuePress 2 vuepress/plugin-blog打包工具&#xff1a;Vite&#xff08;viteBundler&#xff09;包管理&#xff1a;pnpmCI/CD&#xff1a;GitHub Actions部署方案&#xff1a; 源码仓库&#xff1a;urfread1010/mind-elevation打包结…

【人工智能】2025年AI代理开源革命:社区驱动的智能体生态重塑未来

还在为高昂的AI开发成本发愁?这本书教你如何在个人电脑上引爆DeepSeek的澎湃算力! 2025年,AI代理(AI Agents)的开源浪潮席卷全球,标志着人工智能从封闭的商业模式向社区驱动的协作生态转型。这一浪潮由开源社区主导,推动了智能体的快速迭代和民主化发展。本文深入探讨了…

QT:【第一个QT程序】【信号和槽】

目录 一. 第一个QT程序 1.1纯代码实现 1.2可视化操作实现 1.3 项目文件解析 1.3.1 .pro文件解析 1.3.2 widget.h 文件解析 1.3.3 main.cpp文件解析 1.3.4 widget.cpp 1.3.5 widget.ui文件解析 1.4对象树 演示自动释放的过程 1.5 中文乱码 1.6 Qt窗口坐标系 二. 信…

河南萌新联赛2025第(七)场:郑州轻工业大学

河南萌新联赛2025第&#xff08;七&#xff09;场&#xff1a;郑州轻工业大学 If I only could, Id be running up that hill&#xff01;郑轻有品&#xff01;背景终于不是二次元了… 是Stranger Things&#xff01;希望我能像主题曲里那样&#xff0c;勇攀高峰&#xff0c;R…

Java 获取淘宝商品详情(item get)API 接口实战指南

在电商领域&#xff0c;获取商品详情数据对于市场分析、价格监控、用户体验优化等场景具有重要意义。淘宝作为国内领先的电商平台&#xff0c;提供了丰富的 API 接口供开发者使用&#xff0c;其中 taobao.item.get 和 taobao.item.get_pro 接口可以用来获取商品的详细信息。本文…

配送算法17 AFramework for Multi-stage Bonus Allocation in meal delivery Platform

AFramework for Multi-stage Bonus Allocation in meal delivery Platform本文针对美团每日数十万单因无人接单而被取消的痛点&#xff0c;提出“多阶段动态奖金分配”框架&#xff1a;先用半黑盒模型预估奖金—接单概率关系&#xff0c;再用拉格朗日对偶动态规划离线算出阶段乘…

Python DELL Logo

写在前面 Python绘制Android Studio标志的完整代码。 系列文章 序号文章目录直达链接炫酷系列1无法拒绝的表白界面https://want595.blog.csdn.net/article/details/1347448942满屏飘字表白代码https://want595.blog.csdn.net/article/details/1350373883无限弹窗表白代码http…

【架构师干货】软件工程

1. 软件工程概述 软件工程基本原理 软件工程基本原理&#xff1a;通过划分生命周期阶段的方式严格管理、坚持进行阶段评审、实现严格的产品控制、采用现代程序设计技术、结果应能清楚地审查、开发小组的人员应少而精、承认不断改进软件工程实践的必要性。 软件开发生命周期 软件…

3.渗透-.IP地址-详解

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易锦网校 上一个内容&#xff1a;2.渗透-.WEB运行原理-ZBlog安装&#xff08;进一步理解数据库&#xff09; ip地址是互联网中的门牌号…

【数字投影】简单介绍数字展厅内投影融合技术的原理

投影融合技术 https://www.bmcyzs.com/ 是一种将多台投影机的画面无缝拼接成一个完整、统一的高分辨率大画面的技术。它的核心原理在于通过几何校正与边缘羽化等处理&#xff0c;消除设备间的物理缝隙与亮度差异&#xff0c;从而实现视觉上的完美一体化。这一过程高度依赖于专业…

UML状态图中entry/do/exit动作的深入解析与C/C++实现

<摘要> 本文将深入探讨UML状态图中entry、do和exit动作的概念、作用及实现方式&#xff0c;通过astah工具展示如何专业地建模这些元素&#xff0c;并提供完整的C/C代码实现解析。文章包含具体案例和最佳实践&#xff0c;帮助开发者掌握状态机设计的精髓。 <解析> U…

Vue3 Pinia 中 store.$dispose()的用法说明

在 Vue 3 的 Pinia 中&#xff0c;store.$dispose()方法用于手动销毁一个 store 实例&#xff0c;它会重置该 store 的状态并移除所有订阅&#xff08;如通过 $subscribe或 $onAction添加的监听器&#xff09;。如果你发现调用 store.$dispose()后没有达到预期效果&#xff0c;…

Java自定义程序使用Ollama实现本地ai调用

Ollama 提供 两套核心接口、三种常见输入风格、两种输出模式&#xff0c;你可以按需组合。 一、两套核心接口 /api/generate • 一问一答&#xff0c;无对话历史。 • 输入&#xff1a;单次 prompt&#xff0c;可选参数&#xff08;temperature、top_p、max_tokens …&#xff…

操作系统中的死锁是什么意思

问题操作系统中的死锁是什么意思我的回答死锁是指在操作系统中&#xff0c;两个或多个进程互相等待对方释放资源&#xff0c;导致这些进程都无法继续执行的一种状态。简单来说&#xff0c;就像两个人相互礼让过马路&#xff0c;结果谁也不肯先走&#xff0c;最后都卡在那里一样…

DA14531(Cortex-M0+)之Wake-up Interrupt Controller (WIC)

Wake-up Interrupt Controller (WIC) to allow the processor to be powered down during sleep, while interrupt sources are still allowed to wake up the system. 唤醒中断中断器&#xff0c;允许处理器休眠时关闭电源和时钟&#xff0c;但中断源可以唤醒系统。具备独立的…