1 Android 输入法框架源码分析总结( 二 )


4.程序变更焦点,获得焦点变更事件

1  Android 输入法框架源码分析总结

文章插图
// ViewRootImpl.javapublic 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);}//程序获得焦点会通过调用mView.dispatchWindowFocusChanged和//imm.onPostWindowFocus来通知IMMS焦点信息发生改变,需要更新输入法了@Overridepublic 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;...}}
5 焦点View向IMMS请求绑定输入法
获得焦点的 view 通过向通知自己获得焦点
imm.(this);
6.1 之后的流程
1  Android 输入法框架源码分析总结

文章插图
// ViewGroup.java@Overridepublic 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");return;}if (mCurRootView != view.getRootView()) {// This is a request from a window that isn't in the window with// IME focus, so ignore it.if (DEBUG) Log.v(TAG, "Not IME target window, ignoring");return;}mNextServedView = view;// 保存焦点view的变量scheduleCheckFocusLocked(view);}static void scheduleCheckFocusLocked(View view) {ViewRootImpl viewRootImpl = view.getViewRootImpl();if (viewRootImpl != null) {viewRootImpl.dispatchCheckFocus();}}// ViewRootImpl.javapublic void dispatchCheckFocus() {if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {// This will result in a call to checkFocus() below.mHandler.sendEmptyMessage(MSG_CHECK_FOCUS);}}case MSG_CHECK_FOCUS: {InputMethodManager imm = InputMethodManager.peekInstance();if (imm != null) {imm.checkFocus();}} break;// InputMethodManager.java/*** @hide*/public void checkFocus() {// 确认当前 focused view 是否已经调用过 startInputInner() 来绑定输入法,// 因为前面 mView.dispatchWindowFocusChanged() 已经完成了 focused view 的绑定,// 大部分情况下,该函数返回 false , 不会再次调用startInputInner()if (checkFocusNoStartInput(false)) {startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);}}private boolean checkFocusNoStartInput(boolean forceNewFocus) {// This is called a lot, so short-circuit before locking.if (mServedView == mNextServedView && !forceNewFocus) {return false;}final ControlledInputConnectionWrapper ic;synchronized (mH) {if (mServedView == mNextServedView && !forceNewFocus) {return false;}if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView+ " next=" + mNextServedView+ " forceNewFocus=" + forceNewFocus+ " package="+ (mServedView != null ? mServedView.getContext().getPackageName() : ""));if (mNextServedView == null) {finishInputLocked();// In this case, we used to have a focused view on the window,// but no longer do.We should make sure the input method is// no longer shown, since it serves no purpose.closeCurrentInput();return false;}ic = mServedInputConnectionWrapper;mServedView = mNextServedView;mCurrentTextBoxAttribute = null;mCompletions = null;mServedConnecting = true;}if (ic != null) {ic.finishComposingText();}return true;}boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,IBinder windowGainingFocus, int controlFlags, int softInputMode,int windowFlags) {final View view;synchronized (mH) {// 获得上面的焦点viewview = mServedView;// Make sure we have a window token for the served view.if (DEBUG) {Log.v(TAG, "Starting input: view=" + dumpViewInfo(view) +" reason=" + InputMethodClient.getStartInputReason(startInputReason));}if (view == null) {if (DEBUG) Log.v(TAG, "ABORT input: no served view!");return false;}}// Now we need to get an input connection from the served view.// This is complicated in a couple ways: we can't be holding our lock// when calling out to the view, and we need to make sure we call into// the view on the same thread that is driving its view hierarchy.Handler vh = view.getHandler();if (vh == null) {// If the view doesn't have a handler, something has changed out// from under us, so just close the current input.// If we don't close the current input, the current input method can remain on the// screen without a connection.if (DEBUG) Log.v(TAG, "ABORT input: no handler for view! Close current input.");closeCurrentInput();return false;}if (vh.getLooper() != Looper.myLooper()) {// The view is running on a different thread than our own, so// we need to reschedule our work for over there.if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");vh.post(new Runnable() {@Overridepublic void run() {startInputInner(startInputReason, null, 0, 0, 0);}});return false;}// Okay we are now ready to call into the served view and have it// do its stuff.// Life is good: let's hook everything up!EditorInfo tba = new EditorInfo();// Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the// system can verify the consistency between the uid of this process and package name passed// from here. See comment of Context#getOpPackageName() for details.tba.packageName = view.getContext().getOpPackageName();tba.fieldId = view.getId();// 创建数据通信连接接口 InputConnection,这个会传送到InputMethodService// InputMethodService 后面就是通过这个connection将输入法的字符传给该viewInputConnection ic = view.onCreateInputConnection(tba);if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);synchronized (mH) {// Now that we are locked again, validate that our state hasn't// changed.if (mServedView != view || !mServedConnecting) {// Something else happened, so abort.if (DEBUG) Log.v(TAG,"Starting input: finished by someone else. view=" + dumpViewInfo(view)+ " mServedView=" + dumpViewInfo(mServedView)+ " mServedConnecting=" + mServedConnecting);return false;}// If we already have a text box, then this view is already// connected so we want to restart it.if (mCurrentTextBoxAttribute == null) {controlFlags |= CONTROL_START_INITIAL;}// Hook 'em up and let 'er rip.mCurrentTextBoxAttribute = tba;mServedConnecting = false;if (mServedInputConnectionWrapper != null) {mServedInputConnectionWrapper.deactivate();mServedInputConnectionWrapper = null;}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 {if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="+ ic + " tba=" + tba + " controlFlags=#"+ Integer.toHexString(controlFlags));final InputBindResult res = mService.startInputOrWindowGainedFocus(startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,windowFlags, tba, servedContext, missingMethodFlags);if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);if (res != null) {if (res.id != null) {setInputChannelLocked(res.channel);mBindSequence = res.sequence;// 获得输入法的通信接口mCurMethod = res.method;mCurId = res.id;mNextUserActionNotificationSequenceNumber =res.userActionNotificationSequenceNumber;if (mServedInputConnectionWrapper != null) {mServedInputConnectionWrapper.setInputMethodId(mCurId);}} else {if (res.channel != null && res.channel != mCurChannel) {res.channel.dispose();}if (mCurMethod == null) {// This means there is no input method available.if (DEBUG) Log.v(TAG, "ABORT input: no input method!");return true;}}} else {if (startInputReason== InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN) {// We are here probably because of an obsolete window-focus-in message sent// to windowGainingFocus.Since IMMS determines whether a Window can have// IME focus or not by using the latest window focus state maintained in the// WMS, this kind of race condition cannot be avoided.One obvious example// would be that we have already received a window-focus-out message but the// UI thread is still handling previous window-focus-in message here.// TODO: InputBindResult should have the error code.if (DEBUG) Log.w(TAG, "startInputOrWindowGainedFocus failed. "+ "Window focus may have already been lost. "+ "win=" + windowGainingFocus + " view=" + dumpViewInfo(view));if (!mActive) {// mHasBeenInactive is a latch switch to forcefully refresh IME focus// state when an inactive (mActive == false) client is gaining window// focus. In case we have unnecessary disable the latch due to this// spurious wakeup, we re-enable the latch here.// TODO: Come up with more robust solution.mHasBeenInactive = true;}}}if (mCurMethod != null && mCompletions != null) {try {mCurMethod.displayCompletions(mCompletions);} catch (RemoteException e) {}}} catch (RemoteException e) {Log.w(TAG, "IME died: " + mCurId, e);}}return true;}// InputMethodManagerService.java@Overridepublic InputBindResult startInputOrWindowGainedFocus(/* @InputMethodClient.StartInputReason */ final int startInputReason,IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,/* @InputConnectionInspector.missingMethods */ final int missingMethods) {if (windowToken != null) {// focusIn 不走该分支return windowGainedFocus(startInputReason, client, windowToken, controlFlags,softInputMode, windowFlags, attribute, inputContext, missingMethods);} else {//通知InputMethodManagerService,该程序的view获得焦点,IMMS将这个 view 和 输入法绑定return startInput(startInputReason, client, inputContext, missingMethods, attribute,controlFlags);}}