InputMethodFramework学习记录( 一)

原创 2017年01月03日 23:25:49

以下来自android官网的介绍:

Architecture Overview
There are three primary parties involved in the input method framework (IMF) architecture:

  • The input method manager as expressed by this class is the central point of the system that manages interaction between all other parts.It is expressed as the client-side API here which exists in each application context and communicates with a global system service .
  • An input method (IME) implements a particular interaction model allowing the user to generate text. The system binds to the current input method that is use, causing it to be created and run, and tells it when to hide and show its UI. Only one IME is running at a time. 
  • Multiple client applications arbitrate with the input method manager for input focus and control over the state of the IME. Only one suchclient is ever active (working with the IME) at a time.


IMF架构涉及到三个主要的部分:

  • Input Method Manager(IMM)是负责管理其他部分交互的中心,以client-side API的形式存在于每一个应用上下文中,同时和InputMethodManagerService(IMMF)进行通信。
  • IME是具体交互模型的实现,其本质是一个service,允许用户产生text文本。系统绑定当前的IME,使当前的input method创建运行,并负责通知它什么时候隐藏和显示它的UI。 同一时间只有一个IME在运行。
  • 多个客户端应用之间使用IMM对输入焦点和输入法状态的控制进行协调。同一时间只有一个客户端处于激活状态。
      综上,整个IMF是围绕着IMMS,IMM,IMS三个核心类进行工作的,这三个核心类分别运行于system进程,客户端进程以及输入法进程,他们之间的交互需要使用android跨进程通信机制-IPC。下面将分别介绍三个核心类的主要代码以及他们之间是如何进行交互的。


InputMethodManagerService


    IMMS是android的一个系统级服务,运行于system_process进程中。主要作用是管理和维护输入法列表,绑定和解绑当前输入法以及和其他系统级服务交互。

初始化

    IMMS是由SystemServer类创建的,而SystemServer类在设备启动的时候会被初始化,它的核心逻辑的入口是main方法,代码如下

    /**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }

    private void run() {
        ......
        // Start services.
        try {
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
        } catch (Throwable ex) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        }
        ......
    }

    private void startOtherServices() {
        ......
        // Bring up services needed for UI.
        if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
            try {
                Slog.i(TAG, "Input Method Service");
                //实例化了imms对象,并注册到ServiceManager里
                imm = new InputMethodManagerService(context, wm);
                ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
            } catch (Throwable e) {
                reportWtf("starting Input Manager Service", e);
            }
        }
       ......
     
        mActivityManagerService.systemReady(new Runnable() {
            @Override
            public void run() {
                ......           
                try {
                    // 调用systemRunning方法
                    if (immF != null) immF.systemRunning(statusBarF);
                } catch (Throwable e) {
                    reportWtf("Notifying InputMethodService running", e);
                }
                ......
            }
        });
    }


    IMMS的初始化过程中一共做了以下工作:

  • 创建IMF的辅助类,比如InputMethodSettings对象,InputmethodFileManager对象等
  • 创建输入法列表
  • 设置默认输入法
  • 绑定默认输入法

    public void systemRunning(StatusBarManagerService statusBar) {
        synchronized (mMethodMap) {
            if (DEBUG) {
                Slog.d(TAG, "--- systemReady");
            }
            if (!mSystemReady) {
                ......
                //创建输入法列表和设置默认输入法
                buildInputMethodListLocked(mMethodList, mMethodMap,
                        !mImeSelectedOnBoot /* resetDefaultEnabledIme */);
                if (!mImeSelectedOnBoot) {
                    Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here.");
                    resetStateIfCurrentLocaleChangedLocked();
                    InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
                            mSettings.getEnabledInputMethodListLocked(),
                            mSettings.getCurrentUserId(), mContext.getBasePackageName());
                }
                mLastSystemLocale = mRes.getConfiguration().locale;
                try {
                    //绑定默认输入法
                    startInputInnerLocked();
                } catch (RuntimeException e) {
                    Slog.w(TAG, "Unexpected exception", e);
                }
            }
        }
    }


维护和管理输入法

    在IMMS初始化过程中会创建一个ArrayList对象mMethodList和一个HashMap对象mMethodMap来保存系统内所有输入法的信息。代码如下

    void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
            HashMap<String, InputMethodInfo> map, boolean resetDefaultEnabledIme) {
        if (DEBUG) {
            Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
                    + " \n ------ \n" + InputMethodUtils.getStackTrace());
        }
        list.clear();
        map.clear();

        // Use for queryIntentServicesAsUser
        final PackageManager pm = mContext.getPackageManager();
        String disabledSysImes = mSettings.getDisabledSystemInputMethods();
        if (disabledSysImes == null) disabledSysImes = "";

        // 返回满足条件的ResolveInfo,即具有action为android.view.inputmethod的service
        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
                new Intent(InputMethod.SERVICE_INTERFACE),
                PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
                mSettings.getCurrentUserId());

        final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
                mFileManager.getAllAdditionalInputMethodSubtypes();
        for (int i = 0; i < services.size(); ++i) {
            ResolveInfo ri = services.get(i);
            ServiceInfo si = ri.serviceInfo;
            ComponentName compName = new ComponentName(si.packageName, si.name);
            //如果这个service不具有输入法权限android.permission.BIND_INPUT_METHOD,直接跳过
            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
                    si.permission)) {
                Slog.w(TAG, "Skipping input method " + compName
                        + ": it does not require the permission "
                        + android.Manifest.permission.BIND_INPUT_METHOD);
                continue;
            }

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

            try {
                //创建一个InputMethodInfo对象,并放入mMethodList和mMethodMap中,键值为compName,也是其id值.
                //InputMethodInfo的构造方法里通过解析xml,保存其id,subType,settingActivity等信息
                InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
                list.add(p);
                final String id = p.getId();
                map.put(id, p);

                if (DEBUG) {
                    Slog.d(TAG, "Found an input method " + p);
                }

            } catch (XmlPullParserException e) {
                Slog.w(TAG, "Unable to load input method " + compName, e);
            } catch (IOException e) {
                Slog.w(TAG, "Unable to load input method " + compName, e);
            }
        }

        if (resetDefaultEnabledIme) {
            final ArrayList<InputMethodInfo> defaultEnabledIme =
                    InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, list);
            for (int i = 0; i < defaultEnabledIme.size(); ++i) {
                final InputMethodInfo imi =  defaultEnabledIme.get(i);
                if (DEBUG) {
                    Slog.d(TAG, "--- enable ime = " + imi);
                }
                setInputMethodEnabledLocked(imi.getId(), true);
            }
        }

        // 设置默认的输入法
        final String defaultImiId = mSettings.getSelectedInputMethod();
        if (!TextUtils.isEmpty(defaultImiId)) {
            if (!map.containsKey(defaultImiId)) {
                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);
    }

    从创建输入法列表的过程中,就能明白为什么我们在声明一个输入法的service的时候需要添加输入法权限和能匹配对应action的Intent Filter,以及提供一个xml包含subType等信息。

<service android:name="IME"
    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>

绑定输入法


    因为解绑和绑定过程类似,所以这里先简单介绍IMMS是如何绑定IME。代码如下
   InputBindResult startInputInnerLocked() {
        if (mCurMethodId == null) {
            return mNoBinding;
        }

        if (!mSystemReady) {
            // If the system is not yet ready, we shouldn't be running third
            // party code.
            return new InputBindResult(null, null, mCurMethodId, mCurSeq,
                    mCurUserActionNotificationSequenceNumber);
        }

        InputMethodInfo info = mMethodMap.get(mCurMethodId);
        if (info == null) {
            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
        }

        //绑定新的IME前先解绑当前的IME
        unbindCurrentMethodLocked(false, true);

        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 (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
                | Context.BIND_NOT_VISIBLE | Context.BIND_NOT_FOREGROUND
                | Context.BIND_SHOWING_UI)) {
            mLastBindTime = SystemClock.uptimeMillis();
            mHaveConnection = true;
            mCurId = info.getId();
            mCurToken = new Binder();
            try {
                if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
                mIWindowManager.addWindowToken(mCurToken,
                        WindowManager.LayoutParams.TYPE_INPUT_METHOD);
            } catch (RemoteException e) {
            }
            return new InputBindResult(null, null, mCurId, mCurSeq,
                    mCurUserActionNotificationSequenceNumber);
        } else {
            mCurIntent = null;
            Slog.w(TAG, "Failure connecting to input method service: "
                    + mCurIntent);
        }
        return null;
    }

		
    private boolean bindCurrentInputMethodService(
            Intent service, ServiceConnection conn, int flags) {
        if (service == null || conn == null) {
            Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
            return false;
        }
        return mContext.bindServiceAsUser(service, conn, flags,
                new UserHandle(mSettings.getCurrentUserId()));
    }

      因为IMS本质是一个service,所以绑定一个IME实际上就是绑定一个service的过程。IMMS这里属于客户端,如果绑定成功,会回调onServiceConnected方法(同理,解绑也会回调onServiceDisconnected)。IMMS的代码如下
    // 这里也会回调IMS的onBind方法
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        synchronized (mMethodMap) {
            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
	            // mCurMethod持有IInputMethod接口的远程代理对象
                mCurMethod = IInputMethod.Stub.asInterface(service);
                if (mCurToken == null) {
                    Slog.w(TAG, "Service connected without a token!");
                    unbindCurrentMethodLocked(false, false);
                    return;
                }
                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                //会回调IMS的onAttchToken方法
                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
                if (mCurClient != null) {
                    clearClientSessionLocked(mCurClient);
                    // 创建连接会话控制
                    requestClientSessionLocked(mCurClient);
                }
            }
        }
    }
	
    void requestClientSessionLocked(ClientState cs) {
        if (!cs.sessionRequested) {
            if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
            InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
            cs.sessionRequested = true;
            //回调IMS的createSession方法
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
                    MSG_CREATE_SESSION, mCurMethod, channels[1],
                    new MethodCallback(this, mCurMethod, channels[0])));
        }
    }

    void onSessionCreated(IInputMethod method, IInputMethodSession session,
            InputChannel channel) {
        synchronized (mMethodMap) {
            if (mCurMethod != null && method != null
                    && mCurMethod.asBinder() == method.asBinder()) {
                if (mCurClient != null) {
                    clearClientSessionLocked(mCurClient);
	            // 将IInputMethodSession接口的远程代理对象封装在SessionState的对象中,并将其引用赋值给mCurClient的curSession
                    mCurClient.curSession = new SessionState(mCurClient,
                            method, session, channel);
                    InputBindResult res = attachNewInputLocked(true);
                    if (res.method != null) {
                        //会回调IMS的onBindMethod方法
                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
                                MSG_BIND_METHOD, mCurClient.client, res));
                    }
                    return;
                }
            }
        }

        // Session abandoned.  Close its associated input channel.
        channel.dispose();
    }

    InputBindResult attachNewInputLocked(boolean initial) {
        if (!mBoundToMethod) {
            //回调IMS的onBindInput方法
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
            mBoundToMethod = true;
        }
        final SessionState session = mCurClient.curSession;
        if (initial) {
            //回调IMS的onStartInput方法
            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
                    MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
        } else {
            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
                    MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
        }
        if (mShowRequested) {
            // 显示当前输入法
            if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
            showCurrentInputLocked(getAppShowFlags(), null);
        }
        return new InputBindResult(session.session,
                (session.channel != null ? session.channel.dup() : null),
                mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
    }
    
    绑定输入法的过程在IMMS部分到此结束,整个过程最重要的工作是mCurMethod会持有IInputMethod接口的远程代理对象的引用和mCurClient的curSession会持有封装了IInputSesssion接口的远程代理对象的引用。 这里出现两个非常重要的接口IInputMethod和IInputSession,前者供IMMS调用IMS的方法,包括onAttachToken,createSession等,而后者是供IMM调用IMS的方法。
    



版权声明:本文为博主原创文章,转载时请务必注明本文地址, 禁止用于任何商业用途, 否则会用法律维权。

学习点滴(一)

这文章 如何在EditText获得焦点的时候实现其内容全选  参考浏览器的网址输入栏,当点击的时候,其网址内容被全部选中,其原理就是在Edittext获得焦点的时候实现全选。 应用端可如...
  • tfs080640234
  • tfs080640234
  • 2014年09月19日 17:18
  • 1403

Android SDK 之 InputMethodService 详解

http://www.cnmsdn.com/html/201003/1268842609ID2158.html            需要将一个现有的输入法移植到Android平台上去,根据以前的经...
  • cstarbl
  • cstarbl
  • 2012年02月24日 16:44
  • 4607

Google认证fail项解决方案

需要保证网络可以访问谷歌资源。 【TestCase】 【Root Cause】 【Solutiion】 testInputMethodSubtypesOfS...
  • yuweixu2007
  • yuweixu2007
  • 2017年01月09日 17:55
  • 1822

Android4.4 CTS测试Fail项修改总结(三)

接上:Android4.4 CTS测试Fail项修改总结(二)  以下测试是在Android4.4上 6、android.provider.cts.ContactsContractIntentsTe...
  • deng0zhaotai
  • deng0zhaotai
  • 2015年03月27日 18:26
  • 4846

如何锁定 input 内容不可修改

如何锁定input内容不可修改呢? 锁定value不可修改的方法可以用disabled属性.. 这个是input控件的基本属性 input type="text" di...
  • Inite
  • Inite
  • 2016年10月19日 00:48
  • 3438

Android6.0 设置默认输入法

Android6.0设置默认输入法,这里以日文输入法为例, 1.在frameworks/base/packages/SettingsProvider/res/values/defaults.xml 添...
  • a161619
  • a161619
  • 2017年08月24日 14:20
  • 734

InputMethodFramework学习记录-前言

从毕业到现在已经从事android系统开发已经整整3年了,作为一个非科班出身的码农,时常感到压力很大,总是惶恐自己到底会什么。回想起这3年,除了第一年有兴趣写写PPT,研究下源码,后来只是周期性的处理...
  • Justwen26
  • Justwen26
  • 2016年08月06日 23:45
  • 186

Swoole源码学习记录(一)——进程间共享数据ShareMemory 和 MemoryPool

我接触PHP的时间不长,最开始只认为PHP是用来做网站开发,是一个比JSP要简单的语言。后来,因为工作需要,一位学长建议我使用Ngnix + PHP 搭建应用服务器,并建议使用现有的框架。一番搜索之下...
  • ldy3243942
  • ldy3243942
  • 2014年08月15日 10:55
  • 5836

设置disabled属性

//两种方法设置disabled属性   $('#areaSelect').attr("disabled",true);   $('#areaSelect').attr("disabled...
  • macfac
  • macfac
  • 2015年02月07日 14:22
  • 315

jquery设置input disabled属性的示例

如何将设置disabled属性为true,即为不可用状态,分别用js、jquery与html实现。 例子:    代码如下 复制代码 //JS do...
  • u013029858
  • u013029858
  • 2015年10月14日 13:38
  • 4341
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:InputMethodFramework学习记录( 一)
举报原因:
原因补充:

(最多只允许输入30个字)