android 【点击输入框调出输入法前的】输入框获取焦点和输入法的初始化分析
进入一个界面,通过一个buttuon点击打开一个输入框,比如无线网络设置蓝牙名称编辑框,这个时候编辑框会默认有焦点,就会默认调用焦点变化的函数TextViewon FocusChanged方法如下:
@Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { Log.d(TAG, "TextView class onFocusChanged method"); if (mTemporaryDetach) { // If we are temporarily in the detach state, then do nothing. super.onFocusChanged(focused, direction, previouslyFocusedRect); return; }
进入super.onFocusChanged方法,如下:
/** * Called by the view system when the focus state of this view changes. * When the focus change event is caused by directional navigation, direction * and previouslyFocusedRect provide insight into where the focus is coming from. * When overriding, be sure to call up through to the super class so that * the standard focus handling will occur. * * @param gainFocus True if the View has focus; false otherwise. * @param direction The direction focus has moved when requestFocus() * is called to give this view focus. Values are * {@link #FOCUS_UP}, {@link #FOCUS_DOWN}, {@link #FOCUS_LEFT}, * {@link #FOCUS_RIGHT}, {@link #FOCUS_FORWARD}, or {@link #FOCUS_BACKWARD}. * It may not always apply, in which case use the default. * @param previouslyFocusedRect The rectangle, in this view's coordinate * system, of the previously focused view. If applicable, this will be * passed in as finer grained information about where the focus is coming * from (in addition to direction). Will be null
otherwise. */ protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { if (gainFocus) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); } InputMethodManager imm = InputMethodManager.peekInstance(); if (!gainFocus) { if (isPressed()) { setPressed(false); } if (imm != null && mAttachInfo != null && mAttachInfo.mHasWindowFocus) { Log.d(TAG, "View class onFocusChanged method InputMethodManager focusOut"); imm.focusOut(this); } onFocusLost(); } else if (imm != null && mAttachInfo != null && mAttachInfo.mHasWindowFocus) { Log.d(TAG, "View class onFocusChanged method InputMethodManager focusIn"); imm.focusIn(this); } invalidate(true); ListenerInfo li = mListenerInfo; if (li != null && li.mOnFocusChangeListener != null) { li.mOnFocusChangeListener.onFocusChange(this, gainFocus); } if (mAttachInfo != null) { mAttachInfo.mKeyDispatchState.reset(this); } }
日志:View class onFocusChanged method InputMethodManager focusIn,从日志来看是调用了imm.focusIn(this);方法,进入该方法,如下: /** * 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,"InputMethodManager class " + "focusIn: " + view); 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,"InputMethodManager class " + "Not IME target window, ignoring"); return; } mNextServedView = view; scheduleCheckFocusLocked(view); }
输出日志:InputMethodManager class focusIn: com.pateo.as.settings.view.AsEditText@418c5850 void scheduleCheckFocusLocked(View view) { Handler vh = view.getHandler(); if (vh != null && !vh.hasMessages(ViewRootImpl.CHECK_FOCUS)) { // This will result in a call to checkFocus() below. vh.sendMessage(vh.obtainMessage(ViewRootImpl.CHECK_FOCUS)); } }
我们来看这个CHECK_FOCUS的处理,在相应的类ViewRootImpl中
case CHECK_FOCUS: { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { imm.checkFocus(); } } break;
在这里我们看到它继续调用了checkFocus,我们进入该方法
public void checkFocus() { if (checkFocusNoStartInput(false)) { Log.d(TAG, "InputMethodManager class checkFocus method"); startInputInner(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; } InputConnection ic = null; synchronized (mH) { if (mServedView == mNextServedView && !forceNewFocus) { return false; } if (DEBUG) Log.v(TAG,"InputMethodManager class " + "checkFocus: view=" + mServedView + " next=" + mNextServedView + " forceNewFocus=" + forceNewFocus); 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 = mServedInputConnection; mServedView = mNextServedView; mCurrentTextBoxAttribute = null; mCompletions = null; mServedConnecting = true; } if (ic != null) { ic.finishComposingText(); } return true;
我们先在此停顿下,来梳理下上面的方面里面的一些内容,先看日志:
01-01 09:39:14.160 V/PateoInputMethod( 1806): InputMethodManager class checkFocus: view=com.android.internal.policy.impl.PhoneWindow$DecorView@41902530 next=com.pateo.as.settings.view.AsEditText@418c5850 forceNewFocus=false
01-01 09:39:14.160 V/PateoInputMethod( 1806): mServedInputConnection=null
从日志来看InputConnection类型的mServedInputConnection还没有被赋值,重要的是mNextServedView这个不为null是com.pateo.as.settings.view.AsEditText,所以这个时候不用去关闭输入法,看到流程最后返回true,当为true被返回后,执行上面的startInputInner方法,进入该方法
boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode, int windowFlags) { final View view; synchronized (mH) { view = mServedView; // Make sure we have a window token for the served view. if (DEBUG) Log.v(TAG,"InputMethodManager class " + "Starting input: view=" + view); if (view == null) { if (DEBUG) Log.v(TAG,"InputMethodManager class " + "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 bail. if (DEBUG) Log.v(TAG,"InputMethodManager class " + "ABORT input: no handler for view!"); 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,"InputMethodManager class " + "Starting input: reschedule to view thread"); vh.post(new Runnable() { public void run() { Log.d(TAG, "InputMethodManager class startInputInner method"); startInputInner(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(); tba.packageName = view.getContext().getPackageName(); tba.fieldId = view.getId(); InputConnection ic = view.onCreateInputConnection(tba); if (DEBUG) Log.v(TAG,"InputMethodManager class " + "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,"InputMethodManager class " + "Starting input: finished by someone else (view=" + mServedView + " conn=" + 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; mServedInputConnection = ic; IInputContext servedContext; if (ic != null) { mCursorSelStart = tba.initialSelStart; mCursorSelEnd = tba.initialSelEnd; mCursorCandStart = -1; mCursorCandEnd = -1; mCursorRect.setEmpty(); servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this); } else { servedContext = null; } try { if (DEBUG) Log.v(TAG,"InputMethodManager class " + "START INPUT: " + view + " ic=" + ic + " tba=" + tba + " controlFlags=#" + Integer.toHexString(controlFlags)); InputBindResult res; if (windowGainingFocus != null) { res = mService.windowGainedFocus(mClient, windowGainingFocus, controlFlags, softInputMode, windowFlags, tba, servedContext); } else { res = mService.startInput(mClient, servedContext, tba, controlFlags); } if (DEBUG) Log.v(TAG,"InputMethodManager class " + "Starting input: Bind result=" + res); if (res != null) { if (res.id != null) { mBindSequence = res.sequence; mCurMethod = res.method; } else if (mCurMethod == null) { // This means there is no input method available. if (DEBUG) Log.v(TAG,"InputMethodManager class " + "ABORT input: no input method!"); return true; } } if (mCurMethod != null && mCompletions != null) { try { mCurMethod.displayCompletions(mCompletions); } catch (RemoteException e) { } } } catch (RemoteException e) { Log.w(TAG,"InputMethodManager class " + "IME died: " + mCurId, e); } } return true; }
看流程日志:InputMethodManager class Starting input: view=com.pateo.as.settings.view.AsEditText@418c5850
紧接着看到如下两行日志:
01-01 09:39:14.160 D/PateoInputMethod( 1806): TextView class onCreateInputConnection method start
01-01 09:39:14.160 D/PateoInputMethod( 1806): TextView class onCreateInputConnection return EditableInputConnection
好吧,从日志来看,是执行了如下方法:
InputConnection ic = view.onCreateInputConnection(tba);
此处的view我们已经从上面看出是AsEditText,public class AsEditText extends EditText,它是个EditText,最终回到了EditText的父类TextView,进入到TextView类的onCreateInputConnection方法中 @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { Log.d(TAG, "TextView class onCreateInputConnection method start"); if (onCheckIsTextEditor() && isEnabled()) { if (mInputMethodState == null) { mInputMethodState = new InputMethodState(); } outAttrs.inputType = mInputType; if (mInputContentType != null) { outAttrs.imeOptions = mInputContentType.imeOptions; outAttrs.privateImeOptions = mInputContentType.privateImeOptions; outAttrs.actionLabel = mInputContentType.imeActionLabel; outAttrs.actionId = mInputContentType.imeActionId; outAttrs.extras = mInputContentType.extras; } else { outAttrs.imeOptions = EditorInfo.IME_NULL; }// if (focusSearch(FOCUS_DOWN) != null) {// outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;// }// if (focusSearch(FOCUS_UP) != null) {// outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;// }// if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)// == EditorInfo.IME_ACTION_UNSPECIFIED) {// if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {// // An action has not been set, but the enter key will move to// // the next focus, so set the action to that.// outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;// } else {// // An action has not been set, and there is no focus to move// // to, so let's just supply a "done" action.// outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;// }// if (!shouldAdvanceFocusOnEnter()) {// outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;// }// }// if (isMultilineInputType(outAttrs.inputType)) {// // Multi-line text editors should always show an enter key.// outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;// } outAttrs.hintText = mHint; if (mText instanceof Editable) { Log.d(TAG, "TextView class onCreateInputConnection return EditableInputConnection"); InputConnection ic = new EditableInputConnection(this); outAttrs.initialSelStart = getSelectionStart(); outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType); return ic; } } return null; }
上面代码最重要的是InputConnection ic = new EditableInputConnection(this); return ic;这样就返回了一个InputConnection的子类实现对象EditableInputConnection,而这个EditableInputConnection有个很重要的方法commitText,暂时性记忆这点
跟着我们进行看输出的日志:
01-01 09:39:14.160 V/PateoInputMethod( 1806): InputMethodManager class Starting input: tba=android.view.inputmethod.EditorInfo@41bc3c28 ic=com.android.internal.widget.EditableInputConnection@41bc3f00
01-01 09:39:14.160 V/PateoInputMethod( 1806): InputMethodManager class START INPUT: com.pateo.as.settings.view.AsEditText@418c5850 ic=com.android.internal.widget.EditableInputConnection@41bc3f00 tba=android.view.inputmethod.EditorInfo@41bc3c28 controlFlags=#100
因为windowGainingFocus是传过来的null,所以调用了如下代码:
res = mService.startInput(mClient, servedContext, tba, controlFlags);
这个时候去启动输入法,这里的mService是InputMethodManagerService,进入该方法:
@Override public InputBindResult startInput(IInputMethodClient client, IInputContext inputContext, EditorInfo attribute, int controlFlags) { synchronized (mMethodMap) { final long ident = Binder.clearCallingIdentity(); try { return startInputLocked(client, inputContext, attribute, controlFlags); } finally { Binder.restoreCallingIdentity(ident); } } }
InputBindResult startInputLocked(IInputMethodClient client, IInputContext inputContext, EditorInfo attribute, int controlFlags) { // If no method is currently selected, do nothing. if (mCurMethodId == null) { return mNoBinding; } ClientState cs = mClients.get(client.asBinder()); if (cs == null) { throw new IllegalArgumentException("unknown client " + client.asBinder()); } try { // boolean isInputMethodClientHasFocus = mIWindowManager.inputMethodClientHasFocus(cs.client);// // android.util.Log.d(TAG, "InputMethodManagerService class" + "remove judge focus return null when focos false, isInputMethodClientHasFocus=" + isInputMethodClientHasFocus); if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) { // Check with the window manager to make sure this client actually // has a window with focus. If not, reject. This is thread safe // because if the focus changes some time before or after, the // next client receiving focus that has any interest in input will // be calling through here after that change happens. Slog.w(TAG, "InputMethodManagerService class" + "Starting input on non-focused client " + cs.client + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); return null; } } catch (RemoteException e) { } return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags); }
InputBindResult startInputUncheckedLocked(ClientState cs, IInputContext inputContext, EditorInfo attribute, int controlFlags) { // If no method is currently selected, do nothing. if (mCurMethodId == null) { return mNoBinding; } if (mCurClient != cs) { // If the client is changing, we need to switch over to the new // one. unbindCurrentClientLocked(); if (DEBUG) Slog.v(TAG, "InputMethodManagerService class" + "switching to client: client = " + cs.client.asBinder()); // If the screen is on, inform the new client it is active if (mScreenOn) { try { cs.client.setActive(mScreenOn); } catch (RemoteException e) { Slog.w(TAG, "InputMethodManagerService class" + "Got RemoteException sending setActive notification to pid " + cs.pid + " uid " + cs.uid); } } } // Bump up the sequence for this client and attach it. mCurSeq++; if (mCurSeq <= 0) mCurSeq = 1; mCurClient = cs; mCurInputContext = inputContext; mCurAttribute = attribute; // Check if the input method is changing. if (mCurId != null && mCurId.equals(mCurMethodId)) { if (cs.curSession != null) { // Fast case: if we are already connected to the input method, // then just return it. return attachNewInputLocked( (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0); }
看到最后的return attachNewInputLocked方法
InputBindResult attachNewInputLocked(boolean initial) { if (!mBoundToMethod) { executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( MSG_BIND_INPUT, mCurMethod, mCurClient.binding)); mBoundToMethod = true; } final SessionState session = mCurClient.curSession; if (initial) { executeOrSendMessage(session.method, mCaller.obtainMessageOOO( MSG_START_INPUT, session, mCurInputContext, mCurAttribute)); } else { executeOrSendMessage(session.method, mCaller.obtainMessageOOO( MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute)); } if (mShowRequested) { if (DEBUG) Slog.v(TAG, "InputMethodManagerService class" + "Attach new input asks to show input"); showCurrentInputLocked(getAppShowFlags(), null); } return new InputBindResult(session.session, mCurId, mCurSeq); }
上面的代码跟踪,做后进入到了MSG_START_INPUT这个方法中,我们进行跟进
case MSG_START_INPUT: args = (HandlerCaller.SomeArgs)msg.obj; try { SessionState session = (SessionState)args.arg1; setEnabledSessionInMainThread(session); session.method.startInput((IInputContext)args.arg2, (EditorInfo)args.arg3); } catch (RemoteException e) { } return true;
最终进入到了InputMethodService类的startInput方法中 public void startInput(InputConnection ic, EditorInfo attribute) { if (DEBUG) Log.v(TAG,"InputMethodService class" + "startInput(): editor=" + attribute); doStartInput(ic, attribute, false); }
void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) { if (!restarting) { doFinishInput(); } mInputStarted = true; mStartedInputConnection = ic; mInputEditorInfo = attribute; initialize(); if (DEBUG) Log.v(TAG,"InputMethodService class" + "CALL: onStartInput"); onStartInput(attribute, restarting); if (mWindowVisible) { if (mShowInputRequested) { if (DEBUG) Log.v(TAG,"InputMethodService class" + "CALL: onStartInputView"); mInputViewStarted = true; onStartInputView(mInputEditorInfo, restarting); startExtractingText(true); } else if (mCandidatesVisibility == View.VISIBLE) { if (DEBUG) Log.v(TAG,"InputMethodService class" + "CALL: onStartCandidatesView"); mCandidatesViewStarted = true; onStartCandidatesView(mInputEditorInfo, restarting); } } }
看下相应的日志:
01-01 09:39:14.160 V/PateoInputMethod( 1461): InputMethodService classstartInput(): editor=android.view.inputmethod.EditorInfo@418f80b0
01-01 09:39:14.160 V/PateoInputMethod( 1461): InputMethodService classCALL: onFinishInput
进入了doFinishInput方法
void doFinishInput() { if (mInputViewStarted) { if (DEBUG) Log.v(TAG,"InputMethodService class" + "CALL: onFinishInputView"); onFinishInputView(true); } else if (mCandidatesViewStarted) { if (DEBUG) Log.v(TAG,"InputMethodService class" + "CALL: onFinishCandidatesView"); onFinishCandidatesView(true); } mInputViewStarted = false; mCandidatesViewStarted = false; if (mInputStarted) { if (DEBUG) Log.v(TAG,"InputMethodService class" + "CALL: onFinishInput"); onFinishInput(); } mInputStarted = false; mStartedInputConnection = null; mCurCompletions = null; }
到这里结束了,需要进一步回头看一些参数,下面再分析
更多相关文章
- Android(安卓)DataBinding使用详解(一)
- 使用 SQLiteDatabase 操作 SQLite 数据库
- Android菜鸟笔记-调用相机拍照后返回照片过小的问题
- Android(安卓)studio 中与本地 html 页面交互
- ContentProvider中gettype() 和MIME类型的理解
- Android(安卓)开发者必知必会的权限管理知识
- 真机上使用Hierarchy Viewer
- Android(安卓)studio 命令gradlew assembleRelease打包时,出现 Un
- android类作用整理