zoukankan      html  css  js  c++  java
  • Android 9.0 默认输入法的设置流程分析


    Android 输入法设置文章

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

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


     

    前言

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

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

    设置流程分析

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

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

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

     

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

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

    点击查看代码

    void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
        if (DEBUG) {
            Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
                    + " 
     ------ caller=" + Debug.getCallers(10));
        }
        if (!mSystemReady) {
            Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
            return;
        }
        mMethodList.clear();
        mMethodMap.clear();
        mMethodMapUpdateCount++;
        mMyPackageMonitor.clearKnownImePackageNamesLocked();
    
    
        // 第一阶段
        // Use for queryIntentServicesAsUser
        final PackageManager pm = mContext.getPackageManager();
    
        // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default
        // behavior of PackageManager is exactly what we want.  It by default picks up appropriate
        // services depending on the unlock state for the specified user.
        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
                new Intent(InputMethod.SERVICE_INTERFACE),
                getComponentMatchingFlags(PackageManager.GET_META_DATA
                        | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS),
                mSettings.getCurrentUserId());
    
        final HashMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
                mFileManager.getAllAdditionalInputMethodSubtypes();
        for (int i = 0; i < services.size(); ++i) {
            ResolveInfo ri = services.get(i);
            ServiceInfo si = ri.serviceInfo;
            final String imeId = InputMethodInfo.computeId(ri);
            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
                Slog.w(TAG, "Skipping input method " + imeId
                        + ": it does not require the permission "
                        + android.Manifest.permission.BIND_INPUT_METHOD);
                continue;
            }
    
            if (DEBUG) Slog.d(TAG, "Checking " + imeId);
    
            final List<InputMethodSubtype> additionalSubtypes = additionalSubtypeMap.get(imeId);
            try {
                InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
                mMethodList.add(p);
                final String id = p.getId();
                mMethodMap.put(id, p);
    
                if (DEBUG) {
                    Slog.d(TAG, "Found an input method " + p);
                }
            } catch (Exception e) {
                Slog.wtf(TAG, "Unable to load input method " + imeId, e);
            }
        }
        // Construct the set of possible IME packages for onPackageChanged() to avoid false
        // negatives when the package state remains to be the same but only the component state is
        // changed.
        {
            // Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose
            // of this query is to avoid false negatives.  PackageManager.MATCH_ALL could be more
            // conservative, but it seems we cannot use it for now (Issue 35176630).
            final List<ResolveInfo> allInputMethodServices = pm.queryIntentServicesAsUser(
                    new Intent(InputMethod.SERVICE_INTERFACE),
                    getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS),
                    mSettings.getCurrentUserId());
            final int N = allInputMethodServices.size();
            for (int i = 0; i < N; ++i) {
                final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
                if (android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
                    mMyPackageMonitor.addKnownImePackageNameLocked(si.packageName);
                }
            }
        }
    
        //第二阶段
        boolean reenableMinimumNonAuxSystemImes = false;
        if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
            final ArrayList<InputMethodInfo> defaultEnabledIme =
                    InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList,
                            reenableMinimumNonAuxSystemImes);
            final int N = defaultEnabledIme.size();
            for (int i = 0; i < N; ++i) {
                final InputMethodInfo imi =  defaultEnabledIme.get(i);
                if (DEBUG) {
                    Slog.d(TAG, "--- enable ime = " + imi);
                }
                setInputMethodEnabledLocked(imi.getId(), true);
            }
        }
    
    }

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

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

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

     

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

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

    点击查看代码

        boolean setInputMethodEnabledLocked(String id, boolean enabled) {
            // Make sure this is a valid input method.
            InputMethodInfo imm = mMethodMap.get(id);
            if (imm == null) {
                throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
            }
    
            List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
                    .getEnabledInputMethodsAndSubtypeListLocked();
    
            if (enabled) {
                for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
                    if (pair.first.equals(id)) {
                        // We are enabling this input method, but it is already enabled.
                        // Nothing to do. The previous state was enabled.
                        return true;
                    }
                }
                mSettings.appendAndPutEnabledInputMethodLocked(id, false);
                // Previous state was disabled.
                return false;
            } else {
                StringBuilder builder = new StringBuilder();
                if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
                        builder, enabledInputMethodsList, id)) {
                    // Disabled input method is currently selected, switch to another one.
                    final String selId = mSettings.getSelectedInputMethod();
                    if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
                        Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
                        resetSelectedInputMethodAndSubtypeLocked("");
                    }
                    // Previous state was enabled.
                    return true;
                } else {
                    // We are disabling the input method but it is already disabled.
                    // Nothing to do.  The previous state was disabled.
                    return false;
                }
            }
        }

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

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

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

     

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

     

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

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

    7. 继续走到setSelectedInputMethodAndSubtypeLocked方法中

    点击查看代码

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

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

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

     

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

    总结

    简单讲就是:
    开机启动(安装image后第一次开机)时,当ActivityManager Ready后,回调InputMethodManagerService::systemRunning()方法,此时透过PMS去检索系统中安装的input_methods,并将检索的结果设置存储在Setting数据库中enabled_input_methods这个key-value下,然后在这个enabled_input_methods列表中选择一个system IME 作为default, 并将值写入Settings数据库中的default_input_method这个key-value下

    如上,理解不当之处再请不吝赐教

    心有猛虎,细嗅蔷薇,生活就该无惧无悔
    ----------------------------------------------------------------------------
    作者:二的次方
    本文版权归作者和博客园共有,转载必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利
  • 相关阅读:
    201671010105 2016-2017-2《Java程序设计》第十四周学习心得
    201671010105 2016-2017-2《Java程序设计》第十三周学习心得
    201671010105 2016-2017-2《Java程序设计》第十二周学习心得
    201671010105 2016-2017-2《Java程序设计》第十一周学习心得
    201671010105 2016-2017-2《Java程序设计》第10周学习心得
    201671010105 2016-2017-2《Java程序设计》第九周学习心得
    201671010105 2016-2017-2《Java程序设计》第7周学习心得
    201671010105 2016-2017-2《Java程序设计》第六周学习心得
    201671010105 2016-2017-2《Java程序设计》第五周学习心得
    201671010103 2016-2017-2 《Java程序设计》学习心得
  • 原文地址:https://www.cnblogs.com/roger-yu/p/15179507.html
Copyright © 2011-2022 走看看