以下来自android官网的介绍:
Architecture Overview
There are three primary parties involved in the input method framework (IMF) architecture:
IMF架构涉及到三个主要的部分:
综上,整个IMF是围绕着IMMS,IMM,IMS三个核心类进行工作的,这三个核心类分别运行于system进程,客户端进程以及输入法进程,他们之间的交互需要使用android跨进程通信机制-IPC。下面将分别介绍三个核心类的主要代码以及他们之间是如何进行交互的。
首先看一下整体的UML 图
从图中可以看出IMMS,IMS,IMM之间是通过5个AIDL接口进行交互的
下面将从IMMS,IMM,IMS以及整个IMF。
IMMS是android的一个系统级服务,运行于system_process进程中。主要作用是管理输入法列表,绑定和解绑当前输入法,管理客户端以及和其他系统级服务交互。
IMMS是由SystemServer类创建的,而SystemServer类在设备启动的时候会被初始化,它的核心逻辑的入口是main方法,代码如下
/**
* The main entry point from zygote.
*/
public static void main(String[] args) {
new SystemServer().run();
}
private void run() {
......
// Start services.
try {
startBootstrapServices();
startCoreServices();
startOtherServices();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
}
......
}
private void startOtherServices() {
......
// Bring up services needed for UI.
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
try {
Slog.i(TAG, "Input Method Service");
//实例化了imms对象,并注册到ServiceManager里
imm = new InputMethodManagerService(context, wm);
ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
} catch (Throwable e) {
reportWtf("starting Input Manager Service", e);
}
}
......
mActivityManagerService.systemReady(new Runnable() {
@Override
public void run() {
......
try {
// 调用systemRunning方法
if (immF != null) immF.systemRunning(statusBarF);
} catch (Throwable e) {
reportWtf("Notifying InputMethodService running", e);
}
......
}
});
}
public void systemRunning(StatusBarManagerService statusBar) {
synchronized (mMethodMap) {
if (DEBUG) {
Slog.d(TAG, "--- systemReady");
}
if (!mSystemReady) {
......
//创建输入法列表和设置默认输入法
buildInputMethodListLocked(mMethodList, mMethodMap,
!mImeSelectedOnBoot /* resetDefaultEnabledIme */);
......
try {
//绑定默认输入法
startInputInnerLocked();
} catch (RuntimeException e) {
Slog.w(TAG, "Unexpected exception", e);
}
}
}
}
所以IMMS的初始化最重要的工作就是建立一个输入法列表以及绑定默认输入法
在IMMS初始化过程中会创建一个ArrayList对象mMethodList和一个HashMap对象mMethodMap来保存系统内所有输入法的信息。代码如下
void buildInputMethodListLocked(ArrayList list,
HashMap map, boolean resetDefaultEnabledIme) {
......
// 返回满足条件的ResolveInfo,即具有action为android.view.inputmethod的service
final List services = pm.queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
mSettings.getCurrentUserId());
final HashMap> additionalSubtypes =
mFileManager.getAllAdditionalInputMethodSubtypes();
for (int i = 0; i < services.size(); ++i) {
ResolveInfo ri = services.get(i);
ServiceInfo si = ri.serviceInfo;
ComponentName compName = new ComponentName(si.packageName, si.name);
//如果这个service不具有输入法权限android.permission.BIND_INPUT_METHOD,直接跳过
if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
si.permission)) {
Slog.w(TAG, "Skipping input method " + compName
+ ": it does not require the permission "
+ android.Manifest.permission.BIND_INPUT_METHOD);
continue;
}
if (DEBUG) Slog.d(TAG, "Checking " + compName);
try {
//创建一个InputMethodInfo对象,并放入mMethodList和mMethodMap中,键值为compName,也是其id值.
//InputMethodInfo的构造方法里通过解析xml,保存其id,subType,settingActivity等信息
InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
list.add(p);
final String id = p.getId();
map.put(id, p);
if (DEBUG) {
Slog.d(TAG, "Found an input method " + p);
}
} catch (XmlPullParserException e) {
Slog.w(TAG, "Unable to load input method " + compName, e);
} catch (IOException e) {
Slog.w(TAG, "Unable to load input method " + compName, e);
}
}
// 重置默认输入法,
if (resetDefaultEnabledIme) {
final ArrayList defaultEnabledIme =
InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, list);
for (int i = 0; i < defaultEnabledIme.size(); ++i) {
final InputMethodInfo imi = defaultEnabledIme.get(i);
if (DEBUG) {
Slog.d(TAG, "--- enable ime = " + imi);
}
setInputMethodEnabledLocked(imi.getId(), true);
}
}
......
}
从创建输入法列表的过程中,就能明白为什么我们在声明一个输入法的service的时候需要添加输入法权限和能匹配对应action的Intent Filter,以及提供一个xml包含subType等信息。
因为解绑和绑定过程类似,所以这里先简单介绍IMMS是如何绑定IME。代码如下
InputBindResult startInputInnerLocked() {
......
InputMethodInfo info = mMethodMap.get(mCurMethodId);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
}
//绑定新的IME前先解绑当前的IME
unbindCurrentMethodLocked(false, true);
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_NOT_FOREGROUND
| Context.BIND_SHOWING_UI)) {
mLastBindTime = SystemClock.uptimeMillis();
mHaveConnection = true;
mCurId = info.getId();
mCurToken = new Binder();
try {
if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
mIWindowManager.addWindowToken(mCurToken,
WindowManager.LayoutParams.TYPE_INPUT_METHOD);
} catch (RemoteException e) {
}
return new InputBindResult(null, null, mCurId, mCurSeq,
mCurUserActionNotificationSequenceNumber);
} else {
mCurIntent = null;
Slog.w(TAG, "Failure connecting to input method service: "
+ mCurIntent);
}
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()));
}
因为IMS本质是一个service,所以绑定一个IME实际上就是绑定一个service的过程。IMMS这里属于客户端,如果绑定成功,会回调onServiceConnected方法(同理,解绑也会回调onServiceDisconnected)。IMMS的代码如下
// 这里也会回调IMS的onBind方法
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
// mCurMethod持有IInputMethod接口的远程代理对象
mCurMethod = IInputMethod.Stub.asInterface(service);
if (mCurToken == null) {
Slog.w(TAG, "Service connected without a token!");
unbindCurrentMethodLocked(false, false);
return;
}
if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
//会回调IMS的onAttchToken方法
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
// 创建连接会话控制
requestClientSessionLocked(mCurClient);
}
}
}
}
void requestClientSessionLocked(ClientState cs) {
if (!cs.sessionRequested) {
if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
cs.sessionRequested = true;
//回调IMS的createSession方法
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
MSG_CREATE_SESSION, mCurMethod, channels[1],
new MethodCallback(this, mCurMethod, channels[0])));
}
}
void onSessionCreated(IInputMethod method, IInputMethodSession session,
InputChannel channel) {
synchronized (mMethodMap) {
if (mCurMethod != null && method != null
&& mCurMethod.asBinder() == method.asBinder()) {
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
mCurClient.curSession = new SessionState(mCurClient,
method, session, channel);
InputBindResult res = attachNewInputLocked(
InputMethodClient.START_INPUT_REASON_SESSION_CREATED_BY_IME, true);
if (res.method != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
MSG_BIND_CLIENT, mCurClient.client, res));
}
return;
}
}
}
// Session abandoned. Close its associated input channel.
channel.dispose();
}
InputBindResult attachNewInputLocked(boolean initial) {
if (!mBoundToMethod) {
//回调IMS的onBindInput方法
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
mBoundToMethod = true;
}
final SessionState session = mCurClient.curSession;
if (initial) {
//回调IMS的onStartInput方法
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, "Attach new input asks to show input");
showCurrentInputLocked(getAppShowFlags(), null);
}
return new InputBindResult(session.session,
(session.channel != null ? session.channel.dup() : null),
mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
}
下面是完整的时序图
绑定输入法的过程在IMMS部分到此结束,整个过程最重要的工作是mCurMethod会持有IInputMethod接口的远程代理对象的引用和mCurClient的curSession会持有封装了IInputSesssion接口的远程代理对象的引用, 这里出现了之前提到的两个非常重要的接口IInputMethod和IInputSession