源码分析 - Andrid 输入法框架 之 键盘启动的流程
点击 EditText , EditText 获得焦点,然后键盘显示,这是最常见的操作,可是里面的内部实现是怎么样的呢?带着这个疑问,我们来看看系统源码。
我们知道, EditText 是获得了点击事件,才能显示键盘,如果 EditText 连点击事件都没收到,肯定不会显示键盘,所以, EditText 肯定会执行到 onTouchEvent() 方法。
由于 EditText 继承 Textview ,并且本身没有覆盖 onTouchEvent() ,所以我们需要查看 TextView 中的onTouchEvent()
//TextView.java
public boolean onTouchEvent(MotionEvent event) {
...
final boolean superResult = super.onTouchEvent(event);
...
// 显示 IME ,除非选择只读文本。
final InputMethodManager imm = InputMethodManager.peekInstance();
viewClicked(imm);
//这个是真正显示输入法的调用
if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
handled |= imm != null && imm.showSoftInput(this, 0);
}
...
}
在 TextView 的 onTouchEvent() 中,我们发现了两点比较关键信息
- 调用了父类的 onTouchEvent() 方法。
- 找到了显示输入法地方 imm.showSoftInput()。
首先我们查看父类的 onTouchEvent() 都做了什么
View # onTouchEvent()
我们知道, onTouchEvent() 的返回值表示是否消耗该事件, true 表示消耗, false 表示不消耗。
public boolean onTouchEvent(MotionEvent event) {
...
if ((viewFlags & ENABLED_MASK) == DISABLED) {//View 处于不可点击状态下
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
//处于不可点击状态下也是可以消耗事件的,只不过会会响应而已
return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) ==LONG_CLICKABLE));
}
if (mTouchDelegate != null) {//如果设置的 TouchDelegate ,则会执行 TouchDelegate 的 onTouchEvent() ,
//onTouchEvent()的机制和 onTouchListener 类似
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
// 只要设置了 CLICKABLE 或者 LONG_CLICKABLE 任意一个为 true ,就会消耗掉事件
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
//让 view 获得焦点
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
...
}
}
}
}
代码里面带有注释,就不多解释了。只需要记得。在这里面通过 requestFocus()使得View获得了焦点。
执行完View.onTouchEvent()后,就到了关键的得到InputMethodManager对象imm上了。
得到IMM对象
先看看 InputMethodManager imm = InputMethodManager.peekInstance()是怎么实现的吧。
//InputMethodManager.java
public static InputMethodManager peekInstance() {
return sInstance;
}
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
// InputMethodManager其实就是一个 Binder service 的proxy
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
//这个 service 其实就是IMMS
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
peekInstance()就是得到 IMM实例,而这个实例就是在 getInstance()中初始化的,关于IMM的初始化,在前面已经讲过了,详情可以查看源码分析 - Andrid 输入法框架 之 IMM初始化
InputMethodManager.getInstance()又是在哪里执行的呢,经过我们的搜索,发现在WindowManagerGloabl.java 中执行了。
//WindowManagerGloabl.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) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
而getWindowSession()是在 ViewRootImp 初始化的时候执行的
//ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
...
}
我们知道, 每一个 Window 对应一个 View 和 ViewRootImpl , Window 和 View 通过 ViewRootImpl 建立联系 ,所以当我们在 addView() 的时候,就会创建一个ViewRootImpl,那么在这个时候。 InputMethodManager.getInstance()就会执行返回一个IMM对象sInstance,那么在我们点击EditText的时候,InputMethodManager.peekInstance() 就肯定不会为null,所以就可以执行到了imm. showSoftInput()中了。 那我们就看 showSoftInput() 的逻辑吧。
IMM # showSoftInput()
// IMM.java
public boolean showSoftInput(View view, int flags) {
return showSoftInput(view, flags , null);
}
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) {
}
return false;
}
}
看到了一个变量 mService ,在IMM初始化的时候,我们已经说过这个家伙了,是一个 IInputMethodManager 对象,真正实现对象是InputMethodManagerService(IMMS),这里有一步跨进程操作。那我们直接看 IMMS 的 showSoftInput()吧。
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) {
if (mCurClient == null || client == null || mCurClient.client.asBinder() != client.asBinder()) {
try {
// 我们需要检查这是否是焦点在窗口管理器中的当前客户端,以便在输入启动之前进行此调用。
if (!mIWindowManager.inputMethodClientHasFocus(client)) {
return false;
}
} catch (RemoteException e) {
return false;
}
}
return showCurrentInputLocked(flags, resultReceiver);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
执行到了 showCurrentInputLocked() 中
boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
...
if (mCurMethod != null) {
executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(MSG_SHOW_SOFT_INPUT, getImeShowFlags() , mCurMethod , resultReceiver));
mInputShown = true;
if (mHaveConnection && !mVisibleBound) {
bindCurrentInputMethodService(mCurIntent, mVisibleConnection , Context.BIND_AUTO_CREATE | Context.BIND_TREAT_LIKE_ACTIVITY);
mVisibleBound = true;
}
res = true;
} else if (mHaveConnection && SystemClock.uptimeMillis() >= (mLastBindTime + TIME_TO_RECONNECT)) {
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId , SystemClock.uptimeMillis() - mLastBindTime, 1);
mContext.unbindService(this);
bindCurrentInputMethodService(mCurIntent, this , Context.BIND_AUTO_CREATE | Context.BIND_NOT_VISIBLE);
} else {
}
return res;
}
在 showCurrentInputLocked中会通过executeOrSendMessage() 发送 MSG_SHOW_SOFT_INPUT 消息,最后就执行到了HandlerCaller.java 的 handleMessage() 的 MSG_SHOW_SOFT_INPUT 中
executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(MSG_SHOW_SOFT_INPUT,
getImeShowFlags() , mCurMethod , resultReceiver));
//HandlerCaller.java
public Message obtainMessageIOO(int what, int arg1 , Object arg2 , Object arg3) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = arg2;
args.arg2 = arg3;
return mH.obtainMessage(what, arg1 , 0 , args);
}
case MSG_SHOW_SOFT_INPUT:
args = (SomeArgs) msg.obj;
try {
((IInputMethod) args.arg1).showSoftInput(msg.arg1, (ResultReceiver) args.arg2);
} catch (RemoteException e) {
}
args.recycle();
return true;
而在 源码分析 - Andrid 输入法框架 之 启动服务也说了,在IMMS绑定成功,mCurMethod =IInputMethod.Stub.asInterface(service),是IInputMethodWrapper的代理对象,而 args.arg1 就是传递过来的mCurMethod ,那我们就直接看看 IInputMethodWrapper的 showSoftInput()。
//IInputMethodWrapper.java
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT, flags , resultReceiver));
}
//发送消息,然后处理DO_SHOW_SOFT_INPUT 这个消息
@Override
public void executeMessage(Message msg) {
InputMethod inputMethod = mInputMethod.get();
...
switch (msg.what) {
case DO_SHOW_SOFT_INPUT:
inputMethod.showSoftInput(msg.arg1, (ResultReceiver) msg.obj);
}
}
inputMethod 在 源码分析 - Andrid 输入法框架 之 启动服务也说了,就是InputMethodImpl对象。那么我们就直接看 inputMethod.showSoftInput()的流程吧。
InputMethodImpl # showSoftInput()
//InputMethodImpl.java
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
...
//这个是真正显示 UI 的函数
showWindow(true);
...
}
public void showWindow(boolean showInput) {
if (mInShowWindow) {
return;
}
try {
mWindowWasVisible = mWindowVisible;
mInShowWindow = true;
showWindowInner(showInput);
} finally {
mWindowWasVisible = true;
mInShowWindow = false;
}
}
void showWindowInner(boolean showInput) {
...
initialize();
updateFullscreenMode();
//这个函数会创建输入法的键盘
updateInputViewShown();
if (!mWindowAdded || !mWindowCreated) {
mWindowAdded = true;
mWindowCreated = true;
initialize();
//创建输入法 dialog 里的候选词View
View v = onCreateCandidatesView();
if (v != null) {
setCandidatesView(v);
}
}
if (mShowInputRequested) {
if (!mInputViewStarted) {
mInputViewStarted = true;
onStartInputView(mInputEditorInfo, false);
}
} else if (!mCandidatesViewStarted) {
mCandidatesViewStarted = true;
onStartCandidatesView(mInputEditorInfo, false);
}
if (doShowInput) {
startExtractingText(false);
}
if (!wasVisible) {
mImm.setImeWindowStatus(mToken, IME_ACTIVE , mBackDisposition);
onWindowShown();
//这个是 Dialog 的 window ,这里开始就显示 UI 了
mWindow.show();
}
}
关键也就是 showWindowInner()方法,这里面主要做了以下工作
- 在 updateInputViewShown() 中会创建输入法键盘布局,并添加进去
- 通过 onCreateCandidatesView() 创建输入法 dialog 里的候选词View
- onWindowShown()当输入法显示的时候,执行的方法,可以在这里面做一些操作
- 执行 mWindow.show()从而显示输入法
接下来就说说 updateInputViewShown()
InputMethodImpl # updateInputViewShown()
public void updateInputViewShown() {
boolean isShown = mShowInputRequested && onEvaluateInputViewShown();
if (mIsInputViewShown != isShown && mWindowVisible) {
mIsInputViewShown = isShown;
mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE);
if (mInputView == null) {
initialize();
//这个是核心 view ,创建显示键盘的根view
View v = onCreateInputView();
if (v != null) {
setInputView(v);
}
}
}
}
public void setInputView(View view) {
mInputFrame.removeAllViews();
mInputFrame.addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT));
mInputView = view;
}
public View onCreateInputView() {
return null;
}
onCreateInputView()在IMS中是空实现,这个是我们开发输入法的时候,设置输入法布局,创建这个View之后,就会通过 setInputView()把这个View添加到mInputFrame中。这个mInputFrame就是布局是R.layout.input_method中 id 为@android:id/inputArea的 FrameLayout。
例如
public class LatinIME extends InputMethodService {
@Override
public View onCreateInputView() {
if (mEnvironment.needDebug()) {
Log.d(TAG, "onCreateInputView.");
}
rootView = getWindow().getWindow().findViewById(Window.ID_ANDROID_CONTENT);
/**
* 获取导航栏高度——方法1
* */
navigationBarHeight = -1;
//获取status_bar_height资源的ID
int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
//根据资源ID获取响应的尺寸值
navigationBarHeight = getResources().getDimensionPixelSize(resourceId);
}
LayoutInflater inflater = getLayoutInflater();
mSkbContainer = (SkbContainer) inflater.inflate(R.layout.skb_container, null);
if (Utils.isShowKeyboardInEink()) {
mSkbContainer.setBackgroundColor(getResources().getColor(R.color.white));
}
mSkbContainer.setService(this);
mSkbContainer.setInputModeSwitcher(mInputModeSwitcher);
mSkbContainer.setGestureDetector(mGestureDetectorSkb);
return mSkbContainer;
}
@Override
public View onCreateCandidatesView() {
if (mEnvironment.needDebug()) {
Log.d(TAG, "onCreateCandidatesView.");
}
LayoutInflater inflater = getLayoutInflater();
mCandidatesContainer = (CandidatesContainer) inflater.inflate(R.layout.candidates_container, null);
//将ComposingView直接显示到CandidatesContainer中
mComposingView = (ComposingView) mCandidatesContainer.findViewById(R.id.composing_view);
line_separator1 = (TextView) mCandidatesContainer.findViewById(R.id.line_separator1);
line_separator2 = (TextView) mCandidatesContainer.findViewById(R.id.line_separator2);
// Create balloon hint for candidates view.
mCandidatesBalloon = new BalloonHint(this, mCandidatesContainer, MeasureSpec.UNSPECIFIED);
mCandidatesBalloon.setBalloonBackground(getResources().getDrawable(R.drawable.candidate_balloon_bg));
mCandidatesContainer.initialize(mChoiceNotifier, mCandidatesBalloon, mGestureDetectorCandidates);
setCandidatesViewShown(true);
return mCandidatesContainer;
}
}
到这里,输入法已经显示出来了,可是怎么把文本提交到输入框中呢。
简单来说就是通过InputConnection 的 commitText()方法,在每个IMS中,都可以通过getCurrentInputConnection()得到对应的InputConnection,
private void commitResultText(String resultText, String secondeText) {
InputConnection ic = getCurrentInputConnection();
ic.commitText(resultText, 1);
}
EditText 获得焦点,传递的InputConnection就是EditableInputConnection,它继承BaseInputConnection,至于这个是在什么时候传递过去的,请看 源码分析 - Andrid 输入法 之 InputConnection 对象创建
搬运地址:
既已览卷至此,何不品评一二: