Android輸入法框架IMMS

1.前言

我們知道輸入法InputMethodService和三方app分屬于不同進程,彼此不知道對方存在,但是他們又在輸入過程中,有交互,他們是怎么建聯(lián)系的,是誰把他們撮合起來的,圖1.1已經(jīng)給出了答案是InputMethodManagerService,整個撮合過程是我們要分析的。在后面講解中IMMS 就代表InputMethodManagerService輸入法管理者,IME代表InputMethodService輸入法,WMS 代表WindowManagerService窗口管理者,下面代碼分析是基于Android8.1


圖1.1

2.窗口建檔備案(IInputMethodClient)

IInputMethodClient是一個Aidl接口,是供InputMethodManagerService調(diào),獲取app和傳遞給app一些參數(shù)用的

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調(diào)用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側(cè)干了些什么?是怎么和IMMS(InputMethodManagerService)聯(lián)系起來了的

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,調(diào)用他的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中,從這里可以看出每個窗口在創(chuàng)建起初,不管有沒有Edittext焦點,IMMS就會給每個窗口存一個以client為key的ClientState為值得價值對。這樣InputMethodManagerService調(diào)用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前面一節(jié)只是app和IMMS有了聯(lián)系,這一節(jié)是要IMS與IMMS建立聯(lián)系,當我們啟動一個activity既添加一個窗口到WMS或則退出一個activity,都會導致MWS端的窗口排序發(fā)生變化,WMS經(jīng)過計算得出當先誰是聚焦窗口,然后通過遠程調(diào)用 W的windowFocusChanged,最后調(diào)到了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最后調(diào)用到60行,我們看到熟悉的InputConnection和EditorInfo就是這時候收集發(fā)送到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 {
                    //遠程調(diào)用到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遠程調(diào)用到MMS,被遠程調(diào)用過來后經(jīng)過一系列調(diào)用最終到了bindCurrentInputMethodService方法,關(guān)鍵代碼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) {
        //根據(jù)客戶端傳來IInputMethodClient獲取第2節(jié)的備案的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) {
        ...
       //設(shè)置為當前client,后面創(chuàng)建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里面看到創(chuàng)建IInputMethodWrapper這個Binder,并且返回了它,因為3.3節(jié)47行綁定時傳的參數(shù)ServiceConnection conn是InputMethodManagerService this,所以綁定之后回調(diào)會回調(diào)到3.3節(jié)的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.創(chuàng)建IInputMethodSession

4.1在3.3節(jié)綁定完畢后,IMMS中onServiceConnected就擁有了IInputMethodWrapper,在這個函數(shù)中有調(diào)用了requestClientSessionLocked這個函數(shù),他的作用是創(chuàng)建圖1.1的5號鏈接,咱們分析代碼,最終是調(diào)用21行method.createSession(...)這里method既3.4節(jié)創(chuàng)建的IInputMethodWrapper,這個Binder可以調(diào)用到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;
              ...
              //關(guān)鍵代碼調(diào)用到了IME注意這里的IInputSessionCallback就下面的MethodCallback也是一個Bider,發(fā)給IME
              //IME創(chuàng)建好了IInputMethodSession,通過MethodCallback發(fā)送回來,
              //為什么要多此一舉,找個回調(diào)MethodCallback呢?直接返回不好嗎?
              //這樣做是為了防止阻塞,直接會返回存在問題是的IMMS會一直阻塞到這,直到返回,加了回調(diào)我只要發(fā)過去就得了,你準備好了再發(fā)給我
              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經(jīng)過上一步4.1 27行調(diào)用,就會調(diào)用到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;
               //關(guān)鍵代碼
                inputMethod.createSession(new InputMethodSessionCallbackWrapper(
                        mContext, (InputChannel)args.arg1,
                        (IInputSessionCallback)args.arg2));
                args.recycle();
                return;
            }
           ...
    }

}

4.3第6行createSession的主要功能是創(chuàng)建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創(chuàng)建好了InputMethodSessionImpl,通過callback的sessionCreated傳遞給IMMS 既7行,onSessionCreated只是做了一個Handler轉(zhuǎn)發(fā),需要注意的是mCurClient這個成員變量,是怎么確定的,因為要通過mCurClient把InputBindResult res傳遞客戶端app,要傳遞給哪個客戶端,我們前面3.3 節(jié)26行根據(jù)當前聚焦窗口就確定mCurClient,這里的mCurClient就是前面2.4節(jié)建檔傳來的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的后續(xù)處理上代碼,36行res.method就是從IMMS傳過來的IInputMethodSession,也就是IMS里創(chuàng)建的既4.3節(jié)13行創(chuàng)建的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里創(chuàng)建的既4.3節(jié)13行創(chuàng)建的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里創(chuàng)建的既4.3節(jié)13行創(chuàng)建的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節(jié)的第2行最終調(diào)到下面第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節(jié)的中16行中imm.getInputContext()會創(chuàng)建IInputContext,看代碼mIInputContext是在InputMethodManager構(gòu)造函數(shù)中初始化的,第11行傳入的參數(shù)是mDummyInputConnection,而mDummyInputConnection是BaseInputConnection看到他的mTargetView為null,那么他是不是輸入法提交內(nèi)容時就沒法顯示了,那么他是如何顯示上屏內(nèi)容的?留疑問繼續(xù)看看圖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節(jié)窗口建檔的結(jié)果就是在IMMS 里面存了一個價值對2.5節(jié)10行 值為 ClientState(client, inputContext, uid, pid)里面保存的inputContext就是5.1節(jié)第11行創(chuàng)建的ControlledInputConnectionWrapper

5.3我們知道最終inputContext要傳遞給IME,接下我們看看他是如何傳遞給IME的,IME創(chuàng)建完成InputMethodSession回調(diào)在4.4節(jié)第17行IMMS的onSessionCreated方法中,然后調(diào)用第28行attachNewInputLocked,看看attachNewInputLocked做了什么。第9行開始往IME傳遞了,第28行IInputMethod就是綁定IME得到的IInputMethodWrapper (具體3.3節(jié)74行),bindInput()之后就進入了IME,這里mCurClient.binding具體看建檔環(huán)節(jié)2.5節(jié)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節(jié)的第2行最終調(diào)到下面第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.總結(jié)

圖6.1

1.當我們要顯示一個窗口時,首先會把窗口添加到WMS,在添加過程會發(fā)生openSession遠程IPC,IInputContext和IInputMethodClient就會傳遞給WMS

2.WMS會把上面?zhèn)z兄弟添加到IMMS鍵檔,到此圖1.1的2號鏈接建立

3.當我們把窗口添加到WMS,多了一個窗口WMS肯定要計算各窗口z軸值然后排序,如果這個窗口的z值計算出最大既最頂層,那么就WMS會調(diào)到當前窗口的windowFocusChanged

4.當前窗口會遍歷所用view,計算出哪個view是聚焦view后,通過startInputOrWindowGainedFocus遠程調(diào)用IMMS

5.在IMMS綁定IMS

6.綁定返回到IMMS的回調(diào)方法中,IMS會傳過來IInputMethodWrapper,到這里圖1.1的4號鏈接建立

7.緊接著IMMS會利用上面創(chuàng)建好的4號線,通知IME創(chuàng)建IInputMethodSession

8.創(chuàng)建好之后,把IInputMethodSession傳到IMMS

9.在把IInputMethodSession返回到客戶端app之前,利用IInputMethodWrapper,把建檔時候傳來的IInputContext,通過bindInput傳遞給IME這個就是我們經(jīng)常使用的IC InputConnection了至此圖1.1 的6號線路建立好

10.把第8步已經(jīng)創(chuàng)建好的InputMethodSession,利用之前建檔建立好的2線路傳回app,圖1.1的5號線路建立好,到此時六條線路全部建立好

7 .疑問

1號和3號線路,怎么沒有看到建立過程,這與Android服務機制有有關(guān),他們是一個鏈接,都是InputMethodManager,因為IMMS是公開服務,它注冊到ServiceManager服務注冊中心,所以通過第5行代碼就可以把名字(input_method)給到到服務注冊中心獲取1號和3號鏈接,這有點類似中介(IMMS)和房東(IME)和租客(APP)之間的關(guān)系,租客和房東不知道彼此,倆者都會通過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";
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容