基于Android 9.x
1. 目录
1 systemRunning
1.1 InputMethodSettings.switchCurrentUser
1.2 InputMethodSettings.getSelectedInputMethod
1.3 IMMS.buildInputMethodListLocked
1.3.1 chooseNewDefaultIMELocked
1.3.2 updateInputMethodsFromSettingsLocked
1.3.2.1 setInputMethodLocked
1.4 resetDefaultImeLocked
1.5 updateFromSettingsLocked
1.6 setNonSelectedSystemImesDisabledUntilUsed
1.7 startInputInnerLocked
2 总结
关于手机启动过程,输入法相关流程,分析的前提条件是:
- 设置了默认的输入法为搜狗输入法(com.sohu.inputmethod.sogou/.SogouIME)
- 默认的系统应用输入法为拉丁输入法(com.android.inputmethod.latin/.LatinIME)
- 系统中已有的输入法应用为
com.iflytek.inputmethod/.FlyIME
com.sohu.inputmethod.sogou/.SogouIME
com.android.inputmethod.latin/.LatinIME
com.qihoo.appstore/com.qihoo360.mobilesafe.pcinput.HandoffIme
系统启动时,输入法初始化流程图
以下分析将InputMethodManagerService简称为IMMS
涉及的源码
fameworks\base\services\core\java\com\android\server\InputMethodManagerService.java
frameworks\base\core\java\com\android\internal\inputmethod\InputMethodUtils;
frameworks\base\core\java\com\android\internal\inputmethod\InputMethodUtils.InputMethodSettings;
2. systemRunning
public void systemRunning(StatusBarManagerService statusBar) {
synchronized (mMethodMap) {
if (!mSystemReady) {//system没有准备好的情况下,直接退出
mSystemReady = true;
mLastSystemLocales = mRes.getConfiguration().getLocales();
final int currentUserId = mSettings.getCurrentUserId();
//因为当前user并不处于unlockingOrUnlocked状态,因此,第二个参数为true
mSettings.switchCurrentUser(currentUserId,!mUserManager.isUserUnlockingOrUnlocked(currentUserId));
mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
mNotificationManager = mContext.getSystemService(NotificationManager.class);
mStatusBar = statusBar;
if (mStatusBar != null) {
mStatusBar.setIconVisibility(mSlotIme, false);
}
updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
com.android.internal.R.bool.show_ongoing_ime_switcher);
if (mShowOngoingImeSwitcherForPhones) {
mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(
mHardKeyboardListener);
}
//监听安装包的变化,包含安装,卸载,更新等
mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
//监听当前用户的各种输入法相关的settingprovider变化,例如:默认输入法,输入法列表,输入法语言等
mSettingsObserver.registerContentObserverLocked(currentUserId);
final IntentFilter broadcastFilter = new IntentFilter();
broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
broadcastFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
broadcastFilter.addAction(ACTION_SHOW_INPUT_METHOD_PICKER);
mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
//获取用户设置的com.sohu.inputmethod.sogou/.SogouIME输入法
final String defaultImiId = mSettings.getSelectedInputMethod();
//true
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
Slog.d(TAG, "imeSelectedOnBoot :"+imeSelectedOnBoot+"---"+defaultImiId);
//初始化mMethodList,mMethodMap等对象
buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
resetDefaultImeLocked(mContext);
updateFromSettingsLocked(true);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
mSettings.getEnabledInputMethodListLocked(), currentUserId,
mContext.getBasePackageName());
try {
startInputInnerLocked();
} catch (RuntimeException e) {
Slog.w(TAG, "Unexpected exception", e);
}
}
}
}
3. InputMethodSettings.switchCurrentUser
public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId+" copyOnWrite:"+copyOnWrite);
if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) {
mCopyOnWriteDataStore.clear();
mEnabledInputMethodsStrCache = "";
// TODO: mCurrentProfileIds should be cleared here.
}
mCurrentUserId = userId;
mCopyOnWrite = copyOnWrite;
// TODO: mCurrentProfileIds should be updated here.
}
copyOnWrite为true,那么通过mSettings去保存默认输入法id时,只是保存在mSettings的一个本地变量中,并不会保存在SettingProvider中,
因为此时SettingProvider此时还没有准备好
mSettings获取默认输入法也是一样逻辑,copyOnWrite为true,则从本地获取;为false,则从settingprovider获取
相关代码逻辑如下
4. InputMethodSettings.getSelectedInputMethod
@Nullable
public String getSelectedInputMethod() {
final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
if (DEBUG) {
Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
}
return imi;
}
@Nullable
private String getString(@NonNull final String key, @Nullable final String defaultValue) {
final String result;
Slog.d(TAG, "--- getString " + key + " :: " + defaultValue+" mCopyOnWrite:"+mCopyOnWrite+"--mCopyOnWriteDataStore:"+mCopyOnWriteDataStore);
if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
result = mCopyOnWriteDataStore.get(key);
} else {
result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
}
return result != null ? result : defaultValue;
}
打印的日志为
InputMethodUtils: --- getString default_input_method :: null mCopyOnWrite:true--mCopyOnWriteDataStore:{}
因为此时,user还未进入unlocking or unlocked,因此,mCopyOnWrite为true,因为是首次启动,mCopyOnWriteDataStore中还未有
数据,因此,最总还是从Settings中查询到用户设置的默认输入法:搜狗输入法
5. IMMS.buildInputMethodListLocked
//因为用户已经设置了默认的搜狗输入法,因此resetDefaultEnabledIme为false
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.
//查询系统中的输入法服务信息,因为当前PMS未完全加载,只能查询到系统应用中声明了SERVICE_INTERFACE的
//输入法LatinIME的服务
//SERVICE_INTERFACE = android.view.InputMethod
final List services = pm.queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
getComponentMatchingFlags(PackageManager.GET_META_DATA
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS),
mSettings.getCurrentUserId());
//获取各个输入法支持的语言类型
final HashMap> additionalSubtypeMap =
mFileManager.getAllAdditionalInputMethodSubtypes();
//将查询到LatinIME输入法的相关信息,放到mMethodList,mMethodMap等对象中
//此时只有LatinIME输入法
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);
//过滤客户端服务,没有生命BIND_INPUT_METHOD的输入法
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;
}
//只有LatinIME
if (DEBUG) Slog.d(TAG, "Checking " + imeId);
final List 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 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);
}
}
}
boolean reenableMinimumNonAuxSystemImes = false;
// TODO: The following code should find better place to live.
if (!resetDefaultEnabledIme) {//是否需要重新设置输入法resetDefaultEnabledIme = false
boolean enabledImeFound = false;
boolean enabledNonAuxImeFound = false;
//获取secure enabled_input_methods的数据,并在mMethodMap中查询是否存在;enabled_input_methods实在设置中管理键盘界面,开关打开的输入法
//这里测试的时候,使能了搜狗和讯飞输入法,因此得到的数据为:
//com.android.inputmethod.latin/.LatinIME:com.sohu.inputmethod.sogou/.SogouIME:com.iflytek.inputmethod/.FlyIME
//这里不存在,因为mMethodList中只有latin输入法
final List 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,因此返回true
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) {//初始化时,条件不满足,代码不执行
//从mMethodList中按照一定规则,选择一个可用的输入法;mMethodList目前只有latin输入法
final ArrayList 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);
}
//将选择的可用输入法,添加到enabled_input_methods中去,不过,因为目前处于mCopyOnWrite=true状态,只是存储到本地变量中
setInputMethodEnabledLocked(imi.getId(), true);
}
}
//获取default_input_method默认的输入法数据
//因为resetSelectedInputMethodAndSubtypeLocked设置selected为空,因此这里得到空的数据
final String defaultImiId = mSettings.getSelectedInputMethod();
Slog.w(TAG, "-------defaultImiId:"+defaultImiId);
Slog.w(TAG, "-------mMethodMap:"+mMethodMap);//map中只有LatinIME输入法
if (!TextUtils.isEmpty(defaultImiId)) {//默认输入法不为空
if (!mMethodMap.containsKey(defaultImiId)) {//map中没有包含默认的输入法
Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
if (chooseNewDefaultIMELocked()) {
updateInputMethodsFromSettingsLocked(true);//
}
} 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,mMyPackageMonitor数据
- 查询系统中的
输入法服务信息,因为当前PMS未完全加载,只能查询到系统应用中声明了SERVICE_INTERFACE的
输入法LatinIME的服务;将查询的信息存入到mMethodList,mMethodMap,mMyPackageMonitor中
- resetDefaultEnabledIme代码段,查询系统中可用的输入法信息;getEnabledInputMethodsAndSubtypeListLocked获取的是
enabled_input_methods的信息,但如上分析,mMethodMap中只有latin输入法的信息,因此getEnabledInputMethodListLocked返回的数据为空
λ adb shell settings get secure enabled_input_methods
com.sohu.inputmethod.sogou/.SogouIME
public ArrayList getEnabledInputMethodListLocked() {
return createEnabledInputMethodListLocked(
getEnabledInputMethodsAndSubtypeListLocked());
}
private ArrayList createEnabledInputMethodListLocked(
List>> imsList) {
//imsList获取的enabled_input_methods的信息
Slog.d(TAG, "createEnabledInputMethodListLocked : ");
final ArrayList res = new ArrayList<>();
for (Pair> ims: imsList) {
//mMethodMap中只有latin输入法的信息,因此找不到
InputMethodInfo info = mMethodMap.get(ims.first);
Slog.d(TAG, "info : "+info);
if (info != null && !info.isVrOnly()) {
res.add(info);
}
}
Slog.d(TAG, "return res : "+res);
return res;
}
6. chooseNewDefaultIMELocked
选择最合适的输入法设置为默认的输入法,优先级为
是系统输入法,且支持中文 > 是系统输入法 >可用输入法列表的第一个输入法
private boolean chooseNewDefaultIMELocked() {
final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
mSettings.getEnabledInputMethodListLocked());
if (imi != null) {
if (DEBUG) {
Slog.d(TAG, "New default IME was selected: " + imi.getId());
}
//选择LatinIME输入法为默认输入法,因为此时mCopyOnWrite为true,因此默认值是保存在InputMethodUtils中的本地变量中的
resetSelectedInputMethodAndSubtypeLocked(imi.getId());
return true;
}
return false;
}
//选择最合适的默认输入法,
public static InputMethodInfo getMostApplicableDefaultIME(List enabledImes) {
if (enabledImes == null || enabledImes.isEmpty()) {
return null;
}
// We'd prefer to fall back on a system IME, since that is safer.
int i = enabledImes.size();
int firstFoundSystemIme = -1;
while (i > 0) {
i--;
final InputMethodInfo imi = enabledImes.get(i);
if (imi.isAuxiliaryIme()) {
continue;
}
//如果是系统输入法,并且支持应为,则直接返回
if (InputMethodUtils.isSystemIme(imi)
&& containsSubtypeOf(imi, ENGLISH_LOCALE, false /* checkCountry */,
SUBTYPE_MODE_KEYBOARD)) {
return imi;
}
if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)) {
firstFoundSystemIme = i;
}
}
return enabledImes.get(Math.max(firstFoundSystemIme, 0));
}
主要逻辑:
选择一个新的输入法为默认的输入法
7. updateInputMethodsFromSettingsLocked
void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
if (enabledMayChange) {
//遍历系统中可用的输入法程序,搜狗,讯飞,latin
List enabled = mSettings.getEnabledInputMethodListLocked();
for (int i=0; i DEFAULT");
}
mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
mContext.getBasePackageName());
}
} catch (RemoteException e) {
}
}
}
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
// enabled.
//此时的默认输入法已经被设置成了LatinIME
String id = mSettings.getSelectedInputMethod();
// There is no input method selected, try to choose new applicable input method.
//如果获取的默认输入法为空,则尝试从可用输入法列表中,再寻找一个更合适的
if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
id = mSettings.getSelectedInputMethod();
}
if (!TextUtils.isEmpty(id)) {
try {
//设置传入的输入法id为默认的输入法,为mCurMethodId赋值为id;发送ACTION_INPUT_METHOD_CHANGED广播
setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_IME_FAILED);
}
mShortcutInputMethodsAndSubtypes.clear();
} else {
// There is no longer an input method set, so stop any current one.
resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_NO_IME);
}
// 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);
}
该方法主要的完成的工作:
遍历系统的所有的输入法,如果输入法存在被禁用的组件,则重新启用
调用setInputMethodLocked方法完成对输入法设置,和输入法发生变化的广播(ACTION_INPUT_METHOD_CHANGED)的发送
8. setInputMethodLocked
//subtypeId跟输入法一一对应
/* package */ void setInputMethodLocked(String id, int subtypeId) {
Slog.w(TAG, "setInputMethodLocked = " + id + "---" + Log.getStackTraceString(new Throwable()));
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + id);
}
// See if we need to notify a subtype change within the same IME.
//如果该次设置的输入法跟上次的一样,检查subtype是否改变,并退出当前设置流程
if (id.equals(mCurMethodId)) {
final int subtypeCount = info.getSubtypeCount();
if (subtypeCount <= 0) {
return;
}
final InputMethodSubtype oldSubtype = mCurrentSubtype;
final InputMethodSubtype newSubtype;
if (subtypeId >= 0 && subtypeId < subtypeCount) {
newSubtype = info.getSubtypeAt(subtypeId);
} else {
// If subtype is null, try to find the most applicable one from
// getCurrentInputMethodSubtype.
newSubtype = getCurrentInputMethodSubtypeLocked();
}
if (newSubtype == null || oldSubtype == null) {
Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
+ ", new subtype = " + newSubtype);
return;
}
if (newSubtype != oldSubtype) {
setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
if (mCurMethod != null) {
try {
updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
mCurMethod.changeInputMethodSubtype(newSubtype);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call changeInputMethodSubtype");
}
}
}
//退出当前设置流程
return;
}
// Changing to a different IME.
final long ident = Binder.clearCallingIdentity();
try {
// Set a subtype to this input method.
// subtypeId the name of a subtype which will be set.
//设置默认输入法为info,并设置subtype
setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
// mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
// because mCurMethodId is stored as a history in
// setSelectedInputMethodAndSubtypeLocked().
mCurMethodId = id;
if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("input_method_id", id);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
//to do 解绑上次绑定的输入法的客户端
unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_IME);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
该方法主要完成的工作:
为属性mCurMethodId赋值,未赋值前mCurMethodId为null;并发送ACTION_INPUT_METHOD_CHANGED广播
9. resetDefaultImeLocked
private void resetDefaultImeLocked(Context context) {
//当第三方输入法,为默认输入法时,直接返回;根据上文逻辑,mCurMethodId为LatinIME,是系统输入法键盘
// Do not reset the default (current) IME when it is a 3rd-party IME
if (mCurMethodId != null && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
return;
}
//遍历系统中可用的输入法(即在设置中开关是打开的输入法应用),根据一定的规则选择默认的输入法,主要依据是支持的语言
final List suitableImes = InputMethodUtils.getDefaultEnabledImes(
context, mSettings.getEnabledInputMethodListLocked());
if (suitableImes.isEmpty()) {
Slog.i(TAG, "No default found");
return;
}
//
final InputMethodInfo defIm = suitableImes.get(0);
if (DEBUG) {
Slog.i(TAG, "Default found, using " + defIm.getId());
}
//设置latin为默认的输入法
setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
}
当系统中系统中默认输入法为系统输入法时,则选择一个更合适的;
目的应该是当系统中存在多个系统输入法时,并且当前默认不是拉丁输入法,则选择拉丁输入法为默认输入法
10. updateFromSettingsLocked
void updateFromSettingsLocked(boolean enabledMayChange) {
//将输入法应用中被DISABLED的应用,从新enabled。当enabledMayChange为true时,该逻辑才会执行
updateInputMethodsFromSettingsLocked(enabledMayChange);
//设置软件盘和物理键盘是否共存
updateKeyboardFromSettingsLocked();
}
public void updateKeyboardFromSettingsLocked() {
//从settingprovider读取,软键盘和物理键盘是否共存的值
mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
if (mSwitchingDialog != null
&& mSwitchingDialogTitleView != null
&& mSwitchingDialog.isShowing()) {
final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById(
com.android.internal.R.id.hard_keyboard_switch);
hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
}
}
主要工作:
- 将输入法应用中被DISABLED的应用,从新enabled。当enabledMayChange为true时,该逻辑才会执行
- 设置软件盘和物理键盘是否共存
11. setNonSelectedSystemImesDisabledUntilUsed
- com.android.inputmethod.latin
原生源码配置的config_disabledUntilUsedPreinstalledImes为拉丁输入法
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
mIPackageManager,
mSettings.getEnabledInputMethodListLocked(), currentUserId,
mContext.getBasePackageName());
public static void setNonSelectedSystemImesDisabledUntilUsed(
IPackageManager packageManager, List enabledImis,
int userId, String callingPackage) {
if (DEBUG) {
Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
}
//得到系统默认配置的disabledUntilUsedPreinstalledImes名单,原生默认为com.android.inputmethod.latin
final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
return;
}
// Only the current spell checker should be treated as an enabled one.
final SpellCheckerInfo currentSpellChecker =
TextServicesManager.getInstance().getCurrentSpellChecker();
for (final String packageName : systemImesDisabledUntilUsed) {
if (DEBUG) {
Slog.d(TAG, "check " + packageName);
}
boolean enabledIme = false;
//在enable中遍历latin输入法,看是否存在于enable 列表中,很明显,存在,
for (int j = 0; j < enabledImis.size(); ++j) {
final InputMethodInfo imi = enabledImis.get(j);
if (packageName.equals(imi.getPackageName())) {
enabledIme = true;
break;
}
}
if (enabledIme) {//为ture,下面的代码不执行了
// enabled ime. skip
continue;
}
if (currentSpellChecker != null
&& packageName.equals(currentSpellChecker.getPackageName())) {
// enabled spell checker. skip
if (DEBUG) {
Slog.d(TAG, packageName + " is the current spell checker. skip");
}
continue;
}
ApplicationInfo ai = null;
try {
ai = packageManager.getApplicationInfo(packageName,
PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
} catch (RemoteException e) {
Slog.w(TAG, "getApplicationInfo failed. packageName=" + packageName
+ " userId=" + userId, e);
continue;
}
if (ai == null) {
// No app found for packageName
continue;
}
final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
if (!isSystemPackage) {
continue;
}
//如果,系统配置的disabledUntilUsedPreinstalledImes输入法发,非enable,则设置
setDisabledUntilUsed(packageManager, packageName, userId, callingPackage);
}
}
private static void setDisabledUntilUsed(IPackageManager packageManager, String packageName,
int userId, String callingPackage) {
final int state;
try {
state = packageManager.getApplicationEnabledSetting(packageName, userId);
} catch (RemoteException e) {
Slog.w(TAG, "getApplicationEnabledSetting failed. packageName=" + packageName
+ " userId=" + userId, e);
return;
}
//如果支持其他应用调用改组件,则设置该输入法组件为COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
if (DEBUG) {
Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
}
try {
packageManager.setApplicationEnabledSetting(packageName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
0 /* newState */, userId, callingPackage);
} catch (RemoteException e) {
Slog.w(TAG, "setApplicationEnabledSetting failed. packageName=" + packageName
+ " userId=" + userId + " callingPackage=" + callingPackage, e);
return;
}
} else {
if (DEBUG) {
Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
}
}
}
该流程的主要逻辑为:
1:对系统配置的输入法,检查其是否在系统可用输入法列表中
2:如果可用,并且没有配置android:enabled = false ,则设置该输入法应用为:COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
android:enabled用来定义:是否支持其它应用调用当前组件
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
Int 值为0 ,指在manifest中没有显示声明
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
Int 值为1 ,指在manifest中声明为 android:enabled=”true”
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
Int 值为2 ,指在manifest中声明为 android:enabled=”false”
12. startInputInnerLocked
InputBindResult startInputInnerLocked() {
if (mCurMethodId == null) {
return InputBindResult.NO_IME;
}
//system not ready
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);
}
//获取目前默认的输入法::LatinIME
InputMethodInfo info = mMethodMap.get(mCurMethodId);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
}
//解绑上一次默认的输入法,unbindService对应的服务
unbindCurrentMethodLocked(true);
//设置mCurMethodId对应的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));
//绑定mCurMethodId对应的输入法,启动mCurIntent对应的输入法服务
if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
mLastBindTime = SystemClock.uptimeMillis();
mHaveConnection = true;
mCurId = info.getId();
mCurToken = new Binder();
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;
}
@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()));
}
主要流程:根据mCurMethodId生成对应的intent,并启动对应的输入应用服务
总结
综上,系统启动过程,输入法服务的主要流程为:
systemserver start boot阶段,调用输入法第一次初始化的过程
当mSystemReady没有准备好时,该过程不执行
注册监听package的变化,主要处理当默认输入法的应用变化时,及时选择其他默认输入法为默认输入法
注册监听当前用户的各种输入法相关的settingprovider变化,例如:默认输入法,输入法列表,输入法语言等
buildInputMethodListLocked
完成对mMethodList和mMethodMap的数据初始化;检查当前默认的输入法(搜狗)服务是否存在,不存在,重新设置默认的输入法(LatinIME);输入法设置成功后,发送输入法变化广播(ACTION_INPUT_METHOD_CHANGED)resetDefaultImeLocked
当从settingprovider得到的默认输入法为非第三方输入法时,则从可用的系统输入法中,选在第一个为默认输入法updateFromSettingsLocked
将输入法应用中被DISABLED的应用,从新enabled;设置设置软键盘和物理键盘是否共存setNonSelectedSystemImesDisabledUntilUsed
- 对系统配置的输入法,检查其是否在系统可用输入法列表中
- 如果可用,并且没有配置android:enabled = false ,则设置该输入法应用为:COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
以上流程执行完毕后,系统中默认的输入法为LatinIME,跟重启手机前的输入法(搜狗输入法)并不是一致的。
下一章,我们看下,启动LatinIME输入法服务的时候,执行了那些操作
基于Android 9.x
关于手机启动过程,输入法相关流程,分析的前提条件是:
- 设置了默认的输入法为搜狗输入法(com.sohu.inputmethod.sogou/.SogouIME)
- 默认的系统应用输入法为拉丁输入法(com.android.inputmethod.latin/.LatinIME)
- 系统中已有的输入法应用为
com.iflytek.inputmethod/.FlyIME
com.sohu.inputmethod.sogou/.SogouIME
com.android.inputmethod.latin/.LatinIME
com.qihoo.appstore/com.qihoo360.mobilesafe.pcinput.HandoffIme
系统启动时,输入法初始化流程图
以下分析将InputMethodManagerService简称为IMMS
涉及的源码
fameworks\base\services\core\java\com\android\server\InputMethodManagerService.java
frameworks\base\core\java\com\android\internal\inputmethod\InputMethodUtils;
frameworks\base\core\java\com\android\internal\inputmethod\InputMethodUtils.InputMethodSettings;
systemRunning
public void systemRunning(StatusBarManagerService statusBar) {
synchronized (mMethodMap) {
if (!mSystemReady) {//system没有准备好的情况下,直接退出
mSystemReady = true;
mLastSystemLocales = mRes.getConfiguration().getLocales();
final int currentUserId = mSettings.getCurrentUserId();
//因为当前user并不处于unlockingOrUnlocked状态,因此,第二个参数为true
mSettings.switchCurrentUser(currentUserId,!mUserManager.isUserUnlockingOrUnlocked(currentUserId));
mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
mNotificationManager = mContext.getSystemService(NotificationManager.class);
mStatusBar = statusBar;
if (mStatusBar != null) {
mStatusBar.setIconVisibility(mSlotIme, false);
}
updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
com.android.internal.R.bool.show_ongoing_ime_switcher);
if (mShowOngoingImeSwitcherForPhones) {
mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(
mHardKeyboardListener);
}
//监听安装包的变化,包含安装,卸载,更新等
mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
//监听当前用户的各种输入法相关的settingprovider变化,例如:默认输入法,输入法列表,输入法语言等
mSettingsObserver.registerContentObserverLocked(currentUserId);
final IntentFilter broadcastFilter = new IntentFilter();
broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
broadcastFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
broadcastFilter.addAction(ACTION_SHOW_INPUT_METHOD_PICKER);
mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
//获取用户设置的com.sohu.inputmethod.sogou/.SogouIME输入法
final String defaultImiId = mSettings.getSelectedInputMethod();
//true
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
Slog.d(TAG, "imeSelectedOnBoot :"+imeSelectedOnBoot+"---"+defaultImiId);
//初始化mMethodList,mMethodMap等对象
buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
resetDefaultImeLocked(mContext);
updateFromSettingsLocked(true);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
mSettings.getEnabledInputMethodListLocked(), currentUserId,
mContext.getBasePackageName());
try {
startInputInnerLocked();
} catch (RuntimeException e) {
Slog.w(TAG, "Unexpected exception", e);
}
}
}
}
InputMethodSettings.switchCurrentUser
public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId+" copyOnWrite:"+copyOnWrite);
if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) {
mCopyOnWriteDataStore.clear();
mEnabledInputMethodsStrCache = "";
// TODO: mCurrentProfileIds should be cleared here.
}
mCurrentUserId = userId;
mCopyOnWrite = copyOnWrite;
// TODO: mCurrentProfileIds should be updated here.
}
copyOnWrite为true,那么通过mSettings去保存默认输入法id时,只是保存在mSettings的一个本地变量中,并不会保存在SettingProvider中,
因为此时SettingProvider此时还没有准备好
mSettings获取默认输入法也是一样逻辑,copyOnWrite为true,则从本地获取;为false,则从settingprovider获取
相关代码逻辑如下
InputMethodSettings.getSelectedInputMethod
@Nullable
public String getSelectedInputMethod() {
final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
if (DEBUG) {
Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
}
return imi;
}
@Nullable
private String getString(@NonNull final String key, @Nullable final String defaultValue) {
final String result;
Slog.d(TAG, "--- getString " + key + " :: " + defaultValue+" mCopyOnWrite:"+mCopyOnWrite+"--mCopyOnWriteDataStore:"+mCopyOnWriteDataStore);
if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
result = mCopyOnWriteDataStore.get(key);
} else {
result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
}
return result != null ? result : defaultValue;
}
打印的日志为
InputMethodUtils: --- getString default_input_method :: null mCopyOnWrite:true--mCopyOnWriteDataStore:{}
因为此时,user还未进入unlocking or unlocked,因此,mCopyOnWrite为true,因为是首次启动,mCopyOnWriteDataStore中还未有
数据,因此,最总还是从Settings中查询到用户设置的默认输入法:搜狗输入法
IMMS.buildInputMethodListLocked
//因为用户已经设置了默认的搜狗输入法,因此resetDefaultEnabledIme为false
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.
//查询系统中的输入法服务信息,因为当前PMS未完全加载,只能查询到系统应用中声明了SERVICE_INTERFACE的
//输入法LatinIME的服务
//SERVICE_INTERFACE = android.view.InputMethod
final List services = pm.queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
getComponentMatchingFlags(PackageManager.GET_META_DATA
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS),
mSettings.getCurrentUserId());
//获取各个输入法支持的语言类型
final HashMap> additionalSubtypeMap =
mFileManager.getAllAdditionalInputMethodSubtypes();
//将查询到LatinIME输入法的相关信息,放到mMethodList,mMethodMap等对象中
//此时只有LatinIME输入法
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);
//过滤客户端服务,没有生命BIND_INPUT_METHOD的输入法
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;
}
//只有LatinIME
if (DEBUG) Slog.d(TAG, "Checking " + imeId);
final List 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 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);
}
}
}
boolean reenableMinimumNonAuxSystemImes = false;
// TODO: The following code should find better place to live.
if (!resetDefaultEnabledIme) {//是否需要重新设置输入法resetDefaultEnabledIme = false
boolean enabledImeFound = false;
boolean enabledNonAuxImeFound = false;
//获取secure enabled_input_methods的数据,并在mMethodMap中查询是否存在;enabled_input_methods实在设置中管理键盘界面,开关打开的输入法
//这里测试的时候,使能了搜狗和讯飞输入法,因此得到的数据为:
//com.android.inputmethod.latin/.LatinIME:com.sohu.inputmethod.sogou/.SogouIME:com.iflytek.inputmethod/.FlyIME
//这里不存在,因为mMethodList中只有latin输入法
final List 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,因此返回true
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) {//初始化时,条件不满足,代码不执行
//从mMethodList中按照一定规则,选择一个可用的输入法;mMethodList目前只有latin输入法
final ArrayList 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);
}
//将选择的可用输入法,添加到enabled_input_methods中去,不过,因为目前处于mCopyOnWrite=true状态,只是存储到本地变量中
setInputMethodEnabledLocked(imi.getId(), true);
}
}
//获取default_input_method默认的输入法数据
//因为resetSelectedInputMethodAndSubtypeLocked设置selected为空,因此这里得到空的数据
final String defaultImiId = mSettings.getSelectedInputMethod();
Slog.w(TAG, "-------defaultImiId:"+defaultImiId);
Slog.w(TAG, "-------mMethodMap:"+mMethodMap);//map中只有LatinIME输入法
if (!TextUtils.isEmpty(defaultImiId)) {//默认输入法不为空
if (!mMethodMap.containsKey(defaultImiId)) {//map中没有包含默认的输入法
Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
if (chooseNewDefaultIMELocked()) {
updateInputMethodsFromSettingsLocked(true);//
}
} 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,mMyPackageMonitor数据
- 查询系统中的
输入法服务信息,因为当前PMS未完全加载,只能查询到系统应用中声明了SERVICE_INTERFACE的
输入法LatinIME的服务;将查询的信息存入到mMethodList,mMethodMap,mMyPackageMonitor中
- resetDefaultEnabledIme代码段,查询系统中可用的输入法信息;getEnabledInputMethodsAndSubtypeListLocked获取的是
enabled_input_methods的信息,但如上分析,mMethodMap中只有latin输入法的信息,因此getEnabledInputMethodListLocked返回的数据为空
λ adb shell settings get secure enabled_input_methods
com.sohu.inputmethod.sogou/.SogouIME
public ArrayList getEnabledInputMethodListLocked() {
return createEnabledInputMethodListLocked(
getEnabledInputMethodsAndSubtypeListLocked());
}
private ArrayList createEnabledInputMethodListLocked(
List>> imsList) {
//imsList获取的enabled_input_methods的信息
Slog.d(TAG, "createEnabledInputMethodListLocked : ");
final ArrayList res = new ArrayList<>();
for (Pair> ims: imsList) {
//mMethodMap中只有latin输入法的信息,因此找不到
InputMethodInfo info = mMethodMap.get(ims.first);
Slog.d(TAG, "info : "+info);
if (info != null && !info.isVrOnly()) {
res.add(info);
}
}
Slog.d(TAG, "return res : "+res);
return res;
}
chooseNewDefaultIMELocked
选择最合适的输入法设置为默认的输入法,优先级为
是系统输入法,且支持中文 > 是系统输入法 >可用输入法列表的第一个输入法
private boolean chooseNewDefaultIMELocked() {
final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
mSettings.getEnabledInputMethodListLocked());
if (imi != null) {
if (DEBUG) {
Slog.d(TAG, "New default IME was selected: " + imi.getId());
}
//选择LatinIME输入法为默认输入法,因为此时mCopyOnWrite为true,因此默认值是保存在InputMethodUtils中的本地变量中的
resetSelectedInputMethodAndSubtypeLocked(imi.getId());
return true;
}
return false;
}
//选择最合适的默认输入法,
public static InputMethodInfo getMostApplicableDefaultIME(List enabledImes) {
if (enabledImes == null || enabledImes.isEmpty()) {
return null;
}
// We'd prefer to fall back on a system IME, since that is safer.
int i = enabledImes.size();
int firstFoundSystemIme = -1;
while (i > 0) {
i--;
final InputMethodInfo imi = enabledImes.get(i);
if (imi.isAuxiliaryIme()) {
continue;
}
//如果是系统输入法,并且支持应为,则直接返回
if (InputMethodUtils.isSystemIme(imi)
&& containsSubtypeOf(imi, ENGLISH_LOCALE, false /* checkCountry */,
SUBTYPE_MODE_KEYBOARD)) {
return imi;
}
if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)) {
firstFoundSystemIme = i;
}
}
return enabledImes.get(Math.max(firstFoundSystemIme, 0));
}
主要逻辑:
选择一个新的输入法为默认的输入法
updateInputMethodsFromSettingsLocked
void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
if (enabledMayChange) {
//遍历系统中可用的输入法程序,搜狗,讯飞,latin
List enabled = mSettings.getEnabledInputMethodListLocked();
for (int i=0; i DEFAULT");
}
mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
mContext.getBasePackageName());
}
} catch (RemoteException e) {
}
}
}
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
// enabled.
//此时的默认输入法已经被设置成了LatinIME
String id = mSettings.getSelectedInputMethod();
// There is no input method selected, try to choose new applicable input method.
//如果获取的默认输入法为空,则尝试从可用输入法列表中,再寻找一个更合适的
if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
id = mSettings.getSelectedInputMethod();
}
if (!TextUtils.isEmpty(id)) {
try {
//设置传入的输入法id为默认的输入法,为mCurMethodId赋值为id;发送ACTION_INPUT_METHOD_CHANGED广播
setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_IME_FAILED);
}
mShortcutInputMethodsAndSubtypes.clear();
} else {
// There is no longer an input method set, so stop any current one.
resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_NO_IME);
}
// 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);
}
该方法主要的完成的工作:
遍历系统的所有的输入法,如果输入法存在被禁用的组件,则重新启用
调用setInputMethodLocked方法完成对输入法设置,和输入法发生变化的广播(ACTION_INPUT_METHOD_CHANGED)的发送
setInputMethodLocked
//subtypeId跟输入法一一对应
/* package */ void setInputMethodLocked(String id, int subtypeId) {
Slog.w(TAG, "setInputMethodLocked = " + id + "---" + Log.getStackTraceString(new Throwable()));
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + id);
}
// See if we need to notify a subtype change within the same IME.
//如果该次设置的输入法跟上次的一样,检查subtype是否改变,并退出当前设置流程
if (id.equals(mCurMethodId)) {
final int subtypeCount = info.getSubtypeCount();
if (subtypeCount <= 0) {
return;
}
final InputMethodSubtype oldSubtype = mCurrentSubtype;
final InputMethodSubtype newSubtype;
if (subtypeId >= 0 && subtypeId < subtypeCount) {
newSubtype = info.getSubtypeAt(subtypeId);
} else {
// If subtype is null, try to find the most applicable one from
// getCurrentInputMethodSubtype.
newSubtype = getCurrentInputMethodSubtypeLocked();
}
if (newSubtype == null || oldSubtype == null) {
Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
+ ", new subtype = " + newSubtype);
return;
}
if (newSubtype != oldSubtype) {
setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
if (mCurMethod != null) {
try {
updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
mCurMethod.changeInputMethodSubtype(newSubtype);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call changeInputMethodSubtype");
}
}
}
//退出当前设置流程
return;
}
// Changing to a different IME.
final long ident = Binder.clearCallingIdentity();
try {
// Set a subtype to this input method.
// subtypeId the name of a subtype which will be set.
//设置默认输入法为info,并设置subtype
setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
// mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
// because mCurMethodId is stored as a history in
// setSelectedInputMethodAndSubtypeLocked().
mCurMethodId = id;
if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("input_method_id", id);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
//to do 解绑上次绑定的输入法的客户端
unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_IME);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
该方法主要完成的工作:
为属性mCurMethodId赋值,未赋值前mCurMethodId为null;并发送ACTION_INPUT_METHOD_CHANGED广播
resetDefaultImeLocked
private void resetDefaultImeLocked(Context context) {
//当第三方输入法,为默认输入法时,直接返回;根据上文逻辑,mCurMethodId为LatinIME,是系统输入法键盘
// Do not reset the default (current) IME when it is a 3rd-party IME
if (mCurMethodId != null && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
return;
}
//遍历系统中可用的输入法(即在设置中开关是打开的输入法应用),根据一定的规则选择默认的输入法,主要依据是支持的语言
final List suitableImes = InputMethodUtils.getDefaultEnabledImes(
context, mSettings.getEnabledInputMethodListLocked());
if (suitableImes.isEmpty()) {
Slog.i(TAG, "No default found");
return;
}
//
final InputMethodInfo defIm = suitableImes.get(0);
if (DEBUG) {
Slog.i(TAG, "Default found, using " + defIm.getId());
}
//设置latin为默认的输入法
setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
}
当系统中系统中默认输入法为系统输入法时,则选择一个更合适的;
目的应该是当系统中存在多个系统输入法时,并且当前默认不是拉丁输入法,则选择拉丁输入法为默认输入法
updateFromSettingsLocked
void updateFromSettingsLocked(boolean enabledMayChange) {
//将输入法应用中被DISABLED的应用,从新enabled。当enabledMayChange为true时,该逻辑才会执行
updateInputMethodsFromSettingsLocked(enabledMayChange);
//设置软件盘和物理键盘是否共存
updateKeyboardFromSettingsLocked();
}
public void updateKeyboardFromSettingsLocked() {
//从settingprovider读取,软键盘和物理键盘是否共存的值
mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
if (mSwitchingDialog != null
&& mSwitchingDialogTitleView != null
&& mSwitchingDialog.isShowing()) {
final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById(
com.android.internal.R.id.hard_keyboard_switch);
hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
}
}
主要工作:
- 将输入法应用中被DISABLED的应用,从新enabled。当enabledMayChange为true时,该逻辑才会执行
- 设置软件盘和物理键盘是否共存
setNonSelectedSystemImesDisabledUntilUsed
- com.android.inputmethod.latin
原生源码配置的config_disabledUntilUsedPreinstalledImes为拉丁输入法
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
mIPackageManager,
mSettings.getEnabledInputMethodListLocked(), currentUserId,
mContext.getBasePackageName());
public static void setNonSelectedSystemImesDisabledUntilUsed(
IPackageManager packageManager, List enabledImis,
int userId, String callingPackage) {
if (DEBUG) {
Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
}
//得到系统默认配置的disabledUntilUsedPreinstalledImes名单,原生默认为com.android.inputmethod.latin
final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
return;
}
// Only the current spell checker should be treated as an enabled one.
final SpellCheckerInfo currentSpellChecker =
TextServicesManager.getInstance().getCurrentSpellChecker();
for (final String packageName : systemImesDisabledUntilUsed) {
if (DEBUG) {
Slog.d(TAG, "check " + packageName);
}
boolean enabledIme = false;
//在enable中遍历latin输入法,看是否存在于enable 列表中,很明显,存在,
for (int j = 0; j < enabledImis.size(); ++j) {
final InputMethodInfo imi = enabledImis.get(j);
if (packageName.equals(imi.getPackageName())) {
enabledIme = true;
break;
}
}
if (enabledIme) {//为ture,下面的代码不执行了
// enabled ime. skip
continue;
}
if (currentSpellChecker != null
&& packageName.equals(currentSpellChecker.getPackageName())) {
// enabled spell checker. skip
if (DEBUG) {
Slog.d(TAG, packageName + " is the current spell checker. skip");
}
continue;
}
ApplicationInfo ai = null;
try {
ai = packageManager.getApplicationInfo(packageName,
PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
} catch (RemoteException e) {
Slog.w(TAG, "getApplicationInfo failed. packageName=" + packageName
+ " userId=" + userId, e);
continue;
}
if (ai == null) {
// No app found for packageName
continue;
}
final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
if (!isSystemPackage) {
continue;
}
//如果,系统配置的disabledUntilUsedPreinstalledImes输入法发,非enable,则设置
setDisabledUntilUsed(packageManager, packageName, userId, callingPackage);
}
}
private static void setDisabledUntilUsed(IPackageManager packageManager, String packageName,
int userId, String callingPackage) {
final int state;
try {
state = packageManager.getApplicationEnabledSetting(packageName, userId);
} catch (RemoteException e) {
Slog.w(TAG, "getApplicationEnabledSetting failed. packageName=" + packageName
+ " userId=" + userId, e);
return;
}
//如果支持其他应用调用改组件,则设置该输入法组件为COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
|| state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
if (DEBUG) {
Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
}
try {
packageManager.setApplicationEnabledSetting(packageName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
0 /* newState */, userId, callingPackage);
} catch (RemoteException e) {
Slog.w(TAG, "setApplicationEnabledSetting failed. packageName=" + packageName
+ " userId=" + userId + " callingPackage=" + callingPackage, e);
return;
}
} else {
if (DEBUG) {
Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
}
}
}
该流程的主要逻辑为:
1:对系统配置的输入法,检查其是否在系统可用输入法列表中
2:如果可用,并且没有配置android:enabled = false ,则设置该输入法应用为:COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
android:enabled用来定义:是否支持其它应用调用当前组件
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
Int 值为0 ,指在manifest中没有显示声明
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
Int 值为1 ,指在manifest中声明为 android:enabled=”true”
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
Int 值为2 ,指在manifest中声明为 android:enabled=”false”
startInputInnerLocked
InputBindResult startInputInnerLocked() {
if (mCurMethodId == null) {
return InputBindResult.NO_IME;
}
//system not ready
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);
}
//获取目前默认的输入法::LatinIME
InputMethodInfo info = mMethodMap.get(mCurMethodId);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
}
//解绑上一次默认的输入法,unbindService对应的服务
unbindCurrentMethodLocked(true);
//设置mCurMethodId对应的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));
//绑定mCurMethodId对应的输入法,启动mCurIntent对应的输入法服务
if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
mLastBindTime = SystemClock.uptimeMillis();
mHaveConnection = true;
mCurId = info.getId();
mCurToken = new Binder();
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;
}
@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()));
}
主要流程:根据mCurMethodId生成对应的intent,并启动对应的输入应用服务
总结
综上,系统启动过程,输入法服务的主要流程为:
systemserver start boot阶段,调用输入法第一次初始化的过程
当mSystemReady没有准备好时,该过程不执行
注册监听package的变化,主要处理当默认输入法的应用变化时,及时选择其他默认输入法为默认输入法
注册监听当前用户的各种输入法相关的settingprovider变化,例如:默认输入法,输入法列表,输入法语言等
buildInputMethodListLocked
完成对mMethodList和mMethodMap的数据初始化;检查当前默认的输入法(搜狗)服务是否存在,不存在,重新设置默认的输入法(LatinIME);输入法设置成功后,发送输入法变化广播(ACTION_INPUT_METHOD_CHANGED)resetDefaultImeLocked
当从settingprovider得到的默认输入法为非第三方输入法时,则从可用的系统输入法中,选在第一个为默认输入法updateFromSettingsLocked
将输入法应用中被DISABLED的应用,从新enabled;设置设置软键盘和物理键盘是否共存setNonSelectedSystemImesDisabledUntilUsed
- 对系统配置的输入法,检查其是否在系统可用输入法列表中
- 如果可用,并且没有配置android:enabled = false ,则设置该输入法应用为:COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
以上流程执行完毕后,系统中默认的输入法为LatinIME,跟重启手机前的输入法(搜狗输入法)并不是一致的。
下一章,我们看下,启动LatinIME输入法服务的时候,执行了那些操作