1.简介
本文基于 android N,借鉴 http://blog.csdn.net/huangyabin001/article/details/28434989
,记录一下输入法显示的流程,相当于一篇读书笔记,方便记忆与学习
大体流程如下:
- InputMethodManagerService(下文也称IMMS)负责管理系统的所有输入法,包括输入法service(InputMethodService简称IMS)加载及切换。
- 程序获得焦点时,就会通过 InputMethodManager 向 InputMethodManagerService 发出请求绑定自己到当前输入法上。
- 当程序的某个需要输入法的view比如 EditView 获得焦点时,就会通过 InputMethodManager 向 InputMethodManagerService 请求显示输入法,而这时 InputMethodManagerService 收到请求后,会将请求的 EditText 的数据通信接口发送给当前输入法,并请求显示输入法。输入法收到请求后,就显示自己的 UI dialog,同时保存目标 view 的数据结构,当用户实现输入后,直接通过 view 的数据通信接口将字符传递到对应的 View 。接下来就来分析这些过程。
2. InputMethodManager 创建
// ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
public ViewRootImpl(Context context, Display display) {
...
mWindowSession = WindowManagerGlobal.getWindowSession();
...
}
// WindowManagerGlobal.java
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
// 生成 InputMethodManager 实例
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
// InputMethodManager.java
/**
* Retrieve the global InputMethodManager instance, creating it if it
* doesn't already exist.
* @hide
*/
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
3. window 获得焦点
流程图上传后不太清晰,在csdn中上传了原图:http://download.csdn.net/detail/jieqiong1/9835293
// WindowManagerService.java
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
// 计算焦点 window
WindowState newFocus = computeFocusedWindowLocked();
if (mCurrentFocus != newFocus) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
// This check makes sure that we don't already have the focus
// change message pending.
mH.removeMessages(H.REPORT_FOCUS_CHANGE);
mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
...
return true;
}
return false;
}
private WindowState computeFocusedWindowLocked() {
final int displayCount = mDisplayContents.size();
for (int i = 0; i < displayCount; i++) {
final DisplayContent displayContent = mDisplayContents.valueAt(i);
WindowState win = findFocusedWindowLocked(displayContent);
if (win != null) {
return win;
}
}
return null;
}
// 找出 top 需要获得焦点的 window
WindowState findFocusedWindowLocked(DisplayContent displayContent) {
final WindowList windows = displayContent.getWindowList();
for (int i = windows.size() - 1; i >= 0; i--) {
final WindowState win = windows.get(i);
if (localLOGV || DEBUG_FOCUS) Slog.v(
TAG_WM, "Looking for focus: " + i
+ " = " + win
+ ", flags=" + win.mAttrs.flags
+ ", canReceive=" + win.canReceiveKeys());
// 判断 window 是否可以获取焦点
if (!win.canReceiveKeys()) {
continue;
}
// win.mAppToken != null win描述的是一个Activity窗口
AppWindowToken wtoken = win.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"));
continue;
}
// Descend through all of the app tokens and find the first that either matches
// win.mAppToken (return win) or mFocusedApp (return null).
// mFocusedApp 是 top Activity,下边的逻辑是为了确保焦点window的app 必须是焦点程序上的,主要是为了检测出错误
if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING &&
mFocusedApp != null) {
ArrayList<Task> tasks = displayContent.getTasks();
for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
int tokenNdx = tokens.size() - 1;
for ( ; tokenNdx >= 0; --tokenNdx) {
final AppWindowToken token = tokens.get(tokenNdx);
if (wtoken == token) {
break;
}
if (mFocusedApp == token && token.windowsAreFocusable()) {
// Whoops, we are below the focused app whose windows are focusable...
// No focus for you!!!
if (localLOGV || DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM,
"findFocusedWindow: Reached focused app=" + mFocusedApp);
return null;
}
}
if (tokenNdx >= 0) {
// Early exit from loop, must have found the matching token.
break;
}
}
}
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Found new focus @ " + i +
" = " + win);
return win;
}
if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows.");
return null;
}
@Override
public void handleMessage(Message msg) {
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG_WM, "handleMessage: entry what=" + msg.what);
}
switch (msg.what) {
case REPORT_FOCUS_CHANGE: {
WindowState lastFocus;
WindowState newFocus;
AccessibilityController accessibilityController = null;
synchronized(mWindowMap) {
// TODO(multidisplay): Accessibility supported only of default desiplay.
if (mAccessibilityController != null && getDefaultDisplayContentLocked()
.getDisplayId() == Display.DEFAULT_DISPLAY) {
accessibilityController = mAccessibilityController;
}
lastFocus = mLastFocus;
newFocus = mCurrentFocus;
if (lastFocus == newFocus) {
// Focus is not changing, so nothing to do.
return;
}
mLastFocus = newFocus;
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Focus moving from " + lastFocus +
" to " + newFocus);
if (newFocus != null && lastFocus != null
&& !newFocus.isDisplayedLw()) {
//Slog.i(TAG_WM, "Delaying loss of focus...");
mLosingFocus.add(lastFocus);
lastFocus = null;
}
}
// First notify the accessibility manager for the change so it has
// the windows before the newly focused one starts firing eventgs.
if (accessibilityController != null) {
accessibilityController.onWindowFocusChangedNotLocked();
}
//System.out.println("Changing focus from " + lastFocus
// + " to " + newFocus);
if (newFocus != null) {
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Gaining focus: " + newFocus);
// 通知新的焦点程序 获得了焦点
newFocus.reportFocusChangedSerialized(true, mInTouchMode);
notifyFocusChanged();
}
if (lastFocus != null) {
if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing focus: " + lastFocus);
// 通知老的焦点程序 获得了焦点
lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
}
} break;
...
}
// WindowState.java
/**
* Report a focus change. Must be called with no locks held, and consistently
* from the same serialized thread (such as dispatched from a handler).
*/
public void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
try {
// 通过 Binder 告知 client端 其获得或失去了焦点
mClient.windowFocusChanged(focused, inTouchMode);
} catch (RemoteException e) {
}
if (mFocusCallbacks != null) {
final int N = mFocusCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
IWindowFocusObserver obs = mFocusCallbacks.getBroadcastItem(i);
try {
if (focused) {
obs.focusGained(mWindowId.asBinder());
} else {
obs.focusLost(mWindowId.asBinder());
}
} catch (RemoteException e) {
}
}
mFocusCallbacks.finishBroadcast();
}
}
4.程序变更焦点,程序获得焦点变更事件
// 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);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_WINDOW_FOCUS_CHANGED: {
if (mAdded) {
boolean hasWindowFocus = msg.arg1 != 0;
mAttachInfo.mHasWindowFocus = hasWindowFocus;
profileRendering(hasWindowFocus);
if (hasWindowFocus) {
...
}
mLastWasImTarget = WindowManager.LayoutParams
.mayUseInputMethod(mWindowAttributes.flags);
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
imm.onPreWindowFocus(mView, hasWindowFocus);
}
if (mView != null) {
mAttachInfo.mKeyDispatchState.reset();
// 6.1 调用根 view的 dispatchWindowFocusChanged(),通知view程序获得焦点
mView.dispatchWindowFocusChanged(hasWindowFocus);
mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
}
// 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()) {
// 6.2 通知 InputMethodManager 该 window 获得焦点
imm.onPostWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
// Clear the forward bit. We can just do this directly, since
// the window manager doesn't care about it.
mWindowAttributes.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
((WindowManager.LayoutParams)mView.getLayoutParams())
.softInputMode &=
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
mHasHadWindowFocus = true;
}
}
} break;
...
}
}
一.1 焦点View向IMMS请求绑定输入法
6.1 之后的流程
// ViewGroup.java
@Override
public void dispatchWindowFocusChanged(boolean hasFocus) {
super.dispatchWindowFocusChanged(hasFocus);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].dispatchWindowFocusChanged(hasFocus);
}
}
// View.java
/**
* Called when the window containing this view gains or loses window focus.
* ViewGroups should override to route to their children.
*
* @param hasFocus True if the window containing this view now has focus,
* false otherwise.
*/
public void dispatchWindowFocusChanged(boolean hasFocus) {
onWindowFocusChanged(hasFocus);
}
/**
* Called when the window containing this view gains or loses focus. Note
* that this is separate from view focus: to receive key events, both
* your view and its window must have focus. If a window is displayed
* on top of yours that takes input focus, then your own window will lose
* focus but the view focus will remain unchanged.
*
* @param hasWindowFocus True if the window containing this view now has
* focus, false otherwise.
*/
public void onWindowFocusChanged(boolean hasWindowFocus) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (!hasWindowFocus) {
if (isPressed()) {
setPressed(false);
}
if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
imm.focusOut(this);
}
removeLongPressCallback();
removeTapCallback();
onFocusLost();
} else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
// 获得焦点的 view 通过 InputMethodManager 向 Service 通知自己获得焦点
imm.focusIn(this);
}
refreshDrawableState();
}
// InputMethodManager.java
/**
* Call this when a view receives focus.
* @hide
*/
public void focusIn(View view) {
synchronized (mH) {
focusInLocked(view);
}
}
// InputMethodManager.java
/**
* Call this when a view receives focus.
* @hide
*/
public void focusIn(View view) {
synchronized (mH) {
focusInLocked(view);
}
}
void focusInLocked(View view) {
if (DEBUG) Log.v(TAG, "focusIn: " + dumpViewInfo(view));
if (view != null && view.isTemporarilyDetached()) {
// This is a request from a view that is temporarily detached from a window.
if (DEBUG) Log.v(TAG, "Temporarily detached view, ignoring");