Android OSAndroid

Android输入法IMMS服务启动流程(3)(启动IMS应用)

2019-10-31  本文已影响0人  古风子

目录

1 启动输入法服务流程图]
2 代码分析
    2.1 startInputInnerLocked]
    2.2 bindCurrentInputMethodServiceLocked]
    2.3 setAdditionalInputMethodSubtypes]
    2.4 buildInputMethodListLocked]
3 setInputMethodEnabledLocked]
4 总结:

接上一篇,IMMS设置当前默认输入法为LatinIME输入法后,调用onCreate方法,然后会调用startInputInnerLocked启动输入法LatinIME的服务

本章节主要分析,onCreate生命周期中,各个流程调用

启动输入法服务流程图

启动输入法app服务.png

代码分析

startInputInnerLocked

    InputBindResult startInputInnerLocked() {
        
        if (mCurMethodId == null) {//还没有制定默认输入法
            return InputBindResult.NO_IME;
        }

        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);
        }

        InputMethodInfo info = mMethodMap.get(mCurMethodId);//设置的默认输入法,不在可用输入法列表中
        if (info == null) {//抛出unkonwn异常
            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
        }

        unbindCurrentMethodLocked(true);//断开上一次连接的输入法服务

        //获取当前默认的输入法的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));
        //启动当前默认的输入法服务
        if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
            mLastBindTime = SystemClock.uptimeMillis();
            mHaveConnection = true;
            mCurId = info.getId();//当前默认输入法对应的id
            mCurToken = new Binder();//当前默认输入法对应的token
            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;
    }

bindCurrentInputMethodServiceLocked

   @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()));
    }

bindServiceAsUser启动的服务是LatinIME

       //来自源码packages\inputmethods\LatinIME\java
        <service android:name="LatinIME"
                android:label="@string/english_ime_name"
                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>

如上图流程图,服务启动后,会调用到InputMethodManagerService的setAdditionalInputMethodSubtypes方法

setAdditionalInputMethodSubtypes

    @Override
    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
        if (!calledFromValidUser()) {
            return;
        }
        // By this IPC call, only a process which shares the same uid with the IME can add
        // additional input method subtypes to the IME.
        if (TextUtils.isEmpty(imiId) || subtypes == null) return;
        synchronized (mMethodMap) {
            if (!mSystemReady) {
                return;
            }
            final InputMethodInfo imi = mMethodMap.get(imiId);
            if (imi == null) return;
            final String[] packageInfos;
            try {
                packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to get package infos");
                return;
            }
            if (packageInfos != null) {
                final int packageNum = packageInfos.length;
                for (int i = 0; i < packageNum; ++i) {
                    if (packageInfos[i].equals(imi.getPackageName())) {
                        mFileManager.addInputMethodSubtypes(imi, subtypes);
                        final long ident = Binder.clearCallingIdentity();
                        try {
                            //重新调用该方法,去设置默认的输入
                            buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
                        } finally {
                            Binder.restoreCallingIdentity(ident);
                        }
                        return;
                    }
                }
            }
        }
        return;
    }

buildInputMethodListLocked

    @GuardedBy("mMethodMap")
    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的服务
        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
                new Intent(InputMethod.SERVICE_INTERFACE),
                getComponentMatchingFlags(PackageManager.GET_META_DATA
                        | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS),
                mSettings.getCurrentUserId());
        Slog.d(TAG, "queryIntentServicesAsUser services:" + services);
        final HashMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
                mFileManager.getAllAdditionalInputMethodSubtypes();
        for (int i = 0; i < services.size(); ++i) {
            ResolveInfo ri = services.get(i);
            ServiceInfo si = ri.serviceInfo;
            Slog.d(TAG, " ServiceInfo si :" + si);
            final String imeId = InputMethodInfo.computeId(ri);
            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
                Slog.w(TAG, "Skipping input method " + imeId
                        + ": it does not require the permission "
                        + android.Manifest.permission.BIND_INPUT_METHOD);
                continue;
            }

            if (DEBUG) Slog.d(TAG, "Checking " + imeId);

            final List<InputMethodSubtype> additionalSubtypes = additionalSubtypeMap.get(imeId);
            try {
                //创建InputMethodInfo对象,添加到对象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);
                }
            }
        }
        Slog.d(TAG, "mMethodList: " + mMethodList);
        Slog.d(TAG, "mMethodList: " + mMethodList);

        boolean reenableMinimumNonAuxSystemImes = false;
        // TODO: The following code should find better place to live.
        if (!resetDefaultEnabledIme) {//是否需要重新设置输入法resetDefaultEnabledIme = false
            boolean enabledImeFound = false;
            boolean enabledNonAuxImeFound = false;
            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
                    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) {
            final ArrayList<InputMethodInfo> defaultEnabledIme =
                    InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList,
                            reenableMinimumNonAuxSystemImes);
            final int N = defaultEnabledIme.size();
            for (int i = 0; i < N; ++i) {
                final InputMethodInfo imi =  defaultEnabledIme.get(i);
                if (DEBUG) {
                    Slog.d(TAG, "--- enable ime = " + imi);
                }
                setInputMethodEnabledLocked(imi.getId(), true);
            }
        }
        //因为systemrunning过程的执行,此时defaultImiId是LatinIME
        final String defaultImiId = mSettings.getSelectedInputMethod();

        if (!TextUtils.isEmpty(defaultImiId)) {
            //很明显,mMethodMap已经包含了LatinIME输入法,因此不会执行重新选择和设定输入法的操作
            if (!mMethodMap.containsKey(defaultImiId)) {
                Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
                //会重新设置LatinIME为默认的输入法,更新mSetting对象中各个默认输入法相关的属性
                if (chooseNewDefaultIMELocked()) {
                    updateInputMethodsFromSettingsLocked(true);//enable各个输入法被禁用的组件
                }
            } 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的数据初始化;检查当前默认的输入法(LatinIME)服务是否存在,很明显,经过systemrunning过程以后,
mMethodMap已经包含了LatinIME,因此不会重复执行选择和设定另外输入法为默认输入法的操作

setInputMethodEnabledLocked

    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
        // Make sure this is a valid input method.
         //获取当前id对应的输入法信息
        InputMethodInfo imm = mMethodMap.get(id);//不为null
        if (imm == null) {
            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
        }
        //获取系统中所有可用的输入法信息
        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
                .getEnabledInputMethodsAndSubtypeListLocked();

        if (enabled) {
            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
                if (pair.first.equals(id)) {//如果,可用列表中已经包含要设置的id,则直接返回
                    // We are enabling this input method, but it is already enabled.
                    // Nothing to do. The previous state was enabled.
                    return true;
                }
            }
            //将id append到默认可用输入法列表中去
            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
            // Previous state was disabled.
            return false;
        } else {
            StringBuilder builder = new StringBuilder();
            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
                    builder, enabledInputMethodsList, id)) {
                // Disabled input method is currently selected, switch to another one.
                final String selId = mSettings.getSelectedInputMethod();
                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
                    resetSelectedInputMethodAndSubtypeLocked("");
                }
                // Previous state was enabled.
                return true;
            } else {
                // We are disabling the input method but it is already disabled.
                // Nothing to do.  The previous state was disabled.
                return false;
            }
        }
    }

该方法主要是检查默认的LatinIME是否是可用的可用的输入法,如果不可用,则设置为可用;
对应的settingprovider字段为:


adb shell settings get secure enabled_input_methods
com.android.inputmethod.latin/.LatinIME:com.sohu.inputmethod.sogou/.SogouIME:com.iflytek.inputmethod/.FlyIME

总结:

经过以上流程后,默认输入法依然为LatinIME输入法,该流程的主要作用为,将启动的输入法应用更新到可用列表enabled_input_methods中;
不过,目前默认的输入法依然为LatinIME输入法,跟重启手机前,我们设置的输入法(搜狗输入法)依然不一致

下一篇文章,我们研究下,输入法启动过程中的onBind和onServiceConnected流程

上一篇下一篇

猜你喜欢

热点阅读