美文网首页Android OS
Android输入法IMMS服务启动流程(2)(systemRu

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

作者: 古风子 | 来源:发表于2019-10-23 21:31 被阅读0次

    基于Android 9.x

    目录

       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;
    

    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输入法服务的时候,执行了那些操作

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

    相关文章

      网友评论

        本文标题:Android输入法IMMS服务启动流程(2)(systemRu

        本文链接:https://www.haomeiwen.com/subject/ehrlvctx.html