Android输入法IMMS服务启动流程(2)(systemRunning)

基于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 总结

关于手机启动过程,输入法相关流程,分析的前提条件是:

  1. 设置了默认的输入法为搜狗输入法(com.sohu.inputmethod.sogou/.SogouIME)
  2. 默认的系统应用输入法为拉丁输入法(com.android.inputmethod.latin/.LatinIME)
  3. 系统中已有的输入法应用为
com.iflytek.inputmethod/.FlyIME
com.sohu.inputmethod.sogou/.SogouIME
com.android.inputmethod.latin/.LatinIME
com.qihoo.appstore/com.qihoo360.mobilesafe.pcinput.HandoffIme

系统启动时,输入法初始化流程图

输入法_服务启动流程.png

以下分析将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.
        //查询系统中的<SERVICE_INTERFACE>输入法服务信息,因为当前PMS未完全加载,只能查询到系统应用中声明了SERVICE_INTERFACE的
        //输入法LatinIME的服务
        //SERVICE_INTERFACE = android.view.InputMethod
        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();
        //将查询到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<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);
                }
            }
        }

        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<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,因此返回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<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);
                }
                //将选择的可用输入法,添加到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数据
  • 查询系统中的<android.view.InputMethod>输入法服务信息,因为当前PMS未完全加载,只能查询到系统应用中声明了SERVICE_INTERFACE的
    输入法LatinIME的服务;将查询的信息存入到mMethodList,mMethodMap,mMyPackageMonitor中
        <service
            android:name="SecuritySoftKeyboard"
            android:exported="false"
            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>
  • resetDefaultEnabledIme代码段,查询系统中可用的输入法信息;getEnabledInputMethodsAndSubtypeListLocked获取的是
    enabled_input_methods的信息,但如上分析,mMethodMap中只有latin输入法的信息,因此getEnabledInputMethodListLocked返回的数据为空
λ adb shell settings get secure enabled_input_methods
com.sohu.inputmethod.sogou/.SogouIME
        public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
            return createEnabledInputMethodListLocked(
                    getEnabledInputMethodsAndSubtypeListLocked());
        }
        private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
                List<Pair<String, ArrayList<String>>> imsList) {
            //imsList获取的enabled_input_methods的信息
            Slog.d(TAG, "createEnabledInputMethodListLocked : ");
            final ArrayList<InputMethodInfo> res = new ArrayList<>();
            for (Pair<String, ArrayList<String>> 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<InputMethodInfo> 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<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
            for (int i=0; i<enabled.size(); i++) {            
                InputMethodInfo imm = enabled.get(i);
                try {
                    ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
                            mSettings.getCurrentUserId());
                    //系统中没有输入法的组件被禁用,因此该代码不会执行
                    if (ai != null && ai.enabledSetting
                            == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
                        if (DEBUG) {
                            Slog.d(TAG, "Update state(" + imm.getId()
                                    + "): DISABLED_UNTIL_USED -> 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<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());
        }
        //设置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

<string-array name="config_disabledUntilUsedPreinstalledImes" translatable="false">
        <item>com.android.inputmethod.latin</item>
</string-array>

原生源码配置的config_disabledUntilUsedPreinstalledImes为拉丁输入法

  InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                        mIPackageManager,
                        mSettings.getEnabledInputMethodListLocked(), currentUserId,
                        mContext.getBasePackageName());


  public static void setNonSelectedSystemImesDisabledUntilUsed(
            IPackageManager packageManager, List<InputMethodInfo> 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,并启动对应的输入应用服务

总结

综上,系统启动过程,输入法服务的主要流程为:

  1. systemserver start boot阶段,调用输入法第一次初始化的过程

  2. 当mSystemReady没有准备好时,该过程不执行

  3. 注册监听package的变化,主要处理当默认输入法的应用变化时,及时选择其他默认输入法为默认输入法

  4. 注册监听当前用户的各种输入法相关的settingprovider变化,例如:默认输入法,输入法列表,输入法语言等

  5. buildInputMethodListLocked
    完成对mMethodList和mMethodMap的数据初始化;检查当前默认的输入法(搜狗)服务是否存在,不存在,重新设置默认的输入法(LatinIME);输入法设置成功后,发送输入法变化广播(ACTION_INPUT_METHOD_CHANGED)

  6. resetDefaultImeLocked
    当从settingprovider得到的默认输入法为非第三方输入法时,则从可用的系统输入法中,选在第一个为默认输入法

  7. updateFromSettingsLocked
    将输入法应用中被DISABLED的应用,从新enabled;设置设置软键盘和物理键盘是否共存

  8. setNonSelectedSystemImesDisabledUntilUsed

  • 对系统配置的输入法,检查其是否在系统可用输入法列表中
  • 如果可用,并且没有配置android:enabled = false ,则设置该输入法应用为:COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED

以上流程执行完毕后,系统中默认的输入法为LatinIME,跟重启手机前的输入法(搜狗输入法)并不是一致的。
下一章,我们看下,启动LatinIME输入法服务的时候,执行了那些操作

基于Android 9.x

关于手机启动过程,输入法相关流程,分析的前提条件是:

  1. 设置了默认的输入法为搜狗输入法(com.sohu.inputmethod.sogou/.SogouIME)
  2. 默认的系统应用输入法为拉丁输入法(com.android.inputmethod.latin/.LatinIME)
  3. 系统中已有的输入法应用为
com.iflytek.inputmethod/.FlyIME
com.sohu.inputmethod.sogou/.SogouIME
com.android.inputmethod.latin/.LatinIME
com.qihoo.appstore/com.qihoo360.mobilesafe.pcinput.HandoffIme

系统启动时,输入法初始化流程图

输入法-1_服务启动流程.png

以下分析将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.
        //查询系统中的<SERVICE_INTERFACE>输入法服务信息,因为当前PMS未完全加载,只能查询到系统应用中声明了SERVICE_INTERFACE的
        //输入法LatinIME的服务
        //SERVICE_INTERFACE = android.view.InputMethod
        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();
        //将查询到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<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);
                }
            }
        }

        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<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,因此返回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<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);
                }
                //将选择的可用输入法,添加到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数据
  • 查询系统中的<android.view.InputMethod>输入法服务信息,因为当前PMS未完全加载,只能查询到系统应用中声明了SERVICE_INTERFACE的
    输入法LatinIME的服务;将查询的信息存入到mMethodList,mMethodMap,mMyPackageMonitor中
        <service
            android:name="SecuritySoftKeyboard"
            android:exported="false"
            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>
  • resetDefaultEnabledIme代码段,查询系统中可用的输入法信息;getEnabledInputMethodsAndSubtypeListLocked获取的是
    enabled_input_methods的信息,但如上分析,mMethodMap中只有latin输入法的信息,因此getEnabledInputMethodListLocked返回的数据为空
λ adb shell settings get secure enabled_input_methods
com.sohu.inputmethod.sogou/.SogouIME
        public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
            return createEnabledInputMethodListLocked(
                    getEnabledInputMethodsAndSubtypeListLocked());
        }
        private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
                List<Pair<String, ArrayList<String>>> imsList) {
            //imsList获取的enabled_input_methods的信息
            Slog.d(TAG, "createEnabledInputMethodListLocked : ");
            final ArrayList<InputMethodInfo> res = new ArrayList<>();
            for (Pair<String, ArrayList<String>> 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<InputMethodInfo> 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<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
            for (int i=0; i<enabled.size(); i++) {            
                InputMethodInfo imm = enabled.get(i);
                try {
                    ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
                            mSettings.getCurrentUserId());
                    //系统中没有输入法的组件被禁用,因此该代码不会执行
                    if (ai != null && ai.enabledSetting
                            == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
                        if (DEBUG) {
                            Slog.d(TAG, "Update state(" + imm.getId()
                                    + "): DISABLED_UNTIL_USED -> 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<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());
        }
        //设置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

<string-array name="config_disabledUntilUsedPreinstalledImes" translatable="false">
        <item>com.android.inputmethod.latin</item>
</string-array>

原生源码配置的config_disabledUntilUsedPreinstalledImes为拉丁输入法

  InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
                        mIPackageManager,
                        mSettings.getEnabledInputMethodListLocked(), currentUserId,
                        mContext.getBasePackageName());


  public static void setNonSelectedSystemImesDisabledUntilUsed(
            IPackageManager packageManager, List<InputMethodInfo> 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,并启动对应的输入应用服务

总结

综上,系统启动过程,输入法服务的主要流程为:

  1. systemserver start boot阶段,调用输入法第一次初始化的过程

  2. 当mSystemReady没有准备好时,该过程不执行

  3. 注册监听package的变化,主要处理当默认输入法的应用变化时,及时选择其他默认输入法为默认输入法

  4. 注册监听当前用户的各种输入法相关的settingprovider变化,例如:默认输入法,输入法列表,输入法语言等

  5. buildInputMethodListLocked
    完成对mMethodList和mMethodMap的数据初始化;检查当前默认的输入法(搜狗)服务是否存在,不存在,重新设置默认的输入法(LatinIME);输入法设置成功后,发送输入法变化广播(ACTION_INPUT_METHOD_CHANGED)

  6. resetDefaultImeLocked
    当从settingprovider得到的默认输入法为非第三方输入法时,则从可用的系统输入法中,选在第一个为默认输入法

  7. updateFromSettingsLocked
    将输入法应用中被DISABLED的应用,从新enabled;设置设置软键盘和物理键盘是否共存

  8. setNonSelectedSystemImesDisabledUntilUsed

  • 对系统配置的输入法,检查其是否在系统可用输入法列表中
  • 如果可用,并且没有配置android:enabled = false ,则设置该输入法应用为:COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED

以上流程执行完毕后,系统中默认的输入法为LatinIME,跟重启手机前的输入法(搜狗输入法)并不是一致的。
下一章,我们看下,启动LatinIME输入法服务的时候,执行了那些操作


输入法_服务启动流程.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,513评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,312评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,124评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,529评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,937评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,913评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,084评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,816评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,593评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,788评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,267评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,601评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,265评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,158评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,953评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,066评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,852评论 2 277