进入一个界面,通过一个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;
}
/**
* 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));
}
}
case CHECK_FOCUS: {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
imm.checkFocus();
}
} break;
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;
}
紧接着看到如下两行日志:
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;
}
跟着我们进行看输出的日志:
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);
@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);
}
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);
}
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;
}