目录
1 启动输入法服务流程图]
2 代码分析
2.1 startInputInnerLocked]
2.2 bindCurrentInputMethodServiceLocked]
2.3 setAdditionalInputMethodSubtypes]
2.4 buildInputMethodListLocked]
3 setInputMethodEnabledLocked]
4 总结:
接上一篇,IMMS设置当前默认输入法为LatinIME输入法后,调用onCreate方法,然后会调用startInputInnerLocked启动输入法LatinIME的服务
本章节主要分析,onCreate生命周期中,各个流程调用
启动输入法服务流程图
代码分析
startInputInnerLocked
InputBindResult startInputInnerLocked() {
if (mCurMethodId == null) {//还没有制定默认输入法
return InputBindResult.NO_IME;
}
if (!mSystemReady) {
// If the system is not yet ready, we shouldn't be running third
// party code.
return new InputBindResult(
InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
null, null, mCurMethodId, mCurSeq,
mCurUserActionNotificationSequenceNumber);
}
InputMethodInfo info = mMethodMap.get(mCurMethodId);//设置的默认输入法,不在可用输入法列表中
if (info == null) {//抛出unkonwn异常
throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
}
unbindCurrentMethodLocked(true);//断开上一次连接的输入法服务
//获取当前默认的输入法的Intent信息
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 (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
mLastBindTime = SystemClock.uptimeMillis();
mHaveConnection = true;
mCurId = info.getId();//当前默认输入法对应的id
mCurToken = new Binder();//当前默认输入法对应的token
try {
if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
mIWindowManager.addWindowToken(mCurToken, TYPE_INPUT_METHOD, DEFAULT_DISPLAY);
} catch (RemoteException e) {
}
return new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
null, null, mCurId, mCurSeq,
mCurUserActionNotificationSequenceNumber);
}
mCurIntent = null;
Slog.w(TAG, "Failure connecting to input method service: " + mCurIntent);
return InputBindResult.IME_NOT_CONNECTED;
}
bindCurrentInputMethodServiceLocked
@GuardedBy("mMethodMap")
private boolean bindCurrentInputMethodServiceLocked(
Intent service, ServiceConnection conn, int flags) {
if (service == null || conn == null) {
Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
return false;
}
if (mBindInstantServiceAllowed) {
flags |= Context.BIND_ALLOW_INSTANT;
}
return mContext.bindServiceAsUser(service, conn, flags,
new UserHandle(mSettings.getCurrentUserId()));
}
bindServiceAsUser启动的服务是LatinIME
//来自源码packages\inputmethods\LatinIME\java
<service android:name="LatinIME"
android:label="@string/english_ime_name"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method" />
</service>
如上图流程图,服务启动后,会调用到InputMethodManagerService的setAdditionalInputMethodSubtypes方法
setAdditionalInputMethodSubtypes
@Override
public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
if (!calledFromValidUser()) {
return;
}
// By this IPC call, only a process which shares the same uid with the IME can add
// additional input method subtypes to the IME.
if (TextUtils.isEmpty(imiId) || subtypes == null) return;
synchronized (mMethodMap) {
if (!mSystemReady) {
return;
}
final InputMethodInfo imi = mMethodMap.get(imiId);
if (imi == null) return;
final String[] packageInfos;
try {
packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get package infos");
return;
}
if (packageInfos != null) {
final int packageNum = packageInfos.length;
for (int i = 0; i < packageNum; ++i) {
if (packageInfos[i].equals(imi.getPackageName())) {
mFileManager.addInputMethodSubtypes(imi, subtypes);
final long ident = Binder.clearCallingIdentity();
try {
//重新调用该方法,去设置默认的输入
buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
}
return;
}
}
}
}
return;
}
buildInputMethodListLocked
@GuardedBy("mMethodMap")
void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
if (DEBUG) {
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ " \n ------ caller=" + Debug.getCallers(10));
}
if (!mSystemReady) {
Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
return;
}
mMethodList.clear();
mMethodMap.clear();
mMethodMapUpdateCount++;
mMyPackageMonitor.clearKnownImePackageNamesLocked();
// Use for queryIntentServicesAsUser
final PackageManager pm = mContext.getPackageManager();
// Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default
// behavior of PackageManager is exactly what we want. It by default picks up appropriate
// services depending on the unlock state for the specified user.
//查询系统中的<SERVICE_INTERFACE>输入法服务信息,因为当前PMS未完全加载,只能查询到系统应用中声明了SERVICE_INTERFACE的
//输入法LatinIME的服务
final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
getComponentMatchingFlags(PackageManager.GET_META_DATA
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS),
mSettings.getCurrentUserId());
Slog.d(TAG, "queryIntentServicesAsUser services:" + services);
final HashMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
mFileManager.getAllAdditionalInputMethodSubtypes();
for (int i = 0; i < services.size(); ++i) {
ResolveInfo ri = services.get(i);
ServiceInfo si = ri.serviceInfo;
Slog.d(TAG, " ServiceInfo si :" + si);
final String imeId = InputMethodInfo.computeId(ri);
if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
Slog.w(TAG, "Skipping input method " + imeId
+ ": it does not require the permission "
+ android.Manifest.permission.BIND_INPUT_METHOD);
continue;
}
if (DEBUG) Slog.d(TAG, "Checking " + imeId);
final List<InputMethodSubtype> additionalSubtypes = additionalSubtypeMap.get(imeId);
try {
//创建InputMethodInfo对象,添加到对象mMethodList和mMethodMap中
InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
mMethodList.add(p);
final String id = p.getId();
mMethodMap.put(id, p);
if (DEBUG) {
Slog.d(TAG, "Found an input method " + p);
}
} catch (Exception e) {
Slog.wtf(TAG, "Unable to load input method " + imeId, e);
}
}
// Construct the set of possible IME packages for onPackageChanged() to avoid false
// negatives when the package state remains to be the same but only the component state is
// changed.
{
// Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose
// of this query is to avoid false negatives. PackageManager.MATCH_ALL could be more
// conservative, but it seems we cannot use it for now (Issue 35176630).
final List<ResolveInfo> allInputMethodServices = pm.queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS),
mSettings.getCurrentUserId());
final int N = allInputMethodServices.size();
for (int i = 0; i < N; ++i) {
final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
if (android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
//监听输入法应用的包变化广播(安装,卸载,更新)
mMyPackageMonitor.addKnownImePackageNameLocked(si.packageName);
}
}
}
Slog.d(TAG, "mMethodList: " + mMethodList);
Slog.d(TAG, "mMethodList: " + mMethodList);
boolean reenableMinimumNonAuxSystemImes = false;
// TODO: The following code should find better place to live.
if (!resetDefaultEnabledIme) {//是否需要重新设置输入法resetDefaultEnabledIme = false
boolean enabledImeFound = false;
boolean enabledNonAuxImeFound = false;
final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
final int N = enabledImes.size();
for (int i = 0; i < N; ++i) {
final InputMethodInfo imi = enabledImes.get(i);
if (mMethodList.contains(imi)) {//mMethodList只有一个LatinIME
enabledImeFound = true;
//是否是辅助输入法,isAuxiliaryIme默认为false。该方法的返回值是在初始化输入法的app,解析输入法服务的时候得到的
if (!imi.isAuxiliaryIme()) {
enabledNonAuxImeFound = true;
break;
}
}
}
if (!enabledImeFound) {
if (DEBUG) {
Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
}
resetDefaultEnabledIme = true;
resetSelectedInputMethodAndSubtypeLocked("");
} else if (!enabledNonAuxImeFound) {
if (DEBUG) {
Slog.i(TAG, "All the enabled non-Aux IMEs are gone. Do partial reset.");
}
reenableMinimumNonAuxSystemImes = true;
}
}
//条件不满足,该代码区域不执行
if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
final ArrayList<InputMethodInfo> defaultEnabledIme =
InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList,
reenableMinimumNonAuxSystemImes);
final int N = defaultEnabledIme.size();
for (int i = 0; i < N; ++i) {
final InputMethodInfo imi = defaultEnabledIme.get(i);
if (DEBUG) {
Slog.d(TAG, "--- enable ime = " + imi);
}
setInputMethodEnabledLocked(imi.getId(), true);
}
}
//因为systemrunning过程的执行,此时defaultImiId是LatinIME
final String defaultImiId = mSettings.getSelectedInputMethod();
if (!TextUtils.isEmpty(defaultImiId)) {
//很明显,mMethodMap已经包含了LatinIME输入法,因此不会执行重新选择和设定输入法的操作
if (!mMethodMap.containsKey(defaultImiId)) {
Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
//会重新设置LatinIME为默认的输入法,更新mSetting对象中各个默认输入法相关的属性
if (chooseNewDefaultIMELocked()) {
updateInputMethodsFromSettingsLocked(true);//enable各个输入法被禁用的组件
}
} else {
// Double check that the default IME is certainly enabled.
//将默认的输入法应用添加到可用输入法列表中去
setInputMethodEnabledLocked(defaultImiId, true);
}
}
// Here is not the perfect place to reset the switching controller. Ideally
// mSwitchingController and mSettings should be able to share the same state.
// TODO: Make sure that mSwitchingController and mSettings are sharing the
// the same enabled IMEs list.
mSwitchingController.resetCircularListLocked(mContext);
}
该流程的主要逻辑:
完成对mMethodList和mMethodMap的数据初始化;检查当前默认的输入法(LatinIME)服务是否存在,很明显,经过systemrunning过程以后,
mMethodMap已经包含了LatinIME,因此不会重复执行选择和设定另外输入法为默认输入法的操作
setInputMethodEnabledLocked
boolean setInputMethodEnabledLocked(String id, boolean enabled) {
// Make sure this is a valid input method.
//获取当前id对应的输入法信息
InputMethodInfo imm = mMethodMap.get(id);//不为null
if (imm == null) {
throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
}
//获取系统中所有可用的输入法信息
List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
.getEnabledInputMethodsAndSubtypeListLocked();
if (enabled) {
for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
if (pair.first.equals(id)) {//如果,可用列表中已经包含要设置的id,则直接返回
// We are enabling this input method, but it is already enabled.
// Nothing to do. The previous state was enabled.
return true;
}
}
//将id append到默认可用输入法列表中去
mSettings.appendAndPutEnabledInputMethodLocked(id, false);
// Previous state was disabled.
return false;
} else {
StringBuilder builder = new StringBuilder();
if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
builder, enabledInputMethodsList, id)) {
// Disabled input method is currently selected, switch to another one.
final String selId = mSettings.getSelectedInputMethod();
if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
resetSelectedInputMethodAndSubtypeLocked("");
}
// Previous state was enabled.
return true;
} else {
// We are disabling the input method but it is already disabled.
// Nothing to do. The previous state was disabled.
return false;
}
}
}
该方法主要是检查默认的LatinIME是否是可用的可用的输入法,如果不可用,则设置为可用;
对应的settingprovider字段为:
adb shell settings get secure enabled_input_methods
com.android.inputmethod.latin/.LatinIME:com.sohu.inputmethod.sogou/.SogouIME:com.iflytek.inputmethod/.FlyIME
总结:
经过以上流程后,默认输入法依然为LatinIME输入法,该流程的主要作用为,将启动的输入法应用更新到可用列表enabled_input_methods中;
不过,目前默认的输入法依然为LatinIME输入法,跟重启手机前,我们设置的输入法(搜狗输入法)依然不一致
下一篇文章,我们研究下,输入法启动过程中的onBind和onServiceConnected流程