在PC时代,输入法的原始输入来自实体键盘,鼠标,然后输入法将这些事件对应的ASCII码转换为俄文,中文,当然如果是英文是不需要转换,直接发送即可。而在Android系统里,由于输入法dialog永远没法成为焦点window,所以输入法永远没法获取到按键事件,也就是说输入法的输入数据只能来自触摸事件,输入法显示出键盘(大家称之为软键盘),用户点击键盘UI, 然后输入法将触摸事件所在位置的字符当做原始字符输入,最后组装成更为丰富的字符(多个字符组成拼音,然后转化为中文),然后就是发送到对应的程序。
1、输入法整体框架图
输入法系统的整个框架如下:
InputMethodManagerService(下文也称IMMS)负责管理系统的所有输入法,包括输入法service(InputMethodService简称IMS)加载及切换。程序获得焦点时,就会通过InputMethodManager向InputMethodManagerService通知自己获得焦点并请求绑定自己到当前输入法上。同时,当程序的某个需要输入法的view比如EditorView获得焦点时就会通过InputMethodManager向InputMethodManagerService请求显示输入法,而这时InputMethodManagerService收到请求后,会将请求的EditText的数据通信接口发送给当前输入法,并请求显输入法。输入法收到请求后,就显示自己的UI dialog,同时保存目标view的数据结构,当用户实现输入后,直接通过view的数据通信接口将字符传递到对应的View。接下来就来分析这些过程。
2、InputMethodManager创建
每个应用APP程序有一个InputMethodManager实例,这个是应用APP程序和InputMethodManagerService通信的接口,该实例在ViewRootImpl初始化的时候创建。
//frameworks/base/core/java/android/view/ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
}
//frameworks/base/core/java/android/view/WindowManagerGlobal.java
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
//这个进程的InputMethodManager实例就生成了
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
// InputMethodManager其实就是一个Binder service的proxy
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
3、APP程序的Window获得焦点
程序的window获得焦点的时序图如下
哪个程序获得焦点是由系统决定的,是由WindowManagerService决定的,当系统的window状态发生变化时(比如window新增,删除)就会调用函数updateFocusedWindowLocked来更新焦点window。
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
//计算焦点window ----见3.1
WindowState newFocus = computeFocusedWindowLocked();
if (mCurrentFocus != newFocus) {
//焦点window发生变化,post一个message来通知程序焦点发生变化了
mH.removeMessages(H.REPORT_FOCUS_CHANGE);
mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE); //见3.2
//省略.....
return true;
}
return false;
}
3.1 焦点window计算
//frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
WindowState computeFocusedWindow() {
// While the keyguard is showing, we must focus anything besides the main display.
// Otherwise we risk input not going to the keyguard when the user expects it to.
final boolean forceDefaultDisplay = mService.isKeyguardShowingAndNotOccluded();
for (int i = mChildren.size() - 1; i >= 0; i--) {
final DisplayContent dc = mChildren.get(i);
final WindowState win = dc.findFocusedWindow();
if (win != null) {
if (forceDefaultDisplay && !dc.isDefaultDisplay) {
EventLog.writeEvent(0x534e4554, "71786287", win.mOwnerUid, "");
continue;
}
return win;
}
}
return null;
}
//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
WindowState findFocusedWindow() {
mTmpWindow = null;
forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);
if (mTmpWindow == null) {
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows.");
return null;
}
return mTmpWindow;
}
//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
@Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
if (mChildren.isEmpty()) {
// The window has no children so we just return it.
return applyInOrderWithImeWindows(callback, traverseTopToBottom);
}
if (traverseTopToBottom) {//因为traverseTopToBottom为true,故走这个分支
return forAllWindowTopToBottom(callback);
} else {
return forAllWindowBottomToTop(callback);
}
}
private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback,
boolean traverseTopToBottom) {
if (traverseTopToBottom) {
//此处的callback实际上是传入的mFindFocusedWindow,故callback.apply(this)为回调
if (applyImeWindowsIfNeeded(callback, traverseTopToBottom)
|| callback.apply(this)) {
return true;
}
} else {
if (callback.apply(this)
|| applyImeWindowsIfNeeded(callback, traverseTopToBottom)) {
return true;
}
}
return false;
}
//callback.apply(this)为回调函数实现
//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
final AppWindowToken focusedApp = mService.mFocusedApp;
if (DEBUG_FOCUS) Slog.v(TAG_WM, "Looking for focus: " + w
+ ", flags=" + w.mAttrs.flags + ", canReceive=" + w.canReceiveKeys());
if (!w.canReceiveKeys()) {
return false;
}
final AppWindowToken wtoken = w.mAppToken;
// If this window's application has been removed, just skip it.
if (wtoken != null && (wtoken.removed || wtoken.sendingToBottom)) {
if (DEBUG_FOCUS) Slog.v(TAG_WM, "Skipping " + wtoken + " because "
+ (wtoken.removed ? "removed" : "sendingToBottom"));
return false;
}
if (focusedApp == null) {
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp=null"
+ " using new focus @ " + w);
mTmpWindow = w;
return true;
}
if (!focusedApp.windowsAreFocusable()) {
// Current focused app windows aren't focusable...
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp windows not"
+ " focusable using new focus @ " + w);
mTmpWindow = w;
return true;
}
// Descend through all of the app tokens and find the first that either matches
// win.mAppToken (return win) or mFocusedApp (return null).
if (wtoken != null && w.mAttrs.type != TYPE_APPLICATION_STARTING) {
if (focusedApp.compareTo(wtoken) > 0) {
// App stack below focused app stack. No focus for you!!!
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM,
"findFocusedWindow: Reached focused app=" + focusedApp);
mTmpWindow = null;
return true;
}
}
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Found new focus @ " + w);
mTmpWindow = w;
return true;
};
3.2 焦点window切换
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
//接下来系统开始通知程序端哪个window获得了焦点。
final class H extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case REPORT_FOCUS_CHANGE: {
WindowState lastFocus;
WindowState newFocus;
//省略。。。
if (newFocus != null) {
//通知新的焦点程序其获得了焦点
newFocus.reportFocusChangedSerialized(true, mInTouchMode);
notifyFocusChanged();
}
if (lastFocus != null) {
//通知老的焦点程序其获得了焦点
lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
}
} break;
}
//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
public void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
try {
//这个就是通过Binder告知client其获得或失去了焦点
//mClient.windowFocusChanged会调回到ViewRootImpl中的W实例
mClient.windowFocusChanged(focused, inTouchMode);
} catch (RemoteException e) {
}
//省略。。。
}
通知应用APP程序获得焦点改变事件
//ViewRootImpl.java
static class W extends IWindow.Stub {
@Override
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
}
}
//ViewRootImpl.java
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
Message msg = Message.obtain();
msg.what = MSG_WINDOW_FOCUS_CHANGED;
msg.arg1 = hasFocus ? 1 : 0;
msg.arg2 = inTouchMode ? 1 : 0;
mHandler.sendMessage(msg);
}
//ViewRootImpl.java
//程序获得焦点会通过调用mView.dispatchWindowFocusChanged和
//imm.onWindowFocus来通知IMMS焦点信息发生改变,需要更新输入法了
final class ViewRootHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_WINDOW_FOCUS_CHANGED: {
handleWindowFocusChanged();
} break;
}
private void handleWindowFocusChanged() {
//省略...
if (mView != null) {
mAttachInfo.mKeyDispatchState.reset();
//调用根view的dispatchWindowFocusChanged函数通知view
//程序获得焦点
mView.dispatchWindowFocusChanged(hasWindowFocus);
mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
if (mAttachInfo.mTooltipHost != null) {
mAttachInfo.mTooltipHost.hideTooltip();
}
}
// Note: must be done after the focus change callbacks,
// so all of the view state is set up correctly.
if (hasWindowFocus) {
if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
//通知imm该window获得焦点
imm.onPostWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
//省略...
}
//省略...
}
//上面的根view就是DecorView,它只是调用父类ViewGroup
//的dispatchWindowFocusChanged
//ViewGroup.java
@Override
public void dispatchWindowFocusChanged(boolean hasFocus) {
super.dispatchWindowFocusChanged(hasFocus);
final int count = mChildrenCount;
final View[] children = mChildren;
//让每个子view处理window焦点改变时间
//但是只有获得焦点的view才会处理这个时间
for (int i = 0; i < count; i++) {
children[i].dispatchWindowFocusChanged(hasFocus);
}
}
//View.java
public void dispatchWindowFocusChanged(boolean hasFocus) {
onWindowFocusChanged(hasFocus);
}
public void onWindowFocusChanged(boolean hasWindowFocus) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (!hasWindowFocus) {
} else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
//获得焦点的view通过 InputMethodManager向service通知自己获得焦点
imm.focusIn(this);
}
}
4、焦点View向IMMS请求绑定输入法
焦点view请求绑定输入法是通过调用InputMethodManager. focusIn实现的
//frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java
public void focusIn(View view) {
synchronized (mH) {
focusInLocked(view);
}
}
void focusInLocked(View view) {
//保存焦点view变量
mNextServedView = view;
scheduleCheckFocusLocked(view);
}
static void scheduleCheckFocusLocked(View view) {
ViewRootImpl viewRootImpl = view.getViewRootImpl();
if (viewRootImpl != null) {
viewRootImpl.dispatchCheckFocus();
}
}
//frameworks/base/core/java/android/view/ViewRootImpl.java
public void dispatchCheckFocus() {
if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
// This will result in a call to checkFocus() below.
mHandler.sendEmptyMessage(MSG_CHECK_FOCUS);
}
}
public void handleMessage(Message msg) {
switch (msg.what) {
//省略。。。
case MSG_CHECK_FOCUS: {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
imm.checkFocus();
}
} break;
//省略。。。
}
//frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java
public void checkFocus() {
if (checkFocusNoStartInput(false)) {
startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
}
}
//frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java
boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
IBinder windowGainingFocus, int controlFlags, int softInputMode,
int windowFlags) {
final View view;
synchronized (mH) {
//获得上面的焦点view
view = mServedView;
}
//省略。。。
//创建数据通信连接接口,这个会传送到InputMethodService
//InputMethodService后面就通过这个connection将输入法的字符传递给该view
InputConnection ic = view.onCreateInputConnection(tba);
if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
synchronized (mH) {
//省略。。。
ControlledInputConnectionWrapper servedContext;
final int missingMethodFlags;
if (ic != null) {
mCursorSelStart = tba.initialSelStart;
mCursorSelEnd = tba.initialSelEnd;
mCursorCandStart = -1;
mCursorCandEnd = -1;
mCursorRect.setEmpty();
mCursorAnchorInfo = null;
final Handler icHandler;
missingMethodFlags = InputConnectionInspector.getMissingMethodFlags(ic);
if ((missingMethodFlags & InputConnectionInspector.MissingMethodFlags.GET_HANDLER)
!= 0) {
// InputConnection#getHandler() is not implemented.
icHandler = null;
} else {
icHandler = ic.getHandler();
}
//将InputConnection封装为binder对象,这个是真正可以实现跨进程通
//信的封装类
servedContext = new ControlledInputConnectionWrapper(
icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
} else {
servedContext = null;
missingMethodFlags = 0;
}
mServedInputConnectionWrapper = servedContext;
try {
//通知IMMS处理view绑定输入法事件
final InputBindResult res = mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
windowFlags, tba, servedContext, missingMethodFlags,
view.getContext().getApplicationInfo().targetSdkVersion);
//省略。。。
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
}
return true;
}
到此view是如何获取焦点,并通知IMMS去绑定输入法的流程已经分析完成了。下一步将分析IMMS是如何处理view绑定输入法事件。未完待续。。。