基于Android 9.x

目录

1 viewClicked流程

1.1 viewClicked

1.2 checkFocus

1.3 startInputInner

1.4 startInputOrWindowGainedFocus

1.5 startInputLocked

1.6 startInputUncheckedLocked

1.7 attachNewInputLocked

1.7.1 处理返回的结果

2 showSoftInput流程

2.1 showSoftInput

2.2 IMMS#showSoftInput

2.3 showCurrentInputLocked

2.4 IMS$InputMethodImpl$showSoftInput

2.5 dispatchOnShowInputRequested

2.6 IMS$showWindow

2.7 showWindowInner

输入法弹出流程

输入法#拉起流程#输入框弹出流程.png

viewClicked流程

viewClicked

IMM.JAVA

public void viewClicked(View view) {attachNewInputLocked

//服务的view是否相同,当两次点击在同一个输入框时,两者相同;否则不同

//我们首次点击某个输入框为例

final boolean focusChanged = mServedView != mNextServedView;

checkFocus();

synchronized (mH) {

if ((mServedView != view && (mServedView == null

|| !mServedView.checkInputConnectionProxy(view)))

|| mCurrentTextBoxAttribute == null || mCurMethod == null) {

return;

}

try {

if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);

mCurMethod.viewClicked(focusChanged);

} catch (RemoteException e) {

Log.w(TAG, "IME died: " + mCurId, e);

}

}

}

checkFocus

public void checkFocus() {

//检查该view是否已经执行过startInputInner

//主要是比较最后一个mServedView和当前mNextServedView是否相同

//该方法逻辑已经在"输入法窗口和应用窗口绑定."讲过,本章节不在介绍

if (checkFocusNoStartInput(false)) {

startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);

}

}

startInputInner

携带的参数:

-startInputReason:START_INPUT_REASON_CHECK_FOCUS,标明本次调用的目的

-windowGainingFocus:会影响IMMS中startInputOrWindowGainedFocus的调用逻辑

-controlFlags,softInputMode,windowFlags = 0

boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,

IBinder windowGainingFocus, int controlFlags, int softInputMode,

int windowFlags) {

final View view;

synchronized (mH) {

//mServedView已经在checkFocusNoStartInput中赋值为mNextServedView,因此这里为当前要获取输入法焦点的view

view = 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;

}

//判断view是否在UI主线程

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(() -> 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();

//通过TextView的onCreateInputConnection方法初始化,EditorInfo,主要是获取inputType和imeOptions

//ic是一个EditableInputConnection,用来跟IMS通信

InputConnection 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.

//正常情况下不会出现mServedView != view,多线程操作会。

//mServedConnecting在checkFocusNoStartInput过程中,被置位true,为false表示startInputInner被多次执行

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;

//置位false,与checkFocusNoStartInput同步该参数状态

mServedConnecting = false;

if (mServedInputConnectionWrapper != null) {

mServedInputConnectionWrapper.deactivate();

mServedInputConnectionWrapper = null;

}

ControlledInputConnectionWrapper servedContext;

final int missingMethodFlags;

//对于EditorText,不为null

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();

}

//创建ControlledInputConnectionWrapper

servedContext = new ControlledInputConnectionWrapper(

icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);

} else {

servedContext = null;

missingMethodFlags = 0;

}

//The InputConnection that was last retrieved from the served view.

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,

view.getContext().getApplicationInfo().targetSdkVersion);

if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);

if (res == null) {

Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"

+ " null. startInputReason="

+ InputMethodClient.getStartInputReason(startInputReason)

+ " editorInfo=" + tba

+ " controlFlags=#" + Integer.toHexString(controlFlags));

return false;

}

if (res.id != null) {

setInputChannelLocked(res.channel);

mBindSequence = res.sequence;

mCurMethod = res.method;

mCurId = res.id;

mNextUserActionNotificationSequenceNumber =

res.userActionNotificationSequenceNumber;

} else if (res.channel != null && res.channel != mCurChannel) {

res.channel.dispose();

}

switch (res.result) {

case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:

mRestartOnNextWindowFocus = true;

break;

}

if (mCurMethod != null && mCompletions != null) {

try {

mCurMethod.displayCompletions(mCompletions);

} catch (RemoteException e) {

}

}

} catch (RemoteException e) {

Log.w(TAG, "IME died: " + mCurId, e);

}

}

return true;

}

startInputOrWindowGainedFocus###

@NonNull

@Override

public 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,

int unverifiedTargetSdkVersion) {

final InputBindResult result;

if (windowToken != null) {

result = windowGainedFocus(startInputReason, client, windowToken, controlFlags,

softInputMode, windowFlags, attribute, inputContext, missingMethods,

unverifiedTargetSdkVersion);

} else {

result = startInput(startInputReason, client, inputContext, missingMethods, attribute,

controlFlags);

}

if (result == null) {

// This must never happen, but just in case.

Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="

+ InputMethodClient.getStartInputReason(startInputReason)

+ " windowFlags=#" + Integer.toHexString(windowFlags)

+ " editorInfo=" + attribute);

return InputBindResult.NULL;

}

return result;

}

该步骤,主要是同InputMethodManagerService通信,获取将要弹出的输入信息,和绑定当前焦点组件到对应的输入法上

调用时,各个参数的值为:

startInputReason = 4 = START_INPUT_REASON_CHECK_FOCUS

mClient:应用层创建的IInputMethodClient对象

windowGainingFocus = null:应用层的ViewRootImpl$W对象

softInputMode = 0

tba = EditorInfo对象

servedContext = ControlledInputConnectionWrapper{connection=com.android.internal.widget.EditableInputConnection@2a772c0 finished=false mParentInputMethodManager.mActive=true}

@NonNull

private InputBindResult startInput(

/* @InputMethodClient.StartInputReason */ final int startInputReason,

IInputMethodClient client, IInputContext inputContext,

/* @InputConnectionInspector.missingMethods */ final int missingMethods,

@Nullable EditorInfo attribute, int controlFlags) {

//用户合法性验证

if (!calledFromValidUser()) {

return InputBindResult.INVALID_USER;

}

synchronized (mMethodMap) {

final long ident = Binder.clearCallingIdentity();

try {

//直接调用startInputLocked方法

return startInputLocked(startInputReason, client, inputContext, missingMethods,

attribute, controlFlags);

} finally {

Binder.restoreCallingIdentity(ident);

}

}

}

startInputLocked

@GuardedBy("mMethodMap")

@NonNull

InputBindResult startInputLocked(

/* @InputMethodClient.StartInputReason */ final int startInputReason,

IInputMethodClient client, IInputContext inputContext,

/* @InputConnectionInspector.missingMethods */ final int missingMethods,

@Nullable EditorInfo attribute, int controlFlags) {

// If no method is currently selected, do nothing.

//当前默认输入法对应的id

if (mCurMethodId == null) {

return InputBindResult.NO_IME;

}

//当前应用客户单对应的ClientState对象

ClientState cs = mClients.get(client.asBinder());

if (cs == null) {

throw new IllegalArgumentException("unknown client "

+ client.asBinder());

}

if (attribute == null) {

Slog.w(TAG, "Ignoring startInput with null EditorInfo."

+ " uid=" + cs.uid + " pid=" + cs.pid);

return InputBindResult.NULL_EDITOR_INFO;

}

try {

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.

if (DEBUG) {

Slog.w(TAG, "Starting input on non-focused client " + cs.client

+ " (uid=" + cs.uid + " pid=" + cs.pid + ")");

}

return InputBindResult.NOT_IME_TARGET_WINDOW;

}

} catch (RemoteException e) {

}

return startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,

controlFlags, startInputReason);

}

startInputUncheckedLocked

@GuardedBy("mMethodMap")

@NonNull

InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,

/* @InputConnectionInspector.missingMethods */ final int missingMethods,

@NonNull EditorInfo attribute, int controlFlags,

/* @InputMethodClient.StartInputReason */ final int startInputReason) {

// If no method is currently selected, do nothing.

if (mCurMethodId == null) {

return InputBindResult.NO_IME;

}

//校验包名和uid的一致性

if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,

attribute.packageName)) {

Slog.e(TAG, "Rejecting this client as it reported an invalid package name."

+ " uid=" + cs.uid + " package=" + attribute.packageName);

return InputBindResult.INVALID_PACKAGE_NAME;

}

//查看启动输入法过程,应用客户端是否发生变化

if (mCurClient != cs) {

// Was the keyguard locked when switching over to the new client?

mCurClientInKeyguard = isKeyguardLocked();

// If the client is changing, we need to switch over to the new

// one.

unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);

if (DEBUG) Slog.v(TAG, "switching to client: client="

+ cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);

// If the screen is on, inform the new client it is active

if (mIsInteractive) {

executeOrSendMessage(cs.client, mCaller.obtainMessageIO(

MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, cs));

}

}

// Bump up the sequence for this client and attach it.

//记录输入法拉起次数

mCurSeq++;

if (mCurSeq <= 0) mCurSeq = 1;

mCurClient = cs;

mCurInputContext = inputContext;//应用客户端传递的serverContext

mCurInputContextMissingMethods = missingMethods;

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(startInputReason,

(controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);

}

if (mHaveConnection) {

if (mCurMethod != null) {

// Return to client, and we will get back with it when

// we have had a session made for it.

requestClientSessionLocked(cs);

return new InputBindResult(

InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,

null, null, mCurId, mCurSeq,

mCurUserActionNotificationSequenceNumber);

} else if (SystemClock.uptimeMillis()

< (mLastBindTime+TIME_TO_RECONNECT)) {

// In this case we have connected to the service, but

// don't yet have its interface. If it hasn't been too

// long since we did the connection, we'll return to

// the client and wait to get the service interface so

// we can report back. If it has been too long, we want

// to fall through so we can try a disconnect/reconnect

// to see if we can get back in touch with the service.

return new InputBindResult(

InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,

null, null, mCurId, mCurSeq,

mCurUserActionNotificationSequenceNumber);

} else {

EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,

mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);

}

}

}

return startInputInnerLocked();

}

attachNewInputLocked

@GuardedBy("mMethodMap")

@NonNull

InputBindResult attachNewInputLocked(

/* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {

//Have we called mCurMethod.bindInput()?,此处,我们已经调用过一次,因此不会再执行

if (!mBoundToMethod) {

executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(

MSG_BIND_INPUT, mCurMethod, mCurClient.binding));

mBoundToMethod = true;

}

//StartInputInfo对象的key值

final Binder startInputToken = new Binder();

//维护一个状态快照,避免随后调用MSG_START_INPUT,避免接下来的状态发生变化,影响

final StartInputInfo info = new StartInputInfo(mCurToken, mCurId, startInputReason,

!initial, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,

mCurSeq);

mStartInputMap.put(startInputToken, info);

mStartInputHistory.addEntry(info);

final SessionState session = mCurClient.curSession;

//去回调IMS的startInput->doStartInput

executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(

MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,

startInputToken, session, mCurInputContext, mCurAttribute));

//false,改过程不会显示输入法

if (mShowRequested) {

if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");

showCurrentInputLocked(getAppShowFlags(), null);

}

//成功返回InputBindResult结果

return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,

session.session, (session.channel != null ? session.channel.dup() : null),

mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);

}

IMS.InputMethodImpl.startInput

@MainThread

@Override

public void startInput(InputConnection ic, EditorInfo attribute) {

if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);

doStartInput(ic, attribute, false);

}

IMS.InputMethodImpl.doStartInput

void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {

if (!restarting) {

doFinishInput();

}

mInputStarted = true;//代表该IMS的startInput已经执行

mStartedInputConnection = ic;//应用客户端connenction

mInputEditorInfo = attribute;//应用顾客端EditorInfo

initialize();

if (DEBUG) Log.v(TAG, "CALL: onStartInput");

onStartInput(attribute, restarting);//子类实现

if (mWindowVisible) {//当前IMS窗口不可见

if (mShowInputRequested) {

if (DEBUG) Log.v(TAG, "CALL: onStartInputView");

mInputViewStarted = true;

onStartInputView(mInputEditorInfo, restarting);

startExtractingText(true);

} else if (mCandidatesVisibility == View.VISIBLE) {

if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");

mCandidatesViewStarted = true;

onStartCandidatesView(mInputEditorInfo, restarting);

}

}

}

处理返回的结果

if (res == null) {

Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"

+ " null. startInputReason="

+ InputMethodClient.getStartInputReason(startInputReason)

+ " editorInfo=" + tba

+ " controlFlags=#" + Integer.toHexString(controlFlags));

return false;

}

if (res.id != null) {

setInputChannelLocked(res.channel);

mBindSequence = res.sequence;//IMMS的输入法启动计数

mCurMethod = res.method;//用来与IMS通信的对象

mCurId = res.id;//当前输入法的id

mNextUserActionNotificationSequenceNumber =

res.userActionNotificationSequenceNumber;

} else if (res.channel != null && res.channel != mCurChannel) {

res.channel.dispose();

}

switch (res.result) {

//不是输入法目标窗口

case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:

mRestartOnNextWindowFocus = true;

break;

}

if (mCurMethod != null && mCompletions != null) {

try {

//与IMS通信

mCurMethod.displayCompletions(mCompletions);

} catch (RemoteException e) {

}

}

} catch (RemoteException e) {

Log.w(TAG, "IME died: " + mCurId, e);

}

}

return true;

showSoftInput流程

TextView.java

if (touchIsFinished && (isTextEditable() || textIsSelectable)) {

// Show the IME, except when selecting in read-only text.

final InputMethodManager imm = InputMethodManager.peekInstance();

viewClicked(imm);

if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {

imm.showSoftInput(this, 0);

}

// The above condition ensures that the mEditor is not null

mEditor.onTouchUpEvent(event);

handled = true;

}

viewClicked执行完毕后,执行showSoftInput方法,去弹出输入法

showSoftInput

public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {

checkFocus();

synchronized (mH) {

if (mServedView != view && (mServedView == null

|| !mServedView.checkInputConnectionProxy(view))) {

return false;

}

try {

return mService.showSoftInput(mClient, flags, resultReceiver);

} catch (RemoteException e) {

throw e.rethrowFromSystemServer();

}

}

}

IMMS.showSoftInput

@Override

public boolean showSoftInput(IInputMethodClient client, int flags,

ResultReceiver resultReceiver) {

if (!calledFromValidUser()) {

return false;

}

int uid = Binder.getCallingUid();

long ident = Binder.clearCallingIdentity();

try {

synchronized (mMethodMap) {

//当前客户端进程不为null

if (mCurClient == null || client == null

|| mCurClient.client.asBinder() != client.asBinder()) {

try {

// We need to check if this is the current client with

// focus in the window manager, to allow this call to

// be made before input is started in it.

if (!mIWindowManager.inputMethodClientHasFocus(client)) {

Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);

return false;

}

} catch (RemoteException e) {

return false;

}

}

if (DEBUG) Slog.v(TAG, "Client requesting input be shown");

return showCurrentInputLocked(flags, resultReceiver);

}

} finally {

Binder.restoreCallingIdentity(ident);

}

}

showCurrentInputLocked

boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {

mShowRequested = true;

if (mAccessibilityRequestingNoSoftKeyboard) {

return false;

}

//flags = 0;

if ((flags&InputMethodManager.SHOW_FORCED) != 0) {

mShowExplicitlyRequested = true;

mShowForced = true;

} else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {

//Set if we were explicitly told to show the input method

mShowExplicitlyRequested = true;

}

if (!mSystemReady) {

return false;

}

boolean res = false;

if (mCurMethod != null) {

if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);

//调用IMS$InputMethodImpl$showSoftInput方法

executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(

MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,

resultReceiver));

mInputShown = true;

//mHaveConnection:Set to true if our ServiceConnection is currently actively bound to a service (whether or not we have gotten its IBinder back yet).

//mVisibleBound = false

if (mHaveConnection && !mVisibleBound) {

bindCurrentInputMethodServiceLocked(

mCurIntent, mVisibleConnection, IME_VISIBLE_BIND_FLAGS);

mVisibleBound = true;

}

res = true;

} else if (mHaveConnection && SystemClock.uptimeMillis()

>= (mLastBindTime+TIME_TO_RECONNECT)) {

// The client has asked to have the input method shown, but

// we have been sitting here too long with a connection to the

// service and no interface received, so let's disconnect/connect

// to try to prod things along.

EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,

SystemClock.uptimeMillis()-mLastBindTime,1);

Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");

mContext.unbindService(this);

bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS);

} else {

if (DEBUG) {

Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "

+ ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));

}

}

return res;//return true

}

if ((flags&InputMethodManager.SHOW_FORCED) != 0) {

mShowExplicitlyRequested = true;

mShowForced = true;

} else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {

//Set if we were explicitly told to show the input method

mShowExplicitlyRequested = true;

}

这段代码,对传递到IMS的flag影响

private int getImeShowFlags() {

int flags = 0;

if (mShowForced) {

flags |= InputMethod.SHOW_FORCED

| InputMethod.SHOW_EXPLICIT;

} else if (mShowExplicitlyRequested) {

flags |= InputMethod.SHOW_EXPLICIT;

}

return flags;

}

实验证明,当getImeShowFlags返回为0时,会导致IMS不能正常调用showWindow方法;

也就是说mShowExplicitlyRequested和mShowExplicitlyRequested必须有其一为true;

默认情况下,TextView传递的flags为0,满足(flags&InputMethodManager.SHOW_IMPLICIT) == 0,使得mShowExplicitlyRequested = true;

保证了传递到IMS的getImeShowFlags 为1(InputMethod.SHOW_EXPLICIT)

另外感受下源码的混乱

InputMethod.SHOW_EXPLICIT = 0x00001

InputMethod.SHOW_FORCED = 0x00002

InputMethodManager.SHOW_IMPLICIT = 0x0001

InputMethodManager.SHOW_FORCED = 0x0002;

InputMethod的供IMS使用,InputMethodManager供应用客户端使用。

感觉InputMethodManager少了一个定义:InputMethodManager.SHOW_EXPLICIT = 0x00000;以为TextView调用showSoftInput的时候

传递的就是0,而且为0的情况下,正好满足(flags&InputMethodManager.SHOW_IMPLICIT) == 0,是的mShowExplicitlyRequested = true;

IMS.InputMethodImpl.showSoftInput

@MainThread

@Override

public void showSoftInput(int flags, ResultReceiver resultReceiver) {

if (DEBUG) Log.v(TAG, "showSoftInput()");

//输入法没弹出来之前,该值为false

boolean wasVis = isInputViewShown();

if (dispatchOnShowInputRequested(flags, false)) {

try {

showWindow(true);

} catch (BadTokenException e) {

// We have ignored BadTokenException here since Jelly Bean MR-2 (API Level 18).

// We could ignore BadTokenException in InputMethodService#showWindow() instead,

// but it may break assumptions for those who override #showWindow() that we can

// detect errors in #showWindow() by checking BadTokenException.

// TODO: Investigate its feasibility. Update JavaDoc of #showWindow() of

// whether it's OK to override #showWindow() or not.

}

}

clearInsetOfPreviousIme();

// If user uses hard keyboard, IME button should always be shown.

mImm.setImeWindowStatus(mToken, mStartInputToken,

mapToImeWindowStatus(isInputViewShown()), mBackDisposition);

if (resultReceiver != null) {

resultReceiver.send(wasVis != isInputViewShown()

? InputMethodManager.RESULT_SHOWN

: (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN

: InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);

}

}

dispatchOnShowInputRequested

private boolean dispatchOnShowInputRequested(int flags, boolean configChange) {

//判断是否显示输入法;正常情况返回true,去显示输入法

final boolean result = onShowInputRequested(flags, configChange);

if (result) {

//设置

mShowInputFlags = flags;//默认为1,也就是InputMethod.SHOW_EXPLICIT = 0x00001

} else {

mShowInputFlags = 0;

}

return result;

}

onShowInputRequested

判断是否应该显示输入法窗口

public boolean onShowInputRequested(int flags, boolean configChange) {

//主要判断硬键盘相关逻辑,很多输入法,会复写该方法;直接返回true

if (!onEvaluateInputViewShown()) {

return false;

}

//TextView中flag的值默认都是,也就是InputMethodManager.SHOW_EXPLICIT = 0x00000;因此,不会执行里面的相关代码

if ((flags&InputMethod.SHOW_EXPLICIT) == 0) {

//如果用户不是显式的请求显示输入法,并且应用配置没有发生变化,此时请求进入显式全屏输入法窗口,会返回false

//返回false,表示不会对现有的输入法显示与否进行改变

if (!configChange && onEvaluateFullscreenMode()) {

// Don't show if this is not explicitly requested by the user and

// the input method is fullscreen. That would be too disruptive.

// However, we skip this change for a config change, since if

// the IME is already shown we do want to go into fullscreen

// mode at this point.

return false;

}

//shouldShowImeWithHardKeyboard是否支持软键盘和硬键盘共存,默认返回false

//configure.keyboard 默认为Configuration.KEYBOARD_NOKEYS,也就是:设备没有用于文本输入的硬按键。

if (!mSettingsObserver.shouldShowImeWithHardKeyboard() &&

getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS) {

// And if the device has a hard keyboard, even if it is

// currently hidden, don't show the input method implicitly.

// These kinds of devices don't need it that much.

return false;

}

}

return true;//正常情况返回true

}

flags:有三个值,0,SHOW_EXPLICIT,SHOW_FORCED

默认为0,设置为SHOW_FORCED,IMMS会强制改成 SHOW_EXPLICIT|SHOW_FORCED

private int getImeShowFlags() {

int flags = 0;

if (mShowForced) {

flags |= InputMethod.SHOW_FORCED

| InputMethod.SHOW_EXPLICIT;

} else if (mShowExplicitlyRequested) {

flags |= InputMethod.SHOW_EXPLICIT;

}

return flags;

}

另外,改方法只能返回flags |= InputMethod.SHOW_FORCED| InputMethod.SHOW_EXPLICIT;或者flags |= InputMethod.SHOW_EXPLICIT,直接返回0,会导致输入法不显示

onEvaluateInputViewShown

该方法,没有物理键盘的时候返回true

@CallSuper

public boolean onEvaluateInputViewShown() {

//不为null

if (mSettingsObserver == null) {

Log.w(TAG, "onEvaluateInputViewShown: mSettingsObserver must not be null here.");

return false;

}

//是否支持同时显示软键盘和硬键盘

if (mSettingsObserver.shouldShowImeWithHardKeyboard()) {

return true;

}

Configuration config = getResources().getConfiguration();

//设备没有硬键盘;或者硬键盘被设置为隐藏

//HARDKEYBOARDHIDDEN_YES:value corresponding to the physical keyboard being hidden

return config.keyboard == Configuration.KEYBOARD_NOKEYS

|| config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES;

}

config:

{0theme1.0 ?mcc?mnc [zh_CN] ldltr sw360dp w360dp h683dp 320dpi nrml long port finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 0 - 0, 0) mAppBounds=Rect(0, 58 - 720, 1424) mWindowingMode=fullscreen mActivityType=undefined} s.8}

config.keyboard:

nokeys:设备没有用于文本输入的硬按键。

qwerty:设备具有标准硬键盘(无论是否对用户可见)。

12key:设备具有 12 键硬键盘(无论是否对用户可见)

该方法主要流程:

检查当前将要弹出的client和上一步绑定的mCurClient是否一致,如果不一致,返回false

调用上一次绑定的输入法的方法,弹出客户端输入发的布局

IMS.showWindow

public void showWindow(boolean showInput) {

if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput

+ " mShowInputRequested=" + mShowInputRequested

+ " mWindowAdded=" + mWindowAdded

+ " mWindowCreated=" + mWindowCreated

+ " mWindowVisible=" + mWindowVisible

+ " mInputStarted=" + mInputStarted

+ " mShowInputFlags=" + mShowInputFlags);

//避免重复进入

if (mInShowWindow) {

Log.w(TAG, "Re-entrance in to showWindow");

return;

}

try {

mWindowWasVisible = mWindowVisible;//当前窗口是否可见,false

mInShowWindow = true;//标志为,避免重复显示窗口

showWindowInner(showInput);

} catch (BadTokenException e) {

// BadTokenException is a normal consequence in certain situations, e.g., swapping IMEs

// while there is a DO_SHOW_SOFT_INPUT message in the IIMethodWrapper queue.

if (DEBUG) Log.v(TAG, "BadTokenException: IME is done.");

mWindowVisible = false;

mWindowAdded = false;

// Rethrow the exception to preserve the existing behavior. Some IMEs may have directly

// called this method and relied on this exception for some clean-up tasks.

// TODO: Give developers a clear guideline of whether it's OK to call this method or

// InputMethodService#requestShowSelf(int) should always be used instead.

throw e;

} finally {

// TODO: Is it OK to set true when we get BadTokenException?

mWindowWasVisible = true;

mInShowWindow = false;

}

}

showWindowInner

void showWindowInner(boolean showInput) {

boolean doShowInput = false;

//上一个输入法窗口的状态,不可见则为0

final int previousImeWindowStatus =

(mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0);

mWindowVisible = true;

//mShowInputRequested两种情况为false,oncreate和hideSoftInput

//ViewClick流程中,IMMS调用到IMS的startInput方法,置mInputStarted = true

if (!mShowInputRequested && mInputStarted && showInput) {

doShowInput = true;

mShowInputRequested = true;//只要这一个地方可以置为true

}

if (DEBUG) Log.v(TAG, "showWindow: updating UI");

initialize();//调用初始化,startinput阶段已经调用一次,将mInitialized = true,该方法中会进行判断,不会重复执行

updateFullscreenMode();//根据当前是否全屏显示输入法窗口,更新布局的LayoutParams,mLastShowInputRequested等参数

//更新mIsInputViewShown,mIsInputViewShown在oncreate为false,输入法显示后,mIsInputViewShown为true

//调用声明周期函数:onCreateInputView

updateInputViewShown();

//mWindowAdded,只有在showWindow过程中,发生BadTokenException是,才会置位false

//mWindowCreated 初次oncreate-->initView置位false

if (!mWindowAdded || !mWindowCreated) {//本输入法,启动过一次以后,该端代码将不再执行

mWindowAdded = true;

mWindowCreated = true;

initialize();

if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView");

View v = onCreateCandidatesView();//创建候选布局,默认为null

if (DEBUG) Log.v(TAG, "showWindow: candidates=" + v);

if (v != null) {

setCandidatesView(v);

}

}

if (mShowInputRequested) {//楼上已经置位true

if (!mInputViewStarted) {

if (DEBUG) Log.v(TAG, "CALL: onStartInputView");

mInputViewStarted = true;

onStartInputView(mInputEditorInfo, false);//获取EditText的输入类型相关信息,IMS

}

} else if (!mCandidatesViewStarted) {

if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");

mCandidatesViewStarted = true;

onStartCandidatesView(mInputEditorInfo, false);

}

if (doShowInput) {

startExtractingText(false);

}

final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown());

if (previousImeWindowStatus != nextImeWindowStatus) {

mImm.setImeWindowStatus(mToken, mStartInputToken, nextImeWindowStatus,

mBackDisposition);

}

if ((previousImeWindowStatus & IME_ACTIVE) == 0) {

if (DEBUG) Log.v(TAG, "showWindow: showing!");

onWindowShown();

mWindow.show();//真正显示窗口

// Put here rather than in onWindowShown() in case people forget to call

// super.onWindowShown().

mShouldClearInsetOfPreviousIme = false;

}

}

onCreateInputView获取输入法的主布局

onCreateCandidatesView:获取输入的候选词布局

onStartInputView:传递Editext相关信息到IMS的具体实现

简书android 输入法设置,Android输入法弹出流程相关推荐

  1. android动态设置软键盘弹出模式,Android 弹出软键盘所遇到的坑及解决方法

    重要代码: //1.此layout作为最外层的layout: //2.设置需要调整的view: setAdjustView(View view); //3.如果需要控制输入框的显示与隐藏,可以实现On ...

  2. Swift 仿简书、淘宝App的弹出view效果

    感谢原作者提供OC代码 个人博客主站:欢迎访问http://littlesummerboy.com 先来张图让小伙伴们看一眼 主要有四个View 黑色 XtPopViewController的self ...

  3. android代码打开语言和输入法设置,Android默认输入法勾选多国语言,并默许其中一种语言...

    Android默认输入法勾选多国语言,并默认其中一种语言 1.首先在设备上调整输入法 设置>语言输入法>Android键盘(AOSP),在输入语言里勾选要选择的语言,比如选"英语 ...

  4. android 弹出编辑框,Android编程实现的EditText弹出打开和关闭工具类

    本文实例讲述了Android编程实现的EditText弹出打开和关闭工具类.分享给大家供大家参考,具体如下: 需求: 使用代码实现Android的输入框EditText对键盘的关闭弹出的实现. 代码: ...

  5. [RK3399][Android7.1.1]Android->输入法:设置默认输入法及输入法相关设置

    测试平台 Platform: RK3399 OS: Android 7.1.1 需求 始终弹出软键盘 设置默认输入法 解决方案 1. 始终弹出软键盘 : 系统默认当连接物理键盘时,虚拟键盘不会弹出. ...

  6. android 锁屏界面弹出qq,Android锁屏状态下弹出activity,如新版qq的锁屏消息提示

    Android锁屏状态下弹出activity,如新版qq的锁屏消息提示 发布时间:2020-06-26 21:27:13 来源:51CTO 阅读:1643 作者:lp5345 在接收消息广播的onRe ...

  7. [html] android手机的微信H5弹出的软键盘挡住了文本框,如何解决?

    [html] android手机的微信H5弹出的软键盘挡住了文本框,如何解决? window.addEventListener("resize", function () {if ...

  8. android PopupWindow实现从底部弹出或滑出选择菜单或窗口

    android PopupWindow实现从底部弹出或滑出选择菜单或窗口 http://www.open-open.com/lib/view/open1379383271818.html http:/ ...

  9. 高通Android 12默认授权 不弹出投屏弹窗

    1.测试反馈老化界面弹出这个界面,工厂大多数员工是不给你点的,所以这个功能必须要干掉弹窗!没啥可说的! 2. 画个简单的逻辑图,比较粗糙,大家就是简单看下,不惜勿喷哈,谢谢大佬们. 3.好吧,我只是个 ...

  10. 使用网络打印机进行“属性”和“首选项”设置时,弹出个浏览器.exe错误对话框

    使用网络打印机进行"属性"和"首选项"设置时,弹出个浏览器.exe错误对话框:"功能地址0x4f662035造成了一个保护错误. (例外码0xc000 ...

最新文章

  1. pycharm 在ubuntu18.04 20.04以上保存在侧边栏的方法
  2. ethercard php_使用Arduino和ENC28J60以太网LAN网络模块发送HTTP POST请求
  3. 3-3-完全二叉树结点数
  4. 2006年猪的述职报告
  5. 学python最好的方式-最好的Python入门教程是?
  6. 计算机网络实验arp协议分析,计算机网络ARP地址协议解析实验报告
  7. ZooKeeper--分层仲裁简介
  8. ebpf_exporter - Prometheus exporter for custom eBPF metrics
  9. java stringbuffer原理_深入理解String, StringBuffer, StringBuilder的区别(基于JDK1.8)
  10. aes 加密_PHP使用AES加密和解密
  11. DDCTFMulTzor-xoortol的使用
  12. 经典的经典:《自然哲学的数学原理》
  13. 百度如流首发AI智能办公标准 ,BATH持续发力企业办公
  14. 条件运算符的嵌套_条件运算符
  15. Matlab IQR准则剔除异常值
  16. Android 中view的解释
  17. shape的基本用法
  18. ffmpeg 4.2.1 版本升级日志 APIChanges
  19. python富翁与穷人_穷人和富人最根本的区别
  20. RFID反向不归零编码(NRZ)

热门文章

  1. [css]通过transform缩放邮件客户端h5页面
  2. TypeScript手册翻译系列1-基础类型
  3. php函数copy和rename的区别
  4. javascript 中Math函数在勾弦定理计算上的错误
  5. 1.SOA架构:服务和微服务分析及设计--- 理解面向服务
  6. 15.深入理解Zend执行引擎(PHP5)
  7. 57. 局域网控制者:Proxy 服务器
  8. 百度地图API常规应用十功能
  9. xBIM 基础07 创建WebBIM文件
  10. Ubuntu下TP5隐藏入口文件