关闭

Android输入法框架系统(上)

标签: android输入法框架android输入法机制android输入法原理android输入法显示
3983人阅读 评论(1) 收藏 举报
分类:

          输入法,就是用来输入字符(包括英文,俄文,中文)的工具。输入法你可以看成是一种字符发生器,它将输入数据触摸事件或者按键事件转化为其他更丰富的字符。在PC时代,输入法的原始输入来自实体键盘,鼠标,然后输入法将这些事件对应的ASCII码转换为俄文,中文,当然如果是英文是不需要转换,直接发送即可。而在Android系统里,由于输入法dialog永远没法成为焦点window,所以输入法永远没法获取到按键事件,也就是说输入法的输入数据只能来自触摸事件,输入法显示出键盘(大家称之为软键盘),用户点击键盘UI, 然后输入法将触摸事件所在位置的字符当做原始字符输入,最后组装成更为丰富的字符(多个字符组成拼音,然后转化为中文),然后就是发送到对应的程序。

        Android系统中,输入法可以是可以安装的,也就是说系统可以有多个输入法((sougou输入法,百度输入法),但是只有一个是激活的,当然用户可以切换输入法。同时,输入法是以service的方式运行的,输入法同一时间只能服务一个程序,只有最顶层的可见的程序才能接收到输入法的输入数据。

       输入法系统的整个框架如下:

       

       InputMethodManagerService(下文也称IMMS)负责管理系统的所有输入法,包括输入法service(InputMethodService简称IMS)加载及切换。程序获得焦点时,就会通过InputMethodManager向InputMethodManagerService通知自己获得焦点并请求绑定自己到当前输入法上。同时,当程序的某个需要输入法的view比如EditorView获得焦点时就会通过InputMethodManager向InputMethodManagerService请求显示输入法,而这时InputMethodManagerService收到请求后,会将请求的EditText的数据通信接口发送给当前输入法,并请求显输入法。输入法收到请求后,就显示自己的UI dialog,同时保存目标view的数据结构,当用户实现输入后,直接通过view的数据通信接口将字符传递到对应的View。接下来就来分析这些过程。


InputMethodManager创建


        每个程序有一个InputMethodManager实例,这个是程序和InputMethodManagerService通信的接口,该实例在ViewRootImpl初始化的时候创建。

 

    public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
    }

    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    //这个进程的InputMethodManager实例就生成了
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                } catch (RemoteException e) {
                    Log.e(TAG, "Failed to open window session", e);
                }
            }
            return sWindowSession;
        }
    }

    public static InputMethodManager getInstance() {
        synchronized (InputMethodManager.class) {
            if (sInstance == null) {
                // InputMethodManager其实就是一个Binder service的proxy
                IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
                IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
                sInstance = new InputMethodManager(service, Looper.getMainLooper());
            }
            return sInstance;
        }
    }


程序的Window获得焦点


           程序的window获得焦点的时序图如下


 

系统WindowManagerService更新焦点window

         哪个程序获得焦点是由系统决定的,是由WindowManagerService决定的,当系统的window状态发生变化时(比如window新增,删除)就会调用函数updateFocusedWindowLocked来更新焦点window。

    private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
        //计算焦点window
        WindowState newFocus = computeFocusedWindowLocked();
        if (mCurrentFocus != newFocus) {
            //焦点window发生变化,post一个message来通知程序焦点发生变化了
            mH.removeMessages(H.REPORT_FOCUS_CHANGE);
            mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
            return true;
        }
        return false;
    }

    private WindowState computeFocusedWindowLocked() {
        if (mAnimator.mUniverseBackground != null
                && mAnimator.mUniverseBackground.mWin.canReceiveKeys()) {
            return mAnimator.mUniverseBackground.mWin;
        }

        final int displayCount = mDisplayContents.size();
        for (int i = 0; i < displayCount; i++) {
            final DisplayContent displayContent = mDisplayContents.valueAt(i);
            WindowState win = findFocusedWindowLocked(displayContent);
            if (win != null) {
                return win;
            }
        }
        return null;
    }

    //该函数就是找出最top的可以接收按键事件的window,这个window就获得焦点
    private WindowState findFocusedWindowLocked(DisplayContent displayContent) {
        final WindowList windows = displayContent.getWindowList();
        for (int i = windows.size() - 1; i >= 0; i--) {
            final WindowState win = windows.get(i);
            //是否为activity的window
            AppWindowToken wtoken = win.mAppToken;
            //重要函数,window是否可以获取焦点
            if (!win.canReceiveKeys()) {
                continue;
            }
            // mFocusedApp是最top的activity ,下面逻辑是为了确保焦点window的app
            //必须是焦点程序之上,所以这个逻辑其实并没有多大作用,只是为了检测出
            //错误
            if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING &&
                    mFocusedApp != null) {
                ArrayList<Task> tasks = displayContent.getTasks();
                for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
                    AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
                    int tokenNdx = tokens.size() - 1;
                    for ( ; tokenNdx >= 0; --tokenNdx) {
                        final AppWindowToken token = tokens.get(tokenNdx);
                        if (wtoken == token) {
                            break;
                        }
                        if (mFocusedApp == token) {
                            return null;
                        }
                    }
                }
            }
            return win;
        }
        return null;
    }

    public final boolean canReceiveKeys() {
        return isVisibleOrAdding()
                && (mViewVisibility == View.VISIBLE)
                && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0);
    }
    //由于输入法的window带有FLAG_NOT_FOCUSABLE, 从上可见其不可能是焦点window
    //接下来系统开始通知程序端哪个window获得了焦点。

    final class H extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case REPORT_FOCUS_CHANGE: {
                    WindowState lastFocus;
                    WindowState newFocus;

                    synchronized(mWindowMap) {
                        lastFocus = mLastFocus;
                        newFocus = mCurrentFocus;
                        if (lastFocus == newFocus) {
                            // Focus is not changing, so nothing to do.
                            return;
                        }
                        mLastFocus = newFocus;
                    }
                    if (newFocus != null) {
                        //通知新的焦点程序其获得了焦点
                        newFocus.reportFocusChangedSerialized(true, mInTouchMode);
                        notifyFocusChanged();
                    }

                    if (lastFocus != null) {
                        //通知老的焦点程序其获得了焦点
                        lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
                    }
                } break;
     }

     public void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
        try {
            //这个就是通过Binder告知client其获得或失去了焦点
            mClient.windowFocusChanged(focused, inTouchMode);
        } catch (RemoteException e) {
        }
    }



        上面的mClient.windowFocusChanged会调回到ViewRootImpl中的W实例:

程序获得焦点改变事件

           

    //ViewRootImpl.java
    static class W extends IWindow.Stub {
        @Override
        public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
            final ViewRootImpl viewAncestor = mViewAncestor.get();
            if (viewAncestor != null) {
                viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
            }
        }
   }

    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
        Message msg = Message.obtain();
        msg.what = MSG_WINDOW_FOCUS_CHANGED;
        msg.arg1 = hasFocus ? 1 : 0;
        msg.arg2 = inTouchMode ? 1 : 0;
        mHandler.sendMessage(msg);
    }

    //程序获得焦点会通过调用mView.dispatchWindowFocusChanged和
    //imm.onWindowFocus来通知IMMS焦点信息发生改变,需要更新输入法了
    final class ViewRootHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MSG_WINDOW_FOCUS_CHANGED: {
                if (mAdded) {
                    boolean hasWindowFocus = msg.arg1 != 0;
                    mAttachInfo.mHasWindowFocus = hasWindowFocus;
                    mLastWasImTarget = WindowManager.LayoutParams
                            .mayUseInputMethod(mWindowAttributes.flags);
                    InputMethodManager imm = InputMethodManager.peekInstance();
                    if (mView != null) {
                        //调用根view的dispatchWindowFocusChanged函数通知view
                        //程序获得焦点
                        mView.dispatchWindowFocusChanged(hasWindowFocus);
                        mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(
hasWindowFocus);
                    }
                    if (hasWindowFocus) {
                        if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                            //通知imm该window获得焦点
                            imm.onWindowFocus(mView, mView.findFocus(),
                                    mWindowAttributes.softInputMode,
                                    !mHasHadWindowFocus, mWindowAttributes.flags);
                        }
                    }
                }

            } break;
    }

    //上面的根view就是DecorView,它只是调用父类ViewGroup
    //的dispatchWindowFocusChanged
    //ViewGroup.java
    @Override
    public void dispatchWindowFocusChanged(boolean hasFocus) {
        super.dispatchWindowFocusChanged(hasFocus);
        final int count = mChildrenCount;
        final View[] children = mChildren;
        //让每个子view处理window焦点改变时间
        //但是只有获得焦点的view才会处理这个时间
        for (int i = 0; i < count; i++) {
            children[i].dispatchWindowFocusChanged(hasFocus);
        }
    }

    //View.java
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        InputMethodManager imm = InputMethodManager.peekInstance();
        if (!hasWindowFocus) {
        } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
            //获得焦点的view通过 InputMethodManager向service通知自己获得焦点
            imm.focusIn(this);
        }
    }

焦点View向IMMS请求绑定输入法

         焦点view请求绑定输入法是通过调用InputMethodManager. focusIn实现的

       

    public void focusIn(View view) {
        synchronized (mH) {
            focusInLocked(view);
        }
    }

    void focusInLocked(View view) { 
        //保存焦点view变量
        mNextServedView = view;
        scheduleCheckFocusLocked(view);
    }

    static void scheduleCheckFocusLocked(View view) {
        ViewRootImpl viewRootImpl = view.getViewRootImpl();
        if (viewRootImpl != null) {
            viewRootImpl.dispatchCheckFocus();
        }
    }

    public void dispatchCheckFocus() {
        if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {
            // This will result in a call to checkFocus() below.
            mHandler.sendEmptyMessage(MSG_CHECK_FOCUS);
        }
    }
            case MSG_CHECK_FOCUS: {
                InputMethodManager imm = InputMethodManager.peekInstance();
                if (imm != null) {
                    imm.checkFocus();
                }
            } break;

    public void checkFocus() {
        if (checkFocusNoStartInput(false, true)) {
            startInputInner(null, 0, 0, 0);
        }
    }

    
    boolean startInputInner(IBinder windowGainingFocus, int controlFlags, int softInputMode,
            int windowFlags) {
        final View view;
        synchronized (mH) {
            //获得上面的焦点view
            view = mServedView;
        }

        EditorInfo tba = new EditorInfo();
        tba.packageName = view.getContext().getPackageName();
        tba.fieldId = view.getId();
        //创建数据通信连接接口,这个会传送到InputMethodService
        //InputMethodService后面就通过这个connection将输入法的字符传递给该view
        InputConnection ic = view.onCreateInputConnection(tba);
        
        synchronized (mH) {
            mServedInputConnection = ic;
            ControlledInputConnectionWrapper servedContext;
            if (ic != null) {
                mCursorSelStart = tba.initialSelStart;
                mCursorSelEnd = tba.initialSelEnd;
                mCursorCandStart = -1;
                mCursorCandEnd = -1;
                mCursorRect.setEmpty();
                //将InputConnection封装为binder对象,这个是真正可以实现跨进程通
                  //信的封装类
                  servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);
            }
            mServedInputConnectionWrapper = servedContext;
            
            try {
                InputBindResult res;
                if (windowGainingFocus != null) {
                    //focusIn这个不会走到这条分支
                    res = mService.windowGainedFocus(mClient, windowGainingFocus,
                            controlFlags, softInputMode, windowFlags,
                            tba, servedContext);
                } else {
                    //通知InputMethodManagerService,该程序的view获得焦点,IMMS
                    //就会将这个view和输入法绑定
                    res = mService.startInput(mClient,
                            servedContext, tba, controlFlags);
                }
                if (res != null) {
                    if (res.id != null) {
                        setInputChannelLocked(res.channel);
                        mBindSequence = res.sequence;
                        //获得了输入法的通信接口
                        mCurMethod = res.method;
                        mCurId = res.id;
                    }
                }
            }
        }

        return true;
    }


 

IMMS处理view绑定输入法事件

         为了讲解整个绑定过程,我们假设此时输入法service还没启动,这个情况下的输入法绑定是最长的,整个过程经历过如下过程:

1)       启动输入法service

2)       绑定输入法window的token

3)       请求输入法为焦点程序创建一个连接会话-

4)       将输入法的接口传递回程序client端

5)       绑定输入法和焦点view

          1-4是和程序相关的,而5是和view相关的。所以你可以说1~4是用来绑定程序window和输入法,而5是用来绑定程序view和输入法。

          输入法还没启动时,弹出输入法会经过1~5,输入法已经启动,但是焦点window发生变化时会经历3~5,焦点window没有变化,只是改变了焦点view,则只会经历5。整个流程如下:

 

启动输入法service

    @Override
    public InputBindResult startInput(IInputMethodClient client,
            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
        synchronized (mMethodMap) {
            final long ident = Binder.clearCallingIdentity();
            try {
                return startInputLocked(client, inputContext, attribute, controlFlags);
            }
        }
    }
    InputBindResult startInputLocked(IInputMethodClient client,
            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
        //程序在service端对应的数据结构
        ClientState cs = mClients.get(client.asBinder());
        return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
    }

    InputBindResult startInputUncheckedLocked(ClientState cs,
            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
        //如果新程序和当前活动的程序不同
        if (mCurClient != cs) {
            //取消当前活动程序和输入法的绑定
            unbindCurrentClientLocked();
        }

        //将新程序设置为当前活动的程序
        mCurClient = cs;
        mCurInputContext = inputContext;
        mCurAttribute = attribute;

        if (mCurId != null && mCurId.equals(mCurMethodId)) {
            if (cs.curSession != null) {
                //连接已经建立,直接开始绑定
                return attachNewInputLocked(
                        (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
            }
            if (mHaveConnection) {
                //输入法的连接是否已经创建,如果已经创建,直接传递给程序client端
                if (mCurMethod != null) {
                    requestClientSessionLocked(cs);
                    return new InputBindResult(null, null, mCurId, mCurSeq);
                }
            }
        }
        //否则需要启动输入法,并建立连接
        return startInputInnerLocked();
    }

    InputBindResult startInputInnerLocked() {
        InputMethodInfo info = mMethodMap.get(mCurMethodId);

        unbindCurrentMethodLocked(false, true);

        //启动输入法service
        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_SHOWING_UI)) {
            mHaveConnection = true;
            mCurId = info.getId();
            //这个token是给输入法service用来绑定输入法的window的,通过这个token
            //InputMethodManagerService可以很方便的直接管理输入法的window
            mCurToken = new Binder();
            try {
                mIWindowManager.addWindowToken(mCurToken,
                        WindowManager.LayoutParams.TYPE_INPUT_METHOD);
            } catch (RemoteException e) {
            }
            return new InputBindResult(null, null, mCurId, mCurSeq);
        }
        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()));
    }

    //输入法启动完成后就在函数onBind 传回一个binder接口
    @Override
    final public IBinder onBind(Intent intent) {
        if (mInputMethod == null) {
            mInputMethod = onCreateInputMethodInterface();
        }
        // IInputMethodWrapper只是一个wrapper,它负责将IMMS的调用转化为message
        //然后在message线程再调用mInputMethod对应的接口 
          //这样输入法的处理就是异步的了,因此你说它就是mInputMethod
        return new IInputMethodWrapper(this, mInputMethod);
    }

    @Override
    public AbstractInputMethodImpl onCreateInputMethodInterface() {
        return new InputMethodImpl();
    }

    //由于IMMS是以bindService的方式启动输入法service,所以当输入法service启动完
    //成后它就会回调IMMS的onServiceConnected

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        synchronized (mMethodMap) {
            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
                //保存输入法service传递过来的通信接口IInputMethod
                mCurMethod = IInputMethod.Stub.asInterface(service);
                //将刚刚创建的window token传递给输入法service,然后输入用这个token
                //创建window,这样IMMS可以用根据这个token找到输入法在IMMS里
                  //的数据及输入法window在WMS里的数据
                  executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
                if (mCurClient != null) {
                //请求为程序和输入法建立一个连接会话,这样client就可以直接和
                   //输入法通信了
                    requestClientSessionLocked(mCurClient);
                }
            }
        }
    }


 

输入法Window token的绑定及使用分析

      输入法Window token绑定

        IMMS在输入法启动完成并回调onServiceConnected时会将一个Window token传递给输入法。

 

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        synchronized (mMethodMap) {
            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
                mCurMethod = IInputMethod.Stub.asInterface(service);
                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
                if (mCurClient != null) {
                    clearClientSessionLocked(mCurClient);
                    requestClientSessionLocked(mCurClient);
                }
            }
        }
    }
            case MSG_ATTACH_TOKEN:
                args = (SomeArgs)msg.obj;
                try {
                    //和输入法通信
                    ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
                } catch (RemoteException e) {
                }
                args.recycle();
    
    public class InputMethodService extends AbstractInputMethodService {
    public class InputMethodImpl extends AbstractInputMethodImpl {
        public void attachToken(IBinder token) {
            if (mToken == null) {
                //保存token
                mToken = token;
                //这样输入法的window就绑定这个window token
                mWindow.setToken(token);
            }
        }
    }


      输入法Window token使用

       由于系统存在多个输入法,所以输入法要和IMMS通信,必须要个机制来标示自己是哪个输入法,这个就是通过上面的输入法Window token来实现的,比如输入法自己关闭自己:

 

    //InputMethodService.java输入法接口
    public void requestHideSelf(int flags) {
        //mToken就是上面提到的过程----IMMS传递给输入法的
        mImm.hideSoftInputFromInputMethod(mToken, flags);
    }
    //InputMethodManager.java
    public void hideSoftInputFromInputMethod(IBinder token, int flags) {
        try {
            mService.hideMySoftInput(token, flags);
        } catch (RemoteException e) {
            throw new RuntimeException(e);
        }
    }

    //IMMS
    @Override
    public void hideMySoftInput(IBinder token, int flags) {
        if (!calledFromValidUser()) {
            return;
        }
        synchronized (mMethodMap) {
            if (token == null || mCurToken != token) {
                if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
                        + Binder.getCallingUid() + " token: " + token);
                return;
            }
            long ident = Binder.clearCallingIdentity();
            try {
                hideCurrentInputLocked(flags, null);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }


输入法连接会话创建

       到此程序和输入法的session就建立了

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        synchronized (mMethodMap) {
            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
                if (mCurClient != null) {
                    clearClientSessionLocked(mCurClient);
                    requestClientSessionLocked(mCurClient);
                }
            }
        }
    }

    void requestClientSessionLocked(ClientState cs) {
        if (!cs.sessionRequested) {
            //这里又出现了InputChannel对,很面熟吧,在前面几篇文章已经详细分析过
              //了,可见它已经成为一种通用的跨平台的数据通信接口了
            InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
            cs.sessionRequested = true;
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
                    MSG_CREATE_SESSION, mCurMethod, channels[1],
                    new MethodCallback(this, mCurMethod, channels[0])));
        }
     }
            case MSG_CREATE_SESSION: {
                args = (SomeArgs)msg.obj;
                IInputMethod method = (IInputMethod)args.arg1;
                InputChannel channel = (InputChannel)args.arg2;
                try {
                    method.createSession(channel, (IInputSessionCallback)args.arg3);
                } catch (RemoteException e) {
                }
    //上面是IMMS端,下面就看IMS输入法端的处理
     public abstract class AbstractInputMethodService extends Service
        implements KeyEvent.Callback {
     public abstract class AbstractInputMethodImpl implements InputMethod {
        public void createSession(SessionCallback callback) {
            callback.sessionCreated(onCreateInputMethodSessionInterface());
        }
<pre class="java" name="code">     }
     }
} @Override public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() { //sesion的真正实现 return new InputMethodSessionImpl(); } //然后回到了IMMS void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) { synchronized (mMethodMap) { if (mCurMethod != null && method != null && mCurMethod.asBinder() == method.asBinder()) { if (mCurClient != null) { //将session相关的数据封装到SessionState对象里 mCurClient.curSession = new SessionState(mCurClient, method, session, channel); //这个会开始真正的绑定 InputBindResult res = attachNewInputLocked(true); return; } } } }

传递输入法接口给程序

    void onSessionCreated(IInputMethod method, IInputMethodSession session,
            InputChannel channel) {
        synchronized (mMethodMap) {
            if (mCurMethod != null && method != null
                    && mCurMethod.asBinder() == method.asBinder()) {
                if (mCurClient != null) {
                    InputBindResult res = attachNewInputLocked(true);
                    if (res.method != null) {
                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
                                MSG_BIND_METHOD, mCurClient.client, res));
                    }
                    return;
                }
            }
        }
        channel.dispose();
    }
            case MSG_BIND_METHOD: {
                args = (SomeArgs)msg.obj;
                IInputMethodClient client = (IInputMethodClient)args.arg1;
                InputBindResult res = (InputBindResult)args.arg2;
                try {
                    //会调回到程序端
                    client.onBindMethod(res);
                }
                args.recycle();
                return true;
            }


 

输入法和view绑定

   //IMMS 
   InputBindResult attachNewInputLocked(boolean initial) {
        if (!mBoundToMethod) {
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
            mBoundToMethod = true;
        }
        final SessionState session = mCurClient.curSession;
        if (initial) {
            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
                    MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
        } else {
            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
                    MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
        }
        return new InputBindResult(session.session,
                session.channel != null ? session.channel.dup() : null, mCurId, mCurSeq);
    }
            case MSG_BIND_INPUT:
                args = (SomeArgs)msg.obj;
                try {
                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
                } catch (RemoteException e) {
                }
                args.recycle();
                return true;
           
            case MSG_START_INPUT:
                args = (SomeArgs)msg.obj;
                try {
                    SessionState session = (SessionState)args.arg1;
                    session.method.startInput((IInputContext)args.arg2,
                            (EditorInfo)args.arg3);
                } catch (RemoteException e) {
                }
                args.recycle();
                return true;
    //IMS
    @Override
    public void startInput(IInputContext inputContext, EditorInfo attribute) {
        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
                inputContext, attribute));
    }
            case DO_START_INPUT: {
                SomeArgs args = (SomeArgs)msg.obj;
                // IInputContext就是输入法和文本输入view的通信接口
                //通过这个接口,输入法能够获取view的信息,也能够直接将文本传
                //送给view
                IInputContext inputContext = (IInputContext)args.arg1;
                InputConnection ic = inputContext != null
                        ? new InputConnectionWrapper(inputContext) : null;
                EditorInfo info = (EditorInfo)args.arg2;
                inputMethod.startInput(ic, info);
                args.recycle();
                return;
            }
    public class InputMethodImpl extends AbstractInputMethodImpl {
        public void startInput(InputConnection ic, EditorInfo attribute) {
            doStartInput(ic, attribute, false);
        }
    }

    void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
        if (!restarting) {
            doFinishInput();
        }
        mInputStarted = true;
        mStartedInputConnection = ic;
        mInputEditorInfo = attribute;
        initialize();
        onStartInput(attribute, restarting);
        if (mWindowVisible) {
            if (mShowInputRequested) {
                mInputViewStarted = true;
                //真正的输入法需要在这个接口里实现输入法的内容
                onStartInputView(mInputEditorInfo, restarting);
                startExtractingText(true);
            } else if (mCandidatesVisibility == View.VISIBLE) {
                mCandidatesViewStarted = true;
                onStartCandidatesView(mInputEditorInfo, restarting);
            }
        }
    }


 

        到此焦点view已经通过调用IMMS的startInput和输入法绑定了,但是此时输入法还没有显示。但是系统紧接着会调用windowGainFocus来显示输入法。


程序焦点获取事件导致输入法显示

       请查看输入法框架下篇

输入法响应显示请求

       请查看输入法框架下篇

用户单击输入框View导致输入法显示

       请查看输入法框架下篇

输入法传递输入文本信息给view

       请查看输入法框架下篇

/********************************

* 本文来自博客  “爱踢门”

* 转载请标明出处:http://blog.csdn.net/itleaks

******************************************/


1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:93577次
    • 积分:1249
    • 等级:
    • 排名:千里之外
    • 原创:28篇
    • 转载:1篇
    • 译文:1篇
    • 评论:71条
    最新评论