Android 輸入法設定文章

  Android 9.0 預設輸入法的設定流程分析

  Android 9.0 新增預置第三方輸入法/設定預設輸入法(軟鍵盤)

前言

在上一篇文章  Android 9.0 新增預置第三方輸入法/設定預設輸入法(軟鍵盤)    中我們可以通過設定enabled_input_methods和default_input_method兩個key-value的值來顯示的指定可選的輸入法及預設輸入法。

但是,檢視Android原生程式碼,並沒任何地方顯示的設定這兩個值,但是當開機後,我們去console先檢視,這兩個值卻被設定為了google原生輸入法,那這兩個值是在哪裡設定的呢?本篇將簡單介紹

設定流程分析

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

  1. public void systemRunning(StatusBarManagerService statusBar) {
  2. synchronized (mMethodMap) {
  3. ....
  4. final String defaultImiId = mSettings.getSelectedInputMethod(); // 獲取預設輸入法,第一次開機時應該是空
  5. final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
  6. buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);// 傳遞引數resetDefaultEnabledIme=true
  7. resetDefaultImeLocked(mContext);
  8. updateFromSettingsLocked(true);
  9. ....
  10. }
  11. }

3. 接下來我們來看一下buildInputMethodListLocked方法,部分原始碼如下:

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

點選檢視程式碼

  1. void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
  2. if (DEBUG) {
  3. Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
  4. + " \n ------ caller=" + Debug.getCallers(10));
  5. }
  6. if (!mSystemReady) {
  7. Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
  8. return;
  9. }
  10. mMethodList.clear();
  11. mMethodMap.clear();
  12. mMethodMapUpdateCount++;
  13. mMyPackageMonitor.clearKnownImePackageNamesLocked();
  14. // 第一階段
  15. // Use for queryIntentServicesAsUser
  16. final PackageManager pm = mContext.getPackageManager();
  17. // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default
  18. // behavior of PackageManager is exactly what we want. It by default picks up appropriate
  19. // services depending on the unlock state for the specified user.
  20. final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
  21. new Intent(InputMethod.SERVICE_INTERFACE),
  22. getComponentMatchingFlags(PackageManager.GET_META_DATA
  23. | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS),
  24. mSettings.getCurrentUserId());
  25. final HashMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
  26. mFileManager.getAllAdditionalInputMethodSubtypes();
  27. for (int i = 0; i < services.size(); ++i) {
  28. ResolveInfo ri = services.get(i);
  29. ServiceInfo si = ri.serviceInfo;
  30. final String imeId = InputMethodInfo.computeId(ri);
  31. if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
  32. Slog.w(TAG, "Skipping input method " + imeId
  33. + ": it does not require the permission "
  34. + android.Manifest.permission.BIND_INPUT_METHOD);
  35. continue;
  36. }
  37. if (DEBUG) Slog.d(TAG, "Checking " + imeId);
  38. final List<InputMethodSubtype> additionalSubtypes = additionalSubtypeMap.get(imeId);
  39. try {
  40. InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
  41. mMethodList.add(p);
  42. final String id = p.getId();
  43. mMethodMap.put(id, p);
  44. if (DEBUG) {
  45. Slog.d(TAG, "Found an input method " + p);
  46. }
  47. } catch (Exception e) {
  48. Slog.wtf(TAG, "Unable to load input method " + imeId, e);
  49. }
  50. }
  51. // Construct the set of possible IME packages for onPackageChanged() to avoid false
  52. // negatives when the package state remains to be the same but only the component state is
  53. // changed.
  54. {
  55. // Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose
  56. // of this query is to avoid false negatives. PackageManager.MATCH_ALL could be more
  57. // conservative, but it seems we cannot use it for now (Issue 35176630).
  58. final List<ResolveInfo> allInputMethodServices = pm.queryIntentServicesAsUser(
  59. new Intent(InputMethod.SERVICE_INTERFACE),
  60. getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS),
  61. mSettings.getCurrentUserId());
  62. final int N = allInputMethodServices.size();
  63. for (int i = 0; i < N; ++i) {
  64. final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
  65. if (android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
  66. mMyPackageMonitor.addKnownImePackageNameLocked(si.packageName);
  67. }
  68. }
  69. }
  70. //第二階段
  71. boolean reenableMinimumNonAuxSystemImes = false;
  72. if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
  73. final ArrayList<InputMethodInfo> defaultEnabledIme =
  74. InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList,
  75. reenableMinimumNonAuxSystemImes);
  76. final int N = defaultEnabledIme.size();
  77. for (int i = 0; i < N; ++i) {
  78. final InputMethodInfo imi = defaultEnabledIme.get(i);
  79. if (DEBUG) {
  80. Slog.d(TAG, "--- enable ime = " + imi);
  81. }
  82. setInputMethodEnabledLocked(imi.getId(), true);
  83. }
  84. }
  85. }

把程式碼處理流程大概分兩個階段:

第一階段:透過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

點選檢視程式碼

  1. boolean setInputMethodEnabledLocked(String id, boolean enabled) {
  2. // Make sure this is a valid input method.
  3. InputMethodInfo imm = mMethodMap.get(id);
  4. if (imm == null) {
  5. throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
  6. }
  7. List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
  8. .getEnabledInputMethodsAndSubtypeListLocked();
  9. if (enabled) {
  10. for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
  11. if (pair.first.equals(id)) {
  12. // We are enabling this input method, but it is already enabled.
  13. // Nothing to do. The previous state was enabled.
  14. return true;
  15. }
  16. }
  17. mSettings.appendAndPutEnabledInputMethodLocked(id, false);
  18. // Previous state was disabled.
  19. return false;
  20. } else {
  21. StringBuilder builder = new StringBuilder();
  22. if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
  23. builder, enabledInputMethodsList, id)) {
  24. // Disabled input method is currently selected, switch to another one.
  25. final String selId = mSettings.getSelectedInputMethod();
  26. if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
  27. Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
  28. resetSelectedInputMethodAndSubtypeLocked("");
  29. }
  30. // Previous state was enabled.
  31. return true;
  32. } else {
  33. // We are disabling the input method but it is already disabled.
  34. // Nothing to do. The previous state was disabled.
  35. return false;
  36. }
  37. }
  38. }

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()

  1. private void resetDefaultImeLocked(Context context) {
  2. // Do not reset the default (current) IME when it is a 3rd-party IME
  3. if (mCurMethodId != null && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
  4. return;
  5. }
  6. final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
  7. context, mSettings.getEnabledInputMethodListLocked());
  8. if (suitableImes.isEmpty()) {
  9. Slog.i(TAG, "No default found");
  10. return;
  11. }
  12. final InputMethodInfo defIm = suitableImes.get(0);
  13. if (DEBUG) {
  14. Slog.i(TAG, "Default found, using " + defIm.getId());
  15. }
  16. setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
  17. }

7. 繼續走到setSelectedInputMethodAndSubtypeLocked方法中

點選檢視程式碼

  1. private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
  2. boolean setSubtypeOnly) {
  3. // Updates to InputMethod are transient in VR mode. Its not included in history.
  4. final boolean isVrInput = imi != null && imi.isVrOnly();
  5. if (!isVrInput) {
  6. // Update the history of InputMethod and Subtype
  7. mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
  8. }
  9. mCurUserActionNotificationSequenceNumber =
  10. Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
  11. if (DEBUG) {
  12. Slog.d(TAG, "Bump mCurUserActionNotificationSequenceNumber:"
  13. + mCurUserActionNotificationSequenceNumber);
  14. }
  15. if (mCurClient != null && mCurClient.client != null) {
  16. executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
  17. MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
  18. mCurUserActionNotificationSequenceNumber, mCurClient));
  19. }
  20. if (isVrInput) {
  21. // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped.
  22. return;
  23. }
  24. // Set Subtype here
  25. if (imi == null || subtypeId < 0) {
  26. mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
  27. mCurrentSubtype = null;
  28. } else {
  29. if (subtypeId < imi.getSubtypeCount()) {
  30. InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
  31. mSettings.putSelectedSubtype(subtype.hashCode());
  32. mCurrentSubtype = subtype;
  33. } else {
  34. mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
  35. // If the subtype is not specified, choose the most applicable one
  36. mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
  37. }
  38. }
  39. if (!setSubtypeOnly) {
  40. // Set InputMethod here
  41. mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
  42. }
  43. }

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也有了預設值