Android输入法框架IMMS

1.前言

我们知道输入法InputMethodService和三方app分属于不同进程,彼此不知道对方存在,但是他们又在输入过程中,有交互,他们是怎么建联系的,是谁把他们撮合起来的,图1.1已经给出了答案是InputMethodManagerService,整个撮合过程是我们要分析的。在后面讲解中IMMS 就代表InputMethodManagerService输入法管理者,IME代表InputMethodService输入法,WMS 代表WindowManagerService窗口管理者,下面代码分析是基于Android8.1


图1.1

2.窗口建档备案(IInputMethodClient)

IInputMethodClient是一个Aidl接口,是供InputMethodManagerService调,获取app和传递给app一些参数用的

2.1我们每个窗口对应一个ViewRoot,他负责把Decorview添加到WMS(WindowManagerService)然后显示,添加操作是通过mWindowSession进行的,mWindowSessiond的init初始化过程中就会把IInputMethodClient和IInputContext传给WMS

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {     
        ...
        final IWindowSession mWindowSession;
        ...
        public ViewRootImpl(Context context, Display display) {
          mContext = context;
          mWindowSession = WindowManagerGlobal.getWindowSession();
        }

        public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
              ...
              res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                      getHostVisibility(), mDisplay.getDisplayId(),
                      mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                      mAttachInfo.mOutsets, mInputChannel);
             ...
       }
}

2.2windowManager就是wms的代理端,windowManager调用openSession把InputMethodClient和IInputContext传给wms

public final class WindowManagerGlobal {
  ...
  public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
  ...
}

2.3看看WMS侧干了些什么?是怎么和IMMS(InputMethodManagerService)联系起来了的

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {    
     ...
    @Override
    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) {
        if (client == null) throw new IllegalArgumentException("null client");
        if (inputContext == null) throw new IllegalArgumentException("null inputContext");
        Session session = new Session(this, callback, client, inputContext);
        return session;
    }
    ...
}

2.4这里就开始从WMS进入了IMMS ,mService.mInputMethodManager就是IMMS,调用他的addClient把IInputMethodClient添加到了IMMS

public class Session extends IWindowSession.Stub
        implements IBinder.DeathRecipient {
     ...
    public Session(WindowManagerService service, IWindowSessionCallback callback,
            IInputMethodClient client, IInputContext inputContext) {
                                                ...
            mService.mInputMethodManager.addClient(client, inputContext,
                      mUid, mPid);
            ...
    }
  ...
}

2.5终于进入到了IMMS,他给客户端窗口按client即IInputMethodClient建档,存到了成员变量mClients中,从这里可以看出每个窗口在创建起初,不管有没有Edittext焦点,IMMS就会给每个窗口存一个以client为key的ClientState为值得价值对。这样InputMethodManagerService调用app端窗口这个线就建立好了。

public class InputMethodManagerService extends IInputMethodManager.Stub{
   ...
    @Override
    public void addClient(IInputMethodClient client,
            IInputContext inputContext, int uid, int pid) {
        if (!calledFromValidUser()) {
            return;
        }
        synchronized (mMethodMap) {
            mClients.put(client.asBinder(), new ClientState(client,
                    inputContext, uid, pid));
        }
    }
   ...

    static final class ClientState {
      final IInputMethodClient client;
      final IInputContext inputContext;
      final int uid;
      final int pid;
      final InputBinding binding;

      boolean sessionRequested;
      SessionState curSession;
      ClientState(IInputMethodClient _client, IInputContext _inputContext,
              int _uid, int _pid) {
          client = _client;
          inputContext = _inputContext;
          uid = _uid;
          pid = _pid;
          binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
      }
  }
}

3.IMMS绑定IMS(IInputMethodWrapper)

3.1前面一节只是app和IMMS有了联系,这一节是要IMS与IMMS建立联系,当我们启动一个activity既添加一个窗口到WMS或则退出一个activity,都会导致MWS端的窗口排序发生变化,WMS经过计算得出当先谁是聚焦窗口,然后通过远程调用 W的windowFocusChanged,最后调到了onPostWindowFocus方法第30行

ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
        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);
      }
      ...
      final class ViewRootHandler extends Handler {
         @Override
        public void handleMessage(Message msg) {
          ...
          case MSG_WINDOW_FOCUS_CHANGED: {
             ...
             imm.onPostWindowFocus(mView, mView.findFocus(),
                                    mWindowAttributes.softInputMode,
                                    !mHasHadWindowFocus, mWindowAttributes.flags);
              ...
          }
        }
      }
}

3.2onPostWindowFocus最后调用到60行,我们看到熟悉的InputConnection和EditorInfo就是这时候收集发送到IMMS的

InputMethodManager.java 
public final class InputMethodManager {
   public void onPostWindowFocus(View rootView, View focusedView,
            @SoftInputModeFlags int softInputMode, boolean first, int windowFlags) {
             final IInputMethodManager mService;
             ...
            if (startInputInner(InputMethodClient.START_INPUT_REASON_WINDOW_FOCUS_GAIN,
                    rootView.getWindowToken(), controlFlags, softInputMode, windowFlags)) {
                return;
            }
            ...
   }
   boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
            IBinder windowGainingFocus, int controlFlags, int softInputMode,
            int windowFlags) {
            ...
            EditorInfo tba = new EditorInfo();
            ...
            tba.packageName = view.getContext().getOpPackageName();
            tba.fieldId = view.getId();
            InputConnection ic = view.onCreateInputConnection(tba);
            ...
            synchronized (mH) {
                ...
                mCurrentTextBoxAttribute = tba;
                mServedConnecting = false;
                if (mServedInputConnectionWrapper != null) {
                    mServedInputConnectionWrapper.deactivate();
                    mServedInputConnectionWrapper = null;
                }
                ControlledInputConnectionWrapper servedContext;
                final int missingMethodFlags;
                if (ic != null) {
                    mCursorSelStart = tba.initialSelStart;
                    mCursorSelEnd = tba.initialSelEnd;
                    mCursorCandStart = -1;
                    mCursorCandEnd = -1;
                    mCursorRect.setEmpty();
                    mCursorAnchorInfo = null;
                    final Handler icHandler;
                    missingMethodFlags = InputConnectionInspector.getMissingMethodFlags(ic);
                    if ((missingMethodFlags & InputConnectionInspector.MissingMethodFlags.GET_HANDLER)
                            != 0) {
                        // InputConnection#getHandler() is not implemented.
                        icHandler = null;
                    } else {
                        icHandler = ic.getHandler();
                    }
                    servedContext = new ControlledInputConnectionWrapper(
                            icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
                } else {
                    servedContext = null;
                    missingMethodFlags = 0;
                }
                mServedInputConnectionWrapper = servedContext;

                try {
                    //远程调用到IMMS带tba(EditorInfo),servedContext(InputConnection)过去
                    final InputBindResult res = mService.startInputOrWindowGainedFocus(
                            startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
                            windowFlags, tba, servedContext, missingMethodFlags);
                    ...
                } catch (RemoteException e) {
                    Log.w(TAG, "IME died: " + mCurId, e);
                }
            }

        return true;
    }
 }

3.3上面的startInputOrWindowGainedFocus通过Binder远程调用到MMS,被远程调用过来后经过一系列调用最终到了bindCurrentInputMethodService方法,关键代码66行bindServiceAsUser绑定输入法IME


InputMethodManagerService.java
public class InputMethodManagerService extends IInputMethodManager.Stub
        implements ServiceConnection, Handler.Callback {
   @Override
   public InputBindResult startInputOrWindowGainedFocus(final int startInputReason,
              IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
              int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,final int missingMethods) {
         //这里windowToken不为空走第一个
          if (windowToken != null) {
            //走这
              return windowGainedFocus(startInputReason, client, windowToken, controlFlags,
                      softInputMode, windowFlags, attribute, inputContext, missingMethods);
          } else {
              return startInput(startInputReason, client, inputContext, missingMethods, attribute,
                      controlFlags);
          }
      }
   private InputBindResult windowGainedFocus(
              /* @InputMethodClient.StartInputReason */ final int startInputReason,
              IInputMethodClient client, IBinder windowToken, int controlFlags,
              /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
              int windowFlags, EditorInfo attribute, IInputContext inputContext,
              /* @InputConnectionInspector.missingMethods */  final int missingMethods) {
        //根据客户端传来IInputMethodClient获取第2节的备案的ClientState
         ClientState cs = mClients.get(client.asBinder());
         ...
         if (attribute != null) {
                return startInputUncheckedLocked(cs, inputContext, missingMethods,
                        attribute, controlFlags, startInputReason);
         }
         ...

   }

   InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
              /* @InputConnectionInspector.missingMethods */ final int missingMethods,
              @NonNull EditorInfo attribute, int controlFlags,
              /* @InputMethodClient.StartInputReason */ final int startInputReason) {
        ...
       //设置为当前client,后面创建IInputMethodSession使用
        mCurClient = cs;
        mCurInputContext = inputContext;
        ...
        return startInputInnerLocked();
   }

  InputBindResult startInputInnerLocked() {
        ...
         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, IME_CONNECTION_BIND_FLAGS)) {
          ...
         }
  }
  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()));
   }

   @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        synchronized (mMethodMap) {
            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
                mCurMethod = IInputMethod.Stub.asInterface(service);
                if (mCurToken == null) {
                    Slog.w(TAG, "Service connected without a token!");
                    unbindCurrentMethodLocked(false);
                    return;
                }
                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
                if (mCurClient != null) {
                    clearClientSessionLocked(mCurClient);
                    requestClientSessionLocked(mCurClient);
                }
            }
        }
    }
}

3.4绑定之后下面就去了IME里面看到创建IInputMethodWrapper这个Binder,并且返回了它,因为3.3节47行绑定时传的参数ServiceConnection conn是InputMethodManagerService this,所以绑定之后回调会回调到3.3节的62行把IInputMethodWrapper保存在IMMS的成员变量mCurMethod既66行代码,这样图1.1 的4号线也建立了。

public abstract class AbstractInputMethodService extends Service
        implements KeyEvent.Callback {
    ...
     @Override
    final public IBinder onBind(Intent intent) {
        if (mInputMethod == null) {
            mInputMethod = onCreateInputMethodInterface();
        }
        return new IInputMethodWrapper(this, mInputMethod);
    }
    ...
}

4.创建IInputMethodSession

4.1在3.3节绑定完毕后,IMMS中onServiceConnected就拥有了IInputMethodWrapper,在这个函数中有调用了requestClientSessionLocked这个函数,他的作用是创建图1.1的5号链接,咱们分析代码,最终是调用21行method.createSession(...)这里method既3.4节创建的IInputMethodWrapper,这个Binder可以调用到IME

InputMethodManagerService.java
public class InputMethodManagerService extends IInputMethodManager.Stub
        implements ServiceConnection, Handler.Callback {
    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;
                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
                        MSG_CREATE_SESSION, mCurMethod, channels[1],
                        new MethodCallback(this, mCurMethod, channels[0])));
            }
        }
    @Override
    public boolean handleMessage(Message msg) {
      ...
      case MSG_CREATE_SESSION: {
              args = (SomeArgs)msg.obj;
              IInputMethod method = (IInputMethod)args.arg1;
              InputChannel channel = (InputChannel)args.arg2;
              ...
              //关键代码调用到了IME注意这里的IInputSessionCallback就下面的MethodCallback也是一个Bider,发给IME
              //IME创建好了IInputMethodSession,通过MethodCallback发送回来,
              //为什么要多此一举,找个回调MethodCallback呢?直接返回不好吗?
              //这样做是为了防止阻塞,直接会返回存在问题是的IMMS会一直阻塞到这,直到返回,加了回调我只要发过去就得了,你准备好了再发给我
              method.createSession(channel, (IInputSessionCallback)args.arg3);
              ...              
          }
      ...
    }

    private static final class MethodCallback extends IInputSessionCallback.Stub {
          private final InputMethodManagerService mParentIMMS;
          private final IInputMethod mMethod;
          private final InputChannel mChannel;

          MethodCallback(InputMethodManagerService imms, IInputMethod method,
                  InputChannel channel) {
              mParentIMMS = imms;
              mMethod = method;
              mChannel = channel;
          }

          @Override
          public void sessionCreated(IInputMethodSession session) {
              long ident = Binder.clearCallingIdentity();
              try {
                  mParentIMMS.onSessionCreated(mMethod, session, mChannel);
              } finally {
                  Binder.restoreCallingIdentity(ident);
              }
          }
      }
}

4.2经过上一步4.1 27行调用,就会调用到IME中,18行的inputMethod

IInputMethodWrapper.java
class IInputMethodWrapper extends IInputMethod.Stub
        implements HandlerCaller.Callback {

    @Override
    public void createSession(InputChannel channel, IInputSessionCallback callback) {
        mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
                channel, callback));
    }

   @Override
    public void executeMessage(Message msg) {
        InputMethod inputMethod = mInputMethod.get();
        switch (msg.what) {
           ...
           case DO_CREATE_SESSION: {
                SomeArgs args = (SomeArgs)msg.obj;
               //关键代码
                inputMethod.createSession(new InputMethodSessionCallbackWrapper(
                        mContext, (InputChannel)args.arg1,
                        (IInputSessionCallback)args.arg2));
                args.recycle();
                return;
            }
           ...
    }

}

4.3第6行createSession的主要功能是创建InputMethodSessionImpl,并传回到IMMS;InputMethodSessionImpl主要作用是,当app的输入框光标边移动了变化了,用来通知IME,21行到27行

AbstractInputMethodService.java
public abstract class AbstractInputMethodService extends Service
        implements KeyEvent.Callback {
  ...
  public abstract class AbstractInputMethodImpl implements InputMethod {
        public void createSession(SessionCallback callback) {
            callback.sessionCreated(onCreateInputMethodSessionInterface());
        }
   ...
  }

 @Override
  public AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface() {
      return new InputMethodSessionImpl();
  }
  ...

  public class InputMethodSessionImpl extends AbstractInputMethodSessionImpl {
     ...
      //这个是光标移动了通知输入法IME,只展示一常用的方法
     public void updateSelection(int oldSelStart, int oldSelEnd,
                int newSelStart, int newSelEnd,
                int candidatesStart, int candidatesEnd) {
            if (!isEnabled()) {
                return;
            }
            InputMethodService.this.onUpdateSelection(oldSelStart, oldSelEnd,
                    newSelStart, newSelEnd, candidatesStart, candidatesEnd);
        }
    ...
}

4.4创建好了InputMethodSessionImpl,通过callback的sessionCreated传递给IMMS 既7行,onSessionCreated只是做了一个Handler转发,需要注意的是mCurClient这个成员变量,是怎么确定的,因为要通过mCurClient把InputBindResult res传递客户端app,要传递给哪个客户端,我们前面3.3 节26行根据当前聚焦窗口就确定mCurClient,这里的mCurClient就是前面2.4节建档传来的client

public class InputMethodManagerService extends IInputMethodManager.Stub
        implements ServiceConnection, Handler.Callback {
   ...
     private static final class MethodCallback extends IInputSessionCallback.Stub {
        ...
        @Override
        public void sessionCreated(IInputMethodSession session) {
            long ident = Binder.clearCallingIdentity();
            try {
                //这里传入session
                mParentIMMS.onSessionCreated(mMethod, session, mChannel);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }
   void onSessionCreated(IInputMethod method, IInputMethodSession session,
            InputChannel channel) {
        synchronized (mMethodMap) {
            if (mCurMethod != null && method != null
                    && mCurMethod.asBinder() == method.asBinder()) {
                if (mCurClient != null) {
                    clearClientSessionLocked(mCurClient);
                   //这里把session赋值给mCurClient.curSession
                    mCurClient.curSession = new SessionState(mCurClient,
                            method, session, channel);
                   //这里具体看attachNewInputLocked
                    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(
            /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
       ...
       if (!mBoundToMethod) {
         //给IME传递
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
            mBoundToMethod = true;
        }
                                ...
         //取出session赋值给InputBindResult
        final SessionState session = mCurClient.curSession;
        ...
        return new InputBindResult(session.session,
                (session.channel != null ? session.channel.dup() : null),
                mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
    }
    @Override
    public boolean handleMessage(Message msg) {
      ...
      case MSG_BIND_CLIENT: {
                args = (SomeArgs)msg.obj;
                IInputMethodClient client = (IInputMethodClient)args.arg1;
                InputBindResult res = (InputBindResult)args.arg2;
                ...
                 //取出把带有IInputMethodSession的InputBindResult传回客户端app
                client.onBindMethod(res);
                ...
                return true;
        ...
        case MSG_BIND_INPUT:
                args = (SomeArgs)msg.obj;
                try {
                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
                } catch (RemoteException e) {
                }
                args.recycle();
                return true;
        ...
      }
      ...
    }
   ...
}

4.5接下来就到了app端处理了,app接到 InputBindResult res的后续处理上代码,36行res.method就是从IMMS传过来的IInputMethodSession,也就是IMS里创建的既4.3节13行创建的IInputMethodSession,到这里图1.1的5号线路就建立好了

InputMethodManager.java
public final class InputMethodManager {

   IInputMethodSession mCurMethod;
  ...
  final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {

    ...
       @Override
        public void onBindMethod(InputBindResult res) {
            mH.obtainMessage(MSG_BIND, res).sendToTarget();
        }
    ...
  }
  class H extends Handler {
                                ...
        @Override
        public void handleMessage(Message msg) {
            case MSG_BIND: {
                final InputBindResult res = (InputBindResult)msg.obj;

                synchronized (mH) {
                    if (mBindSequence < 0 || mBindSequence != res.sequence) {
                        Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
                                + ", given seq=" + res.sequence);
                        if (res.channel != null && res.channel != mCurChannel) {
                            res.channel.dispose();
                        }
                        return;
                    }

                    mRequestUpdateCursorAnchorInfoMonitorMode =
                            REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;

                    setInputChannelLocked(res.channel);
                    //这里的res.method就是从IMMS传过来的IInputMethodSession,也就是IMS里创建的既4.3节13行创建的IInputMethodSession
                    mCurMethod = res.method;
                    mCurId = res.id;
                    mBindSequence = res.sequence;
                }
               ...
                return;
            }
        }
}
InputMethodManager.java
public final class InputMethodManager {

   IInputMethodSession mCurMethod;
  ...
  final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {

    ...
       @Override
        public void onBindMethod(InputBindResult res) {
            mH.obtainMessage(MSG_BIND, res).sendToTarget();
        }
    ...
  }
  class H extends Handler {
                                ...
        @Override
        public void handleMessage(Message msg) {
            case MSG_BIND: {
                final InputBindResult res = (InputBindResult)msg.obj;

                synchronized (mH) {
                    if (mBindSequence < 0 || mBindSequence != res.sequence) {
                        Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
                                + ", given seq=" + res.sequence);
                        if (res.channel != null && res.channel != mCurChannel) {
                            res.channel.dispose();
                        }
                        return;
                    }

                    mRequestUpdateCursorAnchorInfoMonitorMode =
                            REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;

                    setInputChannelLocked(res.channel);
                    //这里的res.method就是从IMMS传过来的IInputMethodSession,也就是IMS里创建的既4.3节13行创建的IInputMethodSession
                    mCurMethod = res.method;
                    mCurId = res.id;
                    mBindSequence = res.sequence;
                }
               ...
                return;
            }
        }
}

4.6回到4.4 28和31行 IME端bindInput的操作

IInputMethodWrapper.java
class IInputMethodWrapper extends IInputMethod.Stub
        implements HandlerCaller.Callback {
      ...
      @Override
      public void bindInput(InputBinding binding) {
          // This IInputContext is guaranteed to implement all the methods.
          final int missingMethodFlags = 0;
          InputConnection ic = new InputConnectionWrapper(mTarget,
                  IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags);
          InputBinding nu = new InputBinding(ic, binding);
          mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
      }
     ...

     @Override
     public void executeMessage(Message msg) {
       ...
        case DO_SET_INPUT_CONTEXT: {
                inputMethod.bindInput((InputBinding)msg.obj);
                return;
            }
       ...
     }
}

4.7在5.4节的第2行最终调到下面第7行,8、9行完成mInputConnection赋值至此6条线路全部建立

public class InputMethodService extends AbstractInputMethodService {
   InputBinding mInputBinding;
   InputConnection mInputConnection;
  ...
  public class InputMethodImpl extends AbstractInputMethodImpl {
          ...
          public void bindInput(InputBinding binding) {
              mInputBinding = binding;
              mInputConnection = binding.getConnection();
              if (DEBUG) Log.v(TAG, "bindInput(): binding=" + binding
                      + " ic=" + mInputConnection);
              if (mImm != null && mToken != null) {
                  mImm.reportFullscreenMode(mToken, mIsFullscreen);
              }
              initialize();
              onBindInput();
          }
   }
  ...

}

5.传递IInputContext

5.1在2.2节的中16行中imm.getInputContext()会创建IInputContext,看代码mIInputContext是在InputMethodManager构造函数中初始化的,第11行传入的参数是mDummyInputConnection,而mDummyInputConnection是BaseInputConnection看到他的mTargetView为null,那么他是不是输入法提交内容时就没法显示了,那么他是如何显示上屏内容的?留疑问继续看看图1.1的6号线是怎么建立的。

public final class InputMethodManager {
  final IInputContext mIInputContext;
  final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
  public IInputContext getInputContext() {
        return mIInputContext;
    }
  InputMethodManager(IInputMethodManager service, Looper looper) {
        mService = service;
        mMainLooper = looper;
        mH = new H(looper);
        mIInputContext = new ControlledInputConnectionWrapper(looper,
                mDummyInputConnection, this);
    }

}
public class BaseInputConnection implements InputConnection {
    ...
    BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
        mIMM = mgr;
        mTargetView = null;
        mDummyMode = !fullEditor;
    }
    ...
}

5.2 前面讲过第2节窗口建档的结果就是在IMMS 里面存了一个价值对2.5节10行 值为 ClientState(client, inputContext, uid, pid)里面保存的inputContext就是5.1节第11行创建的ControlledInputConnectionWrapper

5.3我们知道最终inputContext要传递给IME,接下我们看看他是如何传递给IME的,IME创建完成InputMethodSession回调在4.4节第17行IMMS的onSessionCreated方法中,然后调用第28行attachNewInputLocked,看看attachNewInputLocked做了什么。第9行开始往IME传递了,第28行IInputMethod就是绑定IME得到的IInputMethodWrapper (具体3.3节74行),bindInput()之后就进入了IME,这里mCurClient.binding具体看建档环节2.5节31行

InputMethodManagerService.java
public class InputMethodManagerService extends IInputMethodManager.Stub
        implements ServiceConnection, Handler.Callback {
    InputBindResult attachNewInputLocked(
                /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
           ...
           if (!mBoundToMethod) {
             //给IME传递
                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                        MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
                mBoundToMethod = true;
            }
            ...
             //取出session赋值给InputBindResult
            final SessionState session = mCurClient.curSession;
            ...
            return new InputBindResult(session.session,
                    (session.channel != null ? session.channel.dup() : null),
                    mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
   }

    @Override
    public boolean handleMessage(Message msg) {
      ...
      case MSG_BIND_INPUT:
                args = (SomeArgs)msg.obj;
                try {
                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
                } catch (RemoteException e) {
                }
                args.recycle();
                return true;
      ...
    }

}

5.4IME端bindInput的操作

IInputMethodWrapper.java
class IInputMethodWrapper extends IInputMethod.Stub
        implements HandlerCaller.Callback {
      ...
      @Override
      public void bindInput(InputBinding binding) {
          // This IInputContext is guaranteed to implement all the methods.
          final int missingMethodFlags = 0;
          InputConnection ic = new InputConnectionWrapper(mTarget,
                  IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags);
          InputBinding nu = new InputBinding(ic, binding);
          mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
      }
     ...

     @Override
     public void executeMessage(Message msg) {
       ...
        case DO_SET_INPUT_CONTEXT: {
                inputMethod.bindInput((InputBinding)msg.obj);
                return;
            }
       ...
     }
}

5.5 在5.4节的第2行最终调到下面第7行,8、9行完成mInputConnection赋值至此6条线路全部建立

public class InputMethodService extends AbstractInputMethodService {
   InputBinding mInputBinding;
   InputConnection mInputConnection;
  ...
  public class InputMethodImpl extends AbstractInputMethodImpl {
          ...
          public void bindInput(InputBinding binding) {
              mInputBinding = binding;
              mInputConnection = binding.getConnection();
              if (DEBUG) Log.v(TAG, "bindInput(): binding=" + binding
                      + " ic=" + mInputConnection);
              if (mImm != null && mToken != null) {
                  mImm.reportFullscreenMode(mToken, mIsFullscreen);
              }
              initialize();
              onBindInput();
          }
   }
  ...

}

6.总结

图6.1

1.当我们要显示一个窗口时,首先会把窗口添加到WMS,在添加过程会发生openSession远程IPC,IInputContext和IInputMethodClient就会传递给WMS

2.WMS会把上面俩兄弟添加到IMMS键档,到此图1.1的2号链接建立

3.当我们把窗口添加到WMS,多了一个窗口WMS肯定要计算各窗口z轴值然后排序,如果这个窗口的z值计算出最大既最顶层,那么就WMS会调到当前窗口的windowFocusChanged

4.当前窗口会遍历所用view,计算出哪个view是聚焦view后,通过startInputOrWindowGainedFocus远程调用IMMS

5.在IMMS绑定IMS

6.绑定返回到IMMS的回调方法中,IMS会传过来IInputMethodWrapper,到这里图1.1的4号链接建立

7.紧接着IMMS会利用上面创建好的4号线,通知IME创建IInputMethodSession

8.创建好之后,把IInputMethodSession传到IMMS

9.在把IInputMethodSession返回到客户端app之前,利用IInputMethodWrapper,把建档时候传来的IInputContext,通过bindInput传递给IME这个就是我们经常使用的IC InputConnection了至此图1.1 的6号线路建立好

10.把第8步已经创建好的InputMethodSession,利用之前建档建立好的2线路传回app,图1.1的5号线路建立好,到此时六条线路全部建立好

7 .疑问

1号和3号线路,怎么没有看到建立过程,这与Android服务机制有有关,他们是一个链接,都是InputMethodManager,因为IMMS是公开服务,它注册到ServiceManager服务注册中心,所以通过第5行代码就可以把名字(input_method)给到到服务注册中心获取1号和3号链接,这有点类似中介(IMMS)和房东(IME)和租客(APP)之间的关系,租客和房东不知道彼此,俩者都会通过114(注册中心),找到中介(IMMS)电话,然后在中介的撮合下俩者完成合同签署

public final class InputMethodManager {
  final IInputMethodManager mService;
      InputMethodManager(Looper looper) throws ServiceNotFoundException {
        this(IInputMethodManager.Stub.asInterface(
                ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)), looper);
    }
   InputMethodManager(IInputMethodManager service, Looper looper) {
        mService = service;
        mMainLooper = looper;
        mH = new H(looper);
        mIInputContext = new ControlledInputConnectionWrapper(looper,
                mDummyInputConnection, this);
    }
}
public static final String INPUT_METHOD_SERVICE = "input_method";
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容