Android 输入法框架 (1)

以下来自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架构涉及到三个主要的部分:

  • InputMethodManager(IMM)是负责管理其他部分交互的中心,以client-side API的形式存在于每一个应用上下文中,同时和InputMethodManagerService(IMMS)进行通信。
  • IME是具体交互模型的实现,其本质是一个service,允许用户产生text文本。系统绑定当前的IME,使当前的input method创建运行,并负责通知它什么时候隐藏和显示它的UI。 同一时间只有一个IME在运行。
  • 多个客户端应用之间使用IMM对输入焦点和输入法状态的控制进行协调。同一时间只有一个客户端处于激活状态。

      综上,整个IMF是围绕着IMMS,IMM,IMS三个核心类进行工作的,这三个核心类分别运行于system进程,客户端进程以及输入法进程,他们之间的交互需要使用android跨进程通信机制-IPC。下面将分别介绍三个核心类的主要代码以及他们之间是如何进行交互的。
 

首先看一下整体的UML 图

Android 输入法框架 (1)_第1张图片

 

从图中可以看出IMMS,IMS,IMM之间是通过5个AIDL接口进行交互的

  • IInputMethod : 具体逻辑由IMS的内部类实现 (非直接实现),定义了系统进程可以调用输入法的相关方法
  • IInputMethodSession : 具体逻辑由IMS的内部类实现(非直接实现),定义了客户端可以调用输入法的相关方法
  • IInputMethodManager : 由IMMS直接实现,定义了客户端可以调用系统进程的相关方法
  • IInputMethodClient : 由IMM里的内部类mClient 实现,定义了系统进程可以调用客户端的相关方法
  • IInputContext : 由当前焦点View进行创建 , 定义了输入法进程可以调用客户端的相关方法

下面将从IMMS,IMM,IMS以及整个IMF。

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);
                }
                ......
            }
        });
    }
    public void systemRunning(StatusBarManagerService statusBar) {
        synchronized (mMethodMap) {
            if (DEBUG) {
                Slog.d(TAG, "--- systemReady");
            }
            if (!mSystemReady) {
                ......
                //创建输入法列表和设置默认输入法
                buildInputMethodListLocked(mMethodList, mMethodMap,
                        !mImeSelectedOnBoot /* resetDefaultEnabledIme */);
                ......
                try {
                    //绑定默认输入法
                    startInputInnerLocked();
                } catch (RuntimeException e) {
                    Slog.w(TAG, "Unexpected exception", e);
                }
            }
        }
    }

所以IMMS的初始化最重要的工作就是建立一个输入法列表以及绑定默认输入法
 

 

维护和管理输入法

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

 

    void buildInputMethodListLocked(ArrayList list,
            HashMap map, boolean resetDefaultEnabledIme) {
        ......
        // 返回满足条件的ResolveInfo,即具有action为android.view.inputmethod的service
        final List services = pm.queryIntentServicesAsUser(
                new Intent(InputMethod.SERVICE_INTERFACE),
                PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
                mSettings.getCurrentUserId());

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

        ......
    }


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

 


    
        
    
    

 

绑定输入法

 

 

    因为解绑和绑定过程类似,所以这里先简单介绍IMMS是如何绑定IME。代码如下

 

   InputBindResult startInputInnerLocked() {
        ......
        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);
                    mCurClient.curSession = new SessionState(mCurClient,
                            method, session, channel);
                    InputBindResult res = attachNewInputLocked(
                            InputMethodClient.START_INPUT_REASON_SESSION_CREATED_BY_IME, true);
                    if (res.method != null) {
                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
                                MSG_BIND_CLIENT, 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);
    }
    

 

下面是完整的时序图

Android 输入法框架 (1)_第2张图片   

 

绑定输入法的过程在IMMS部分到此结束,整个过程最重要的工作是mCurMethod会持有IInputMethod接口的远程代理对象的引用和mCurClient的curSession会持有封装了IInputSesssion接口的远程代理对象的引用, 这里出现了之前提到的两个非常重要的接口IInputMethod和IInputSession

    

你可能感兴趣的:(android,android,源码分析)