Android 9.0 默认输入法的设置流程分析

时间:2021-10-22 15:55:44

Android 输入法设置文章

  Android 9.0 默认输入法的设置流程分析

  Android 9.0 添加预置第三方输入法/设置默认输入法(软键盘)

前言

在上一篇文章  Android 9.0 添加预置第三方输入法/设置默认输入法(软键盘)    中我们可以通过设置enabled_input_methods和default_input_method两个key-value的值来显示的指定可选的输入法及默认输入法。

但是,查看Android原生代码,并没任何地方显示的设置这两个值,但是当开机后,我们去console先查看,这两个值却被设置为了google原生输入法,那这两个值是在哪里设置的呢?本篇将简单介绍

Android 9.0 默认输入法的设置流程分析

设置流程分析

1.  Android系统开机后,当ActivityManagerService及PackageManagerService都ready后,systemserver会回调到InputMethodManagerService::systemRunning()方法http://aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java#1508

2. systemRunning()方法中会去设置一些初始参数,并依次调用buildInputMethodListLocked和resetDefaultImeLocked

public void systemRunning(StatusBarManagerService statusBar) {
synchronized (mMethodMap) {
....
final String defaultImiId = mSettings.getSelectedInputMethod(); // 获取默认输入法,第一次开机时应该是空
final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);// 传递参数resetDefaultEnabledIme=true
resetDefaultImeLocked(mContext);
updateFromSettingsLocked(true);
....
}
}

3. 接下来我们来看一下buildInputMethodListLocked方法,部分源码如下:

http://aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java#3616

点击查看代码

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.
final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
getComponentMatchingFlags(PackageManager.GET_META_DATA
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS),
mSettings.getCurrentUserId()); 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;
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 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);
}
}
} //第二阶段
boolean reenableMinimumNonAuxSystemImes = false;
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);
}
} }

把代码处理流程大概分两个阶段:

第一阶段:透过PackageManager去检索已安装的输入法app,构建一个List:mMethodList

第二阶段:将上一步骤中检索的的输入法做enable ime处理,此时调用到了setInputMethodEnabledLocked(imi.getId(), true)

4.  再来看看setInputMethodEnabledLocked的内容:这个方法比较简单,调用mSettings.appendAndPutEnabledInputMethodLocked(id, false)去做设置

http://aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java#3987

点击查看代码

    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
// Make sure this is a valid input method.
InputMethodInfo imm = mMethodMap.get(id);
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)) {
// We are enabling this input method, but it is already enabled.
// Nothing to do. The previous state was enabled.
return true;
}
}
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;
}
}
}

5. 流程就走到了InputMethodUtils::putEnabledInputMethodStr,将值写入Settings数据库 putString(Settings.Secure.ENABLED_INPUT_METHODS, str);

http://aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/core/java/com/android/internal/inputmethod/InputMethodUtils.java#1052

http://aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/core/java/com/android/internal/inputmethod/InputMethodUtils.java#1108

分析到这里Settings数据库中enabled_input_methods这个key-value就有了默认值了,一般是“com.android.inputmethod.latin/.LatinIME”

6. 接着分析,buildInputMethodListLocked()完成后,返回到systemRunning()中继续调用到resetDefaultImeLocked()

    private void resetDefaultImeLocked(Context context) {
// 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<InputMethodInfo> 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());
}
setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
}

7. 继续走到setSelectedInputMethodAndSubtypeLocked方法中

点击查看代码

    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
boolean setSubtypeOnly) {
// Updates to InputMethod are transient in VR mode. Its not included in history.
final boolean isVrInput = imi != null && imi.isVrOnly();
if (!isVrInput) {
// Update the history of InputMethod and Subtype
mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
} mCurUserActionNotificationSequenceNumber =
Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
if (DEBUG) {
Slog.d(TAG, "Bump mCurUserActionNotificationSequenceNumber:"
+ mCurUserActionNotificationSequenceNumber);
} if (mCurClient != null && mCurClient.client != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
mCurUserActionNotificationSequenceNumber, mCurClient));
} if (isVrInput) {
// Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped.
return;
} // Set Subtype here
if (imi == null || subtypeId < 0) {
mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
mCurrentSubtype = null;
} else {
if (subtypeId < imi.getSubtypeCount()) {
InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
mSettings.putSelectedSubtype(subtype.hashCode());
mCurrentSubtype = subtype;
} else {
mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
// If the subtype is not specified, choose the most applicable one
mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
}
} if (!setSubtypeOnly) {
// Set InputMethod here
mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
}
}

8. mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "") ==> putSelectedInputMethod==>putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId)
最终将值写入Settings数据库中的default_input_method

http://aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/core/java/com/android/internal/inputmethod/InputMethodUtils.java#1315

至此default_input_method这个key-value也有了默认值