输入法,就是用来输入字符(包括英文,俄文,中文)的工具。输入法你可以看成是一种字符发生器,它将输入数据触摸事件或者按键事件转化为其他更丰富的字符。在PC时代,输入法的原始输入来自实体键盘,鼠标,然后输入法将这些事件对应的ASCII码转换为俄文,中文,当然如果是英文是不需要转换,直接发送即可。而在Android系统里,由于输入法dialog永远没法成为焦点window,所以输入法永远没法获取到按键事件,也就是说输入法的输入数据只能来自触摸事件,输入法显示出键盘(大家称之为软键盘),用户点击键盘UI, 然后输入法将触摸事件所在位置的字符当做原始字符输入,最后组装成更为丰富的字符(多个字符组成拼音,然后转化为中文),然后就是发送到对应的程序。
Android系统中,输入法可以是可以安装的,也就是说系统可以有多个输入法((sougou输入法,百度输入法),但是只有一个是激活的,当然用户可以切换输入法。同时,输入法是以service的方式运行的,输入法同一时间只能服务一个程序,只有最顶层的可见的程序才能接收到输入法的输入数据。
输入法系统的整个框架如下:
InputMethodManagerService(下文也称IMMS)负责管理系统的所有输入法,包括输入法service(InputMethodService简称IMS)加载及切换。程序获得焦点时,就会通过InputMethodManager向InputMethodManagerService通知自己获得焦点并请求绑定自己到当前输入法上。同时,当程序的某个需要输入法的view比如EditorView获得焦点时就会通过InputMethodManager向InputMethodManagerService请求显示输入法,而这时InputMethodManagerService收到请求后,会将请求的EditText的数据通信接口发送给当前输入法,并请求显输入法。输入法收到请求后,就显示自己的UI dialog,同时保存目标view的数据结构,当用户实现输入后,直接通过view的数据通信接口将字符传递到对应的View。接下来就来分析这些过程。
InputMethodManager创建
每个程序有一个InputMethodManager实例,这个是程序和InputMethodManagerService通信的接口,该实例在ViewRootImpl初始化的时候创建。
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
}
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
//这个进程的InputMethodManager实例就生成了
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
} catch (RemoteException e) {
Log.e(TAG, "Failed to open window session", e);
}
}
return sWindowSession;
}
}
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
// InputMethodManager其实就是一个Binder service的proxy
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
程序的Window获得焦点
程序的window获得焦点的时序图如下
系统WindowManagerService更新焦点window
哪个程序获得焦点是由系统决定的,是由WindowManagerService决定的,当系统的window状态发生变化时(比如window新增,删除)就会调用函数updateFocusedWindowLocked来更新焦点window。
private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
//计算焦点window
WindowState newFocus = computeFocusedWindowLocked();
if (mCurrentFocus != newFocus) {
//焦点window发生变化,post一个message来通知程序焦点发生变化了
mH.removeMessages(H.REPORT_FOCUS_CHANGE);
mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
return true;
}
return false;
}
private WindowState computeFocusedWindowLocked() {
if (mAnimator.mUniverseBackground != null
&& mAnimator.mUniverseBackground.mWin.canReceiveKeys()) {
return mAnimator.mUniverseBackground.mWin;
}
final int displayCount = mDisplayContents.size();
for (int i = 0; i < displayCount; i++) {
final DisplayContent displayContent = mDisplayContents.valueAt(i);
WindowState win = findFocusedWindowLocked(displayContent);
if (win != null) {
return win;
}
}
return null;
}
//该函数就是找出最top的可以接收按键事件的window,这个window就获得焦点
private WindowState findFocusedWindowLocked(DisplayContent displayContent) {
final WindowList windows = displayContent.getWindowList();
for (int i = windows.size() - 1; i >= 0; i--) {
final WindowState win = windows.get(i);
//是否为activity的window
AppWindowToken wtoken = win.mAppToken;
//重要函数,window是否可以获取焦点
if (!win.canReceiveKeys()) {
continue;
}
// mFocusedApp是最top的activity ,下面逻辑是为了确保焦点window的app
//必须是焦点程序之上,所以这个逻辑其实并没有多大作用,只是为了检测出
//错误
if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING &&
mFocusedApp != null) {
ArrayList<Task> tasks = displayContent.getTasks();
for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
int tokenNdx = tokens.size() - 1;
for ( ; tokenNdx >= 0; --tokenNdx) {
final AppWindowToken token = tokens.get(tokenNdx);
if (wtoken == token) {
break;
}
if (mFocusedApp == token) {
return null;
}
}
}
}
return win;
}
return null;
}
public final boolean canReceiveKeys() {
return isVisibleOrAdding()
&& (mViewVisibility == View.VISIBLE)
&& ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0);
}
//由于输入法的window带有FLAG_NOT_FOCUSABLE, 从上可见其不可能是焦点window
//接下来系统开始通知程序端哪个window获得了焦点。
final class H extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case REPORT_FOCUS_CHANGE: {
WindowState lastFocus;
WindowState newFocus;
synchronized(mWindowMap) {
lastFocus = mLastFocus;
newFocus = mCurrentFocus;
if (lastFocus == newFocus) {
// Focus is not changing, so nothing to do.
return;
}
mLastFocus = newFocus;
}
if (newFocus != null) {
//通知新的焦点程序其获得了焦点
newFocus.reportFocusChangedSerialized(true, mInTouchMode);
notifyFocusChanged();
}
if (lastFocus != null) {
//通知老的焦点程序其获得了焦点
lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
}
} break;
}
public void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
try {
//这个就是通过Binder告知client其获得或失去了焦点
mClient.windowFocusChanged(focused, inTouchMode);
} catch (RemoteException e) {
}
}
上面的mClient.windowFocusChanged会调回到ViewRootImpl中的W实例:
程序获得焦点改变事件
//ViewRootImpl.java
static class W extends IWindow.Stub {
@Override
public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
}
}
}
public 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.onWindowFocus来通知IMMS焦点信息发生改变,需要更新输入法了
final class ViewRootHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_WINDOW_FOCUS_CHANGED: {
if (mAdded) {
boolean hasWindowFocus = msg.arg1 != 0;
mAttachInfo.mHasWindowFocus = hasWindowFocus;
mLastWasImTarget = WindowManager.LayoutParams
.mayUseInputMethod(mWindowAttributes.flags);
InputMethodManager imm = InputMethodManager.peekInstance();
if (mView != null) {
//调用根view的dispatchWindowFocusChanged函数通知view
//程序获得焦点
mView.dispatchWindowFocusChanged(hasWindowFocus);
mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(
hasWindowFocus);
}
if (hasWindowFocus) {
if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
//通知imm该window获得焦点
imm.onWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
}
}
} break;
}
//上面的根view就是DecorView,它只是调用父类ViewGroup
//的dispatchWindowFocusChanged
//ViewGroup.java
@Override
public void dispatchWindowFocusChanged(boolean hasFocus) {
super.dispatchWindowFocusChanged(hasFocus);
final int count = mChildrenCount;
final View[] children = mChildren;
//让每个子view处理window焦点改变时间
//但是只有获得焦点的view才会处理这个时间
for (int i = 0; i < count; i++) {
children[i].dispatchWindowFocusChanged(hasFocus);
}
}
//View.java
public void onWindowFocusChanged(boolean hasWindowFocus) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (!hasWindowFocus) {
} else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
//获得焦点的view通过 InputMethodManager向service通知自己获得焦点
imm.focusIn(this);
}
}
焦点View向IMMS请求绑定输入法
焦点view请求绑定输入法是通过调用InputMethodManager. focusIn实现的
public void focusIn(View view) {
synchronized (mH) {
focusInLocked(view);
}
}
void focusInLocked(View view) {
//保存焦点view变量
mNextServedView = view;
scheduleCheckFocusLocked(view);
}
static void scheduleCheckFocusLocked(View view) {
ViewRootImpl viewRootImpl = view.getViewRootImpl();
if (viewRootImpl != null) {
viewRootImpl.dispatchCheckFocus();
}
}
public 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;
public void checkFocus() {
if (checkFocusNoStartInput(false, true)) {
startInputInner(null, 0, 0, 0);
}
}
boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,
int windowFlags) {
final View view;
synchronized (mH) {
//获得上面的焦点view
view = mServedView;
}
EditorInfo tba = new EditorInfo();
tba.packageName = view.getContext().getPackageName();
tba.fieldId = view.getId();
//创建数据通信连接接口,这个会传送到InputMethodService
//InputMethodService后面就通过这个connection将输入法的字符传递给该view
InputConnection ic = view.onCreateInputConnection(tba);
synchronized (mH) {
mServedInputConnection = ic;
ControlledInputConnectionWrapper servedContext;
if (ic != null) {
mCursorSelStart = tba.initialSelStart;
mCursorSelEnd = tba.initialSelEnd;
mCursorCandStart = -1;
mCursorCandEnd = -1;
mCursorRect.setEmpty();
//将InputConnection封装为binder对象,这个是真正可以实现跨进程通
//信的封装类
servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);
}
mServedInputConnectionWrapper = servedContext;
try {
InputBindResult res;
if (windowGainingFocus != null) {
//focusIn这个不会走到这条分支
res = mService.windowGainedFocus(mClient, windowGainingFocus,
controlFlags, softInputMode, windowFlags,
tba, servedContext);
} else {
//通知InputMethodManagerService,该程序的view获得焦点,IMMS
//就会将这个view和输入法绑定
res = mService.startInput(mClient,
servedContext, tba, controlFlags);
}
if (res != null) {
if (res.id != null) {
setInputChannelLocked(res.channel);
mBindSequence = res.sequence;
//获得了输入法的通信接口
mCurMethod = res.method;
mCurId = res.id;
}
}
}
}
return true;
}
IMMS处理view绑定输入法事件
为了讲解整个绑定过程,我们假设此时输入法service还没启动,这个情况下的输入法绑定是最长的,整个过程经历过如下过程:
1) 启动输入法service
2) 绑定输入法window的token
3) 请求输入法为焦点程序创建一个连接会话-
4) 将输入法的接口传递回程序client端
5) 绑定输入法和焦点view
1-4是和程序相关的,而5是和view相关的。所以你可以说1~4是用来绑定程序window和输入法,而5是用来绑定程序view和输入法。
输入法还没启动时,弹出输入法会经过1~5,输入法已经启动,但是焦点window发生变化时会经历3~5,焦点window没有变化,只是改变了焦点view,则只会经历5。整个流程如下:
启动输入法service
@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);
}
}
}
InputBindResult startInputLocked(IInputMethodClient client,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
//程序在service端对应的数据结构
ClientState cs = mClients.get(client.asBinder());
return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
}
InputBindResult startInputUncheckedLocked(ClientState cs,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
//如果新程序和当前活动的程序不同
if (mCurClient != cs) {
//取消当前活动程序和输入法的绑定
unbindCurrentClientLocked();
}
//将新程序设置为当前活动的程序
mCurClient = cs;
mCurInputContext = inputContext;
mCurAttribute = attribute;
if (mCurId != null && mCurId.equals(mCurMethodId)) {
if (cs.curSession != null) {
//连接已经建立,直接开始绑定
return attachNewInputLocked(
(controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
}
if (mHaveConnection) {
//输入法的连接是否已经创建,如果已经创建,直接传递给程序client端
if (mCurMethod != null) {
requestClientSessionLocked(cs);
return new InputBindResult(null, null, mCurId, mCurSeq);
}
}
}
//否则需要启动输入法,并建立连接
return startInputInnerLocked();
}
InputBindResult startInputInnerLocked() {
InputMethodInfo info = mMethodMap.get(mCurMethodId);
unbindCurrentMethodLocked(false, true);
//启动输入法service
mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
mCurIntent.setComponent(info.getComponent());
mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.input_method_binding_label);
mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
| Context.BIND_NOT_VISIBLE | Context.BIND_SHOWING_UI)) {
mHaveConnection = true;
mCurId = info.getId();
//这个token是给输入法service用来绑定输入法的window的,通过这个token
//InputMethodManagerService可以很方便的直接管理输入法的window
mCurToken = new Binder();
try {
mIWindowManager.addWindowToken(mCurToken,
WindowManager.LayoutParams.TYPE_INPUT_METHOD);
} catch (RemoteException e) {
}
return new InputBindResult(null, null, mCurId, mCurSeq);
}
return null;
}
private boolean bindCurrentInputMethodService(
Intent service, ServiceConnection conn, int flags) {
if (service == null || conn == null) {
Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
return false;
}
return mContext.bindServiceAsUser(service, conn, flags,
new UserHandle(mSettings.getCurrentUserId()));
}
//输入法启动完成后就在函数onBind 传回一个binder接口
@Override
final public IBinder onBind(Intent intent) {
if (mInputMethod == null) {
mInputMethod = onCreateInputMethodInterface();
}
// IInputMethodWrapper只是一个wrapper,它负责将IMMS的调用转化为message
//然后在message线程再调用mInputMethod对应的接口
//这样输入法的处理就是异步的了,因此你说它就是mInputMethod
return new IInputMethodWrapper(this, mInputMethod);
}
@Override
public AbstractInputMethodImpl onCreateInputMethodInterface() {
return new InputMethodImpl();
}
//由于IMMS是以bindService的方式启动输入法service,所以当输入法service启动完
//成后它就会回调IMMS的onServiceConnected
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
//保存输入法service传递过来的通信接口IInputMethod
mCurMethod = IInputMethod.Stub.asInterface(service);
//将刚刚创建的window token传递给输入法service,然后输入用这个token
//创建window,这样IMMS可以用根据这个token找到输入法在IMMS里
//的数据及输入法window在WMS里的数据
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
if (mCurClient != null) {
//请求为程序和输入法建立一个连接会话,这样client就可以直接和
//输入法通信了
requestClientSessionLocked(mCurClient);
}
}
}
}
输入法Window token的绑定及使用分析
输入法Window token绑定
IMMS在输入法启动完成并回调onServiceConnected时会将一个Window token传递给输入法。
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
mCurMethod = IInputMethod.Stub.asInterface(service);
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
requestClientSessionLocked(mCurClient);
}
}
}
}
case MSG_ATTACH_TOKEN:
args = (SomeArgs)msg.obj;
try {
//和输入法通信
((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
public class InputMethodService extends AbstractInputMethodService {
public class InputMethodImpl extends AbstractInputMethodImpl {
public void attachToken(IBinder token) {
if (mToken == null) {
//保存token
mToken = token;
//这样输入法的window就绑定这个window token
mWindow.setToken(token);
}
}
}
输入法Window token使用
由于系统存在多个输入法,所以输入法要和IMMS通信,必须要个机制来标示自己是哪个输入法,这个就是通过上面的输入法Window token来实现的,比如输入法自己关闭自己:
//InputMethodService.java输入法接口
public void requestHideSelf(int flags) {
//mToken就是上面提到的过程----IMMS传递给输入法的
mImm.hideSoftInputFromInputMethod(mToken, flags);
}
//InputMethodManager.java
public void hideSoftInputFromInputMethod(IBinder token, int flags) {
try {
mService.hideMySoftInput(token, flags);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
//IMMS
@Override
public void hideMySoftInput(IBinder token, int flags) {
if (!calledFromValidUser()) {
return;
}
synchronized (mMethodMap) {
if (token == null || mCurToken != token) {
if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
+ Binder.getCallingUid() + " token: " + token);
return;
}
long ident = Binder.clearCallingIdentity();
try {
hideCurrentInputLocked(flags, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
输入法连接会话创建
到此程序和输入法的session就建立了
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
requestClientSessionLocked(mCurClient);
}
}
}
}
void requestClientSessionLocked(ClientState cs) {
if (!cs.sessionRequested) {
//这里又出现了InputChannel对,很面熟吧,在前面几篇文章已经详细分析过
//了,可见它已经成为一种通用的跨平台的数据通信接口了
InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
cs.sessionRequested = true;
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
MSG_CREATE_SESSION, mCurMethod, channels[1],
new MethodCallback(this, mCurMethod, channels[0])));
}
}
case MSG_CREATE_SESSION: {
args = (SomeArgs)msg.obj;
IInputMethod method = (IInputMethod)args.arg1;
InputChannel channel = (InputChannel)args.arg2;
try {
method.createSession(channel, (IInputSessionCallback)args.arg3);
} catch (RemoteException e) {
}
//上面是IMMS端,下面就看IMS输入法端的处理
public abstract class AbstractInputMethodService extends Service
implements KeyEvent.Callback {
public abstract class AbstractInputMethodImpl implements InputMethod {
public void createSession(SessionCallback callback) {
callback.sessionCreated(onCreateInputMethodSessionInterface());
}
<pre class="java" name="code"> }
}
传递输入法接口给程序
void onSessionCreated(IInputMethod method, IInputMethodSession session,
InputChannel channel) {
synchronized (mMethodMap) {
if (mCurMethod != null && method != null
&& mCurMethod.asBinder() == method.asBinder()) {
if (mCurClient != null) {
InputBindResult res = attachNewInputLocked(true);
if (res.method != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
MSG_BIND_METHOD, mCurClient.client, res));
}
return;
}
}
}
channel.dispose();
}
case MSG_BIND_METHOD: {
args = (SomeArgs)msg.obj;
IInputMethodClient client = (IInputMethodClient)args.arg1;
InputBindResult res = (InputBindResult)args.arg2;
try {
//会调回到程序端
client.onBindMethod(res);
}
args.recycle();
return true;
}
输入法和view绑定
//IMMS
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));
}
return new InputBindResult(session.session,
session.channel != null ? session.channel.dup() : null, mCurId, mCurSeq);
}
case MSG_BIND_INPUT:
args = (SomeArgs)msg.obj;
try {
((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
return true;
case MSG_START_INPUT:
args = (SomeArgs)msg.obj;
try {
SessionState session = (SessionState)args.arg1;
session.method.startInput((IInputContext)args.arg2,
(EditorInfo)args.arg3);
} catch (RemoteException e) {
}
args.recycle();
return true;
//IMS
@Override
public void startInput(IInputContext inputContext, EditorInfo attribute) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
inputContext, attribute));
}
case DO_START_INPUT: {
SomeArgs args = (SomeArgs)msg.obj;
// IInputContext就是输入法和文本输入view的通信接口
//通过这个接口,输入法能够获取view的信息,也能够直接将文本传
//送给view
IInputContext inputContext = (IInputContext)args.arg1;
InputConnection ic = inputContext != null
? new InputConnectionWrapper(inputContext) : null;
EditorInfo info = (EditorInfo)args.arg2;
inputMethod.startInput(ic, info);
args.recycle();
return;
}
public class InputMethodImpl extends AbstractInputMethodImpl {
public void startInput(InputConnection ic, EditorInfo attribute) {
doStartInput(ic, attribute, false);
}
}
void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
if (!restarting) {
doFinishInput();
}
mInputStarted = true;
mStartedInputConnection = ic;
mInputEditorInfo = attribute;
initialize();
onStartInput(attribute, restarting);
if (mWindowVisible) {
if (mShowInputRequested) {
mInputViewStarted = true;
//真正的输入法需要在这个接口里实现输入法的内容
onStartInputView(mInputEditorInfo, restarting);
startExtractingText(true);
} else if (mCandidatesVisibility == View.VISIBLE) {
mCandidatesViewStarted = true;
onStartCandidatesView(mInputEditorInfo, restarting);
}
}
}
到此焦点view已经通过调用IMMS的startInput和输入法绑定了,但是此时输入法还没有显示。但是系统紧接着会调用windowGainFocus来显示输入法。
程序焦点获取事件导致输入法显示
输入法响应显示请求
用户单击输入框View导致输入法显示
输入法传递输入文本信息给view
/********************************
* 本文来自博客 “爱踢门”
* 转载请标明出处:http://blog.csdn.net/itleaks
******************************************/