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下

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

    心有猛虎,细嗅蔷薇,生活就该无惧无悔
    ----------------------------------------------------------------------------
    作者:二的次方
    本文版权归作者和博客园共有,转载必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利
  • 相关阅读:
    HDU Railroad (记忆化)
    HDU 1227 Fast Food
    HDU 3008 Warcraft
    asp vbscript 检测客户端浏览器和操作系统(也可以易于升级到ASP.NET)
    Csharp 讀取大文本文件數據到DataTable中,大批量插入到數據庫中
    csharp 在万年历中计算显示农历日子出错
    csharp create ICS file extension
    CSS DIV Shadow
    DataTable search keyword
    User select fontface/color/size/backgroundColor设置 字体,颜色,大小,背景色兼容主流浏览器
  • 原文地址:https://www.cnblogs.com/roger-yu/p/15179507.html
Copyright © 2011-2022 走看看