Android9.0输入法框架(1)

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

输入法系统的整个框架
Android9.0输入法框架(1)_第1张图片
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;
GORDON
        mWindowSession = WindowManagerGlobal.getWindowSession();

   public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
GORDON 这个进程的InputMethodManager实例就生成了 
                    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;
        }
    }

    public static InputMethodManager getInstance() {
        synchronized (InputMethodManager.class) {
            if (sInstance == null) {
                try {
                    sInstance = new InputMethodManager(Looper.getMainLooper());
                } catch (ServiceNotFoundException e) {
                    throw new IllegalStateException(e);
                }
            }
            return sInstance;
        }
    }

    InputMethodManager(Looper looper) throws ServiceNotFoundException {
GORDON  InputMethodManager其实就是一个Binder InputMethodManagerService的proxy  
        this(IInputMethodManager.Stub.asInterface(
                ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)), looper);
    }

Client程序的Window获得焦点
时序图
Android9.0输入法框架(1)_第2张图片
系统WindowManagerService更新焦点window
哪个程序获得焦点是由系统决定的,是由WindowManagerService决定的,当系统的window状态发生变化时(比如window新增,删除)就会调用函数updateFocusedWindowLocked来更新焦点window。
WindowManagerService.java

// TODO: Move to DisplayContent
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
GORDON 计算焦点
    WindowState newFocus = mRoot.computeFocusedWindow();
    if (mCurrentFocus != newFocus) {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
        // This check makes sure that we don't already have the focus
        // change message pending.
        mH.removeMessages(H.REPORT_FOCUS_CHANGE);
GORDON 焦点发现改变时发送message REPORT_FOCUS_CHANGE
        mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);

WindowState computeFocusedWindow() {
    // While the keyguard is showing, we must focus anything besides the main display.
    // Otherwise we risk input not going to the keyguard when the user expects it to.
    final boolean forceDefaultDisplay = mService.isKeyguardShowingAndNotOccluded();
    for (int i = mChildren.size() - 1; i >= 0; i--) {
        final DisplayContent dc = mChildren.get(i);
        GORDON
        final WindowState win = dc.findFocusedWindow();
        if (win != null) {
            if (forceDefaultDisplay && !dc.isDefaultDisplay) {
                EventLog.writeEvent(0x534e4554, "71786287", win.mOwnerUid, "");
                continue;
            }
            return win;
        }
    }
    return null;
}

WindowState findFocusedWindow() {
    mTmpWindow = null;
    GORDON
    forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);
    if (mTmpWindow == null) {
        if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows.");
        return null;
    }
    GORDON  mTmpWindow为找到的焦点window
    return mTmpWindow;
}

@Override
boolean forAllWindows(ToBooleanFunction callback, boolean traverseTopToBottom) {
    // Special handling so we can process IME windows with #forAllImeWindows above their IME
    // target, or here in order if there isn't an IME target.
    if (traverseTopToBottom) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final DisplayChildWindowContainer child = mChildren.get(i);
            if (skipTraverseChild(child)) {
                continue;
            }

            if (child.forAllWindows(callback, traverseTopToBottom)) {
                return true;
            }
        }
    } else {
        final int count = mChildren.size();
        for (int i = 0; i < count; i++) {
            final DisplayChildWindowContainer child = mChildren.get(i);
            if (skipTraverseChild(child)) {
                continue;
            }

            if (child.forAllWindows(callback, traverseTopToBottom)) {
                return true;
            }
        }
    }
    return false;
}

系统通知程序端哪个window获得了焦点

final class H extends android.os.Handler {
    @Override
    public void handleMessage(Message msg) {

        switch (msg.what) {
            case REPORT_FOCUS_CHANGE: {
                WindowState lastFocus;
                WindowState newFocus;

                AccessibilityController accessibilityController = null;
                synchronized(mWindowMap) {
                    // TODO(multidisplay): Accessibility supported only of default desiplay.
                    if (mAccessibilityController != null && getDefaultDisplayContentLocked()
                            .getDisplayId() == DEFAULT_DISPLAY) {
                        accessibilityController = mAccessibilityController;
                    }

                    lastFocus = mLastFocus;
                    newFocus = mCurrentFocus;

                // First notify the accessibility manager for the change so it has
                // the windows before the newly focused one starts firing eventgs.
                if (accessibilityController != null) {
                    accessibilityController.onWindowFocusChangedNotLocked();
                }

                //System.out.println("Changing focus from " + lastFocus + " to " + newFocus);
                if (newFocus != null) {
                    if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Gaining focus: " + newFocus);
                    GORDON 通知新的焦点程序获得了焦点
                    newFocus.reportFocusChangedSerialized(true, mInTouchMode);
                    notifyFocusChanged();
                }

                if (lastFocus != null) {
                    if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Losing focus: " + lastFocus);
                    GORDON 通知旧的焦点程序失去了焦点
                    lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
                }
            } break;

void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
    try {
    GORDON 会调回到ViewRootImpl中的W实例
        mClient.windowFocusChanged(focused, inTouchMode);
    } catch (RemoteException e) {
    }
    if (mFocusCallbacks != null) {
        final int N = mFocusCallbacks.beginBroadcast();
        for (int i=0; i

Client程序获得焦点改变事件

public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
    synchronized (this) {
        mWindowFocusChanged = true;
        mUpcomingWindowFocus = hasFocus;
        mUpcomingInTouchMode = inTouchMode;
    }
    Message msg = Message.obtain();
    msg.what = MSG_WINDOW_FOCUS_CHANGED;
    mHandler.sendMessage(msg);
}

@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
    ...
      case MSG_WINDOW_FOCUS_CHANGED: {
          handleWindowFocusChanged();
      } break;

private void handleWindowFocusChanged() {
...
    InputMethodManager imm = InputMethodManager.peekInstance();
    if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
        imm.onPreWindowFocus(mView, hasWindowFocus);
    }
    if (mView != null) {
        mAttachInfo.mKeyDispatchState.reset();
        GORDON 通知view获得了焦点
        mView.dispatchWindowFocusChanged(hasWindowFocus);
        mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
        if (mAttachInfo.mTooltipHost != null) {
            mAttachInfo.mTooltipHost.hideTooltip();
        }
    }

    // Note: must be done after the focus change callbacks,
    // so all of the view state is set up correctly.
    if (hasWindowFocus) {
        if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
        GORDON 通知IMM该window获得了焦点
            imm.onPostWindowFocus(mView, mView.findFocus(),
                    mWindowAttributes.softInputMode,
                    !mHasHadWindowFocus, mWindowAttributes.flags);
        }

View.java
public void dispatchWindowFocusChanged(boolean hasFocus) {
    onWindowFocusChanged(hasFocus);
}

public void onWindowFocusChanged(boolean hasWindowFocus) {
    InputMethodManager imm = InputMethodManager.peekInstance();
    if (!hasWindowFocus) {
        if (isPressed()) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
            imm.focusOut(this);
        }
        removeLongPressCallback();
        removeTapCallback();
        onFocusLost();
    } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
    GORDON 获得焦点的view通过InputMethodManager通知IMMS自己获得了焦点
        imm.focusIn(this);
    }
    refreshDrawableState();
}

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

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

    void focusInLocked(View view) {
        if (DEBUG) Log.v(TAG, "focusIn: " + dumpViewInfo(view));

        if (view != null && view.isTemporarilyDetached()) {
            // This is a request from a view that is temporarily detached from a window.
            if (DEBUG) Log.v(TAG, "Temporarily detached view, ignoring");
            return;
        }
        if (mCurRootView != view.getRootView()) {
            // This is a request from a window that isn't in the window with
            // IME focus, so ignore it.
            if (DEBUG) Log.v(TAG, "Not IME target window, ignoring");
            return;
        }
GORDON 保存焦点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);
    }
}

final class ViewRootHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_CHECK_FOCUS: {
                InputMethodManager imm = InputMethodManager.peekInstance();
                if (imm != null) {
                    imm.checkFocus();
                }
            } break;

InputMethodManager.java

public void checkFocus() {
    if (checkFocusNoStartInput(false)) {
        startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
    }
}

boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
        IBinder windowGainingFocus, int controlFlags, int softInputMode,
        int windowFlags) {
    final View view;
    synchronized (mH) {
    GORDON 之前保存的有焦点的view对象
        view = mServedView;

        // Make sure we have a window token for the served view.
        if (DEBUG) {
            Log.v(TAG, "Starting input: view=" + dumpViewInfo(view) +
                    " reason=" + InputMethodClient.getStartInputReason(startInputReason));
        }
        if (view == null) {
            if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
            return false;
        }
    }

    // Now we need to get an input connection from the served view.
    // This is complicated in a couple ways: we can't be holding our lock
    // when calling out to the view, and we need to make sure we call into
    // the view on the same thread that is driving its view hierarchy.
    Handler vh = view.getHandler();
    if (vh == null) {
        // If the view doesn't have a handler, something has changed out
        // from under us, so just close the current input.
        // If we don't close the current input, the current input method can remain on the
        // screen without a connection.
        if (DEBUG) Log.v(TAG, "ABORT input: no handler for view! Close current input.");
        closeCurrentInput();
        return false;
    }
    if (vh.getLooper() != Looper.myLooper()) {
        // The view is running on a different thread than our own, so
        // we need to reschedule our work for over there.
        if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
        vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0));
        return false;
    }

    // Okay we are now ready to call into the served view and have it
    // do its stuff.
    // Life is good: let's hook everything up!
    EditorInfo tba = new EditorInfo();
    // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
    // system can verify the consistency between the uid of this process and package name passed
    // from here. See comment of Context#getOpPackageName() for details.
    tba.packageName = view.getContext().getOpPackageName();
    tba.fieldId = view.getId();
GORDON 创建数据通信接口,这个接口connection会传递到InputMethodService,后面输入法也会将字符等通过这个接口传回给view
    InputConnection ic = view.onCreateInputConnection(tba);
    if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);

    synchronized (mH) {
        // Now that we are locked again, validate that our state hasn't
        // changed.
        if (mServedView != view || !mServedConnecting) {
            // Something else happened, so abort.
            if (DEBUG) Log.v(TAG,
                    "Starting input: finished by someone else. view=" + dumpViewInfo(view)
                    + " mServedView=" + dumpViewInfo(mServedView)
                    + " mServedConnecting=" + mServedConnecting);
            return false;
        }

        // If we already have a text box, then this view is already
        // connected so we want to restart it.
        if (mCurrentTextBoxAttribute == null) {
            controlFlags |= CONTROL_START_INITIAL;
        }

        // Hook 'em up and let 'er rip.
        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();
            }
GORDON ControlledInputConnectionWrapper是对InputConnection的进一步封装实现跨进程通信
            servedContext = new ControlledInputConnectionWrapper(
                    icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
        } else {
            servedContext = null;
            missingMethodFlags = 0;
        }
        mServedInputConnectionWrapper = servedContext;

        try {
            if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
                    + ic + " tba=" + tba + " controlFlags=#"
                    + Integer.toHexString(controlFlags));
GORDON 通知IMMS 程序view获得了焦点,请求将view和输入法绑定
            final InputBindResult res = mService.startInputOrWindowGainedFocus(
                    startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
                    windowFlags, tba, servedContext, missingMethodFlags,
                    view.getContext().getApplicationInfo().targetSdkVersion);
            if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
            if (res == null) {
                Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
                        + " null. startInputReason="
                        + InputMethodClient.getStartInputReason(startInputReason)
                        + " editorInfo=" + tba
                        + " controlFlags=#" + Integer.toHexString(controlFlags));
                return false;
            }
            if (res.id != null) {
                setInputChannelLocked(res.channel);
                mBindSequence = res.sequence;
                mCurMethod = res.method;
                mCurId = res.id;
                mNextUserActionNotificationSequenceNumber =
                        res.userActionNotificationSequenceNumber;
            } else if (res.channel != null && res.channel != mCurChannel) {
                res.channel.dispose();
            }
            switch (res.result) {
                case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
                    mRestartOnNextWindowFocus = true;
                    break;
            }
            if (mCurMethod != null && mCompletions != null) {
                try {
                    mCurMethod.displayCompletions(mCompletions);
                } catch (RemoteException e) {
                }
            }
        } catch (RemoteException e) {
            Log.w(TAG, "IME died: " + mCurId, e);
        }
    }
    return true;
}

IMMS处理view绑定输入法事件
1、启动输入法service
2、绑定输入法window的token
3、请求输入法为焦点程序创建一个连接会话
4、将输入法的接口传递回程序client端
5、绑定输入法和焦点view
1-4是和程序相关的,而5是和view相关的。所以你可以说1~4是用来绑定程序window和输入法,而5是用来绑定程序view和输入法。

输入法还没启动:
弹出输入法会经过1-5
输入法已经启动:
1、但是焦点window发生变化时会经历3-5
2、焦点window没有变化,只是改变了焦点view,则只会经历5

InputMethodManagerService.java

    @Override
    public InputBindResult startInputOrWindowGainedFocus(
            /* @InputMethodClient.StartInputReason */ final int startInputReason,
            IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
            int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
            /* @InputConnectionInspector.missingMethods */ final int missingMethods,
            int unverifiedTargetSdkVersion) {
        final InputBindResult result;
        if (windowToken != null) {
            result = windowGainedFocus(startInputReason, client, windowToken, controlFlags,
                    softInputMode, windowFlags, attribute, inputContext, missingMethods,
                    unverifiedTargetSdkVersion);
        } else {
        GORDON 执行view和输入法的绑定操作 执行步骤1、启动输入法service
            result = startInput(startInputReason, client, inputContext, missingMethods, attribute,
                    controlFlags);
        }
        if (result == null) {
            // This must never happen, but just in case.
            Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
                    + InputMethodClient.getStartInputReason(startInputReason)
                    + " windowFlags=#" + Integer.toHexString(windowFlags)
                    + " editorInfo=" + attribute);
            return InputBindResult.NULL;
        }
        return result;
    }

启动输入法service InputMethodService

@NonNull
private InputBindResult startInput(
        /* @InputMethodClient.StartInputReason */ final int startInputReason,
        IInputMethodClient client, IInputContext inputContext,
        /* @InputConnectionInspector.missingMethods */ final int missingMethods,
        @Nullable EditorInfo attribute, int controlFlags) {
    if (!calledFromValidUser()) {
        return InputBindResult.INVALID_USER;
    }
    synchronized (mMethodMap) {
        if (DEBUG) {
            Slog.v(TAG, "startInput: reason="
                    + InputMethodClient.getStartInputReason(startInputReason)
                    + " client = " + client.asBinder()
                    + " inputContext=" + inputContext
                    + " missingMethods="
                    + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
                    + " attribute=" + attribute
                    + " controlFlags=#" + Integer.toHexString(controlFlags));
        }
        final long ident = Binder.clearCallingIdentity();
        try {
            return startInputLocked(startInputReason, client, inputContext, missingMethods,
                    attribute, controlFlags);
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }
}


@GuardedBy("mMethodMap")
@NonNull
InputBindResult startInputLocked(
        /* @InputMethodClient.StartInputReason */ final int startInputReason,
        IInputMethodClient client, IInputContext inputContext,
        /* @InputConnectionInspector.missingMethods */ final int missingMethods,
        @Nullable EditorInfo attribute, int controlFlags) {
    // If no method is currently selected, do nothing.
    if (mCurMethodId == null) {
        return InputBindResult.NO_IME;
    }
GORDON client在service端对应的数据结构
    ClientState cs = mClients.get(client.asBinder());
    if (cs == null) {
        throw new IllegalArgumentException("unknown client "
                + client.asBinder());
    }

    if (attribute == null) {
        Slog.w(TAG, "Ignoring startInput with null EditorInfo."
                + " uid=" + cs.uid + " pid=" + cs.pid);
        return InputBindResult.NULL_EDITOR_INFO;
    }

    try {
        if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
            // Check with the window manager to make sure this client actually
            // has a window with focus.  If not, reject.  This is thread safe
            // because if the focus changes some time before or after, the
            // next client receiving focus that has any interest in input will
            // be calling through here after that change happens.
            if (DEBUG) {
                Slog.w(TAG, "Starting input on non-focused client " + cs.client
                        + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
            }
            return InputBindResult.NOT_IME_TARGET_WINDOW;
        }
    } catch (RemoteException e) {
    }

    return startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
            controlFlags, startInputReason);
}

启动输入法前的一系列检查
@GuardedBy("mMethodMap")
@NonNull
InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
        /* @InputConnectionInspector.missingMethods */ final int missingMethods,
        @NonNull EditorInfo attribute, int controlFlags,
        /* @InputMethodClient.StartInputReason */ final int startInputReason) {
    // If no method is currently selected, do nothing.
    if (mCurMethodId == null) {
        return InputBindResult.NO_IME;
    }

    if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
            attribute.packageName)) {
        Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
                + " uid=" + cs.uid + " package=" + attribute.packageName);
        return InputBindResult.INVALID_PACKAGE_NAME;
    }
GORDON 当前活动的程序和新的程序不同
    if (mCurClient != cs) {
        // Was the keyguard locked when switching over to the new client?
        mCurClientInKeyguard = isKeyguardLocked();
        // If the client is changing, we need to switch over to the new
        // one.
        unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
        if (DEBUG) Slog.v(TAG, "switching to client: client="
                + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);

        // If the screen is on, inform the new client it is active
        if (mIsInteractive) {
            executeOrSendMessage(cs.client, mCaller.obtainMessageIO(
                    MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, cs));
        }
    }

    // Bump up the sequence for this client and attach it.
    mCurSeq++;
    if (mCurSeq <= 0) mCurSeq = 1;
    mCurClient = cs;
    mCurInputContext = inputContext;
    mCurInputContextMissingMethods = missingMethods;
    mCurAttribute = attribute;

    // Check if the input method is changing.
    if (mCurId != null && mCurId.equals(mCurMethodId)) {
    GORDON 连接是否建立,直接绑定
        if (cs.curSession != null) {
            // Fast case: if we are already connected to the input method,
            // then just return it.
            return attachNewInputLocked(startInputReason,
                    (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
        }
        if (mHaveConnection) {
            if (mCurMethod != null) {
                // Return to client, and we will get back with it when
                // we have had a session made for it.
                GORDON 连接是否创建,如果创建了直接传递给client
                requestClientSessionLocked(cs);
                return new InputBindResult(
                        InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
                        null, null, mCurId, mCurSeq,
                        mCurUserActionNotificationSequenceNumber);
            } else if (SystemClock.uptimeMillis()
                    < (mLastBindTime+TIME_TO_RECONNECT)) {
                // In this case we have connected to the service, but
                // don't yet have its interface.  If it hasn't been too
                // long since we did the connection, we'll return to
                // the client and wait to get the service interface so
                // we can report back.  If it has been too long, we want
                // to fall through so we can try a disconnect/reconnect
                // to see if we can get back in touch with the service.
                return new InputBindResult(
                        InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
                        null, null, mCurId, mCurSeq,
                        mCurUserActionNotificationSequenceNumber);
            } else {
                EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
                        mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
            }
        }
    }
GORDON 如果都没有则需要启动输入法并创建连接
    return startInputInnerLocked();
}

InputBindResult startInputInnerLocked() {
    if (mCurMethodId == null) {
        return InputBindResult.NO_IME;
    }

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

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

    unbindCurrentMethodLocked(true);
GORDON 1、启动输入法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));
GORDON bindService
    if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
        mLastBindTime = SystemClock.uptimeMillis();
        mHaveConnection = true;
        mCurId = info.getId();
        GORDON 2、绑定输入法window的token,这个token是给输入法service用来绑定输入法的window的,通过这个token  
        InputMethodManagerService可以直接管理输入法的window  
        mCurToken = new Binder();
        try {
            if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
            mIWindowManager.addWindowToken(mCurToken, TYPE_INPUT_METHOD, DEFAULT_DISPLAY);
        } catch (RemoteException e) {
        }
        return new InputBindResult(
                InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
                null, null, mCurId, mCurSeq,
                mCurUserActionNotificationSequenceNumber);
    }
    mCurIntent = null;
    Slog.w(TAG, "Failure connecting to input method service: " + mCurIntent);
    return InputBindResult.IME_NOT_CONNECTED;
}

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

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

    public abstract AbstractInputMethodImpl onCreateInputMethodInterface();

GORDON InputMethodService.java
    @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())) {
            GORDON 保存输入法service传递过来的接口
                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);
GORDON 将刚刚创建的window token传递给输入法service,然后输入法用这个token创建window,这样IMMS可以用根据这个token找到输入法在IMMS里  
的数据及输入法window在WMS里的数据  
                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
                if (mCurClient != null) {
                    clearClientSessionLocked(mCurClient);
                    requestClientSessionLocked(mCurClient);
                }
            }
        }
    }

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

 IMMS在输入法启动完成并回调onServiceConnected时会将一个Window token传递给输入法。
         case MSG_ATTACH_TOKEN:
            args = (SomeArgs)msg.obj;
            try {
                if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
                GORDON 调用输入法方法
                ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
            } catch (RemoteException e) {
            }
            args.recycle();
            return true;

public class InputMethodService extends AbstractInputMethodService {  
public class InputMethodImpl extends AbstractInputMethodImpl {  
        @Override
        public void attachToken(IBinder token) {
            if (mToken == null) {
                mToken = token;
                GORDON 这样输入法的window就绑定这个window token
                mWindow.setToken(token);
            }
        }

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

    public void requestHideSelf(int flags) {
        mImm.hideSoftInputFromInputMethodInternal(mToken, flags);
    }

    public void hideSoftInputFromInputMethodInternal(IBinder token, int flags) {
        try {
            mService.hideMySoftInput(token, flags);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
IMMS
    @Override
    public void hideMySoftInput(IBinder token, int flags) {
        if (!calledFromValidUser()) {
            return;
        }
        synchronized (mMethodMap) {
            if (!calledWithValidToken(token)) {
                return;
            }
            long ident = Binder.clearCallingIdentity();
            try {
                hideCurrentInputLocked(flags, null);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }
    }

输入法连接会话创建
回到IMMS onServiceConnected

    @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);
                    GORDON 
                    requestClientSessionLocked(mCurClient);
                }
            }
        }
    }
GORDON 创建view和输入法连接的session
    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])));
        }
    }

        case MSG_CREATE_SESSION: {
            args = (SomeArgs)msg.obj;
            IInputMethod method = (IInputMethod)args.arg1;
            InputChannel channel = (InputChannel)args.arg2;
            try {
            GORDON 输入法创建的session
                method.createSession(channel, (IInputSessionCallback)args.arg3);
            } catch (RemoteException e) {
            } finally {
                // Dispose the channel if the input method is not local to this process
                // because the remote proxy will get its own copy when unparceled.
                if (channel != null && Binder.isProxy(method)) {
                    channel.dispose();
                }
            }
            args.recycle();
            return true;
        }

IMS -> createSession

    public abstract class AbstractInputMethodImpl implements InputMethod {
        /**
         * Instantiate a new client session for the input method, by calling
         * back to {@link AbstractInputMethodService#onCreateInputMethodSessionInterface()
         * AbstractInputMethodService.onCreateInputMethodSessionInterface()}.
         */
        @MainThread
        public void createSession(SessionCallback callback) {
            callback.sessionCreated(onCreateInputMethodSessionInterface());
        }

最终调用IMMS 的onSessionCreated
    void onSessionCreated(IInputMethod method, IInputMethodSession session,
            InputChannel channel) {
        synchronized (mMethodMap) {
            if (mCurMethod != null && method != null
                    && mCurMethod.asBinder() == method.asBinder()) {
                if (mCurClient != null) {
                    clearClientSessionLocked(mCurClient);
                    mCurClient.curSession = new SessionState(mCurClient,
                            method, session, channel);
                    InputBindResult res = attachNewInputLocked(
                            InputMethodClient.START_INPUT_REASON_SESSION_CREATED_BY_IME, true);
                    if (res.method != null) {
                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
                                MSG_BIND_CLIENT, mCurClient.client, res));
                    }
                    return;
                }
            }
        }
        // Session abandoned.  Close its associated input channel.
        channel.dispose();
    }

传递输入法接口给程序

        case MSG_BIND_CLIENT: {
            args = (SomeArgs)msg.obj;
            IInputMethodClient client = (IInputMethodClient)args.arg1;
            InputBindResult res = (InputBindResult)args.arg2;
            try {
            GORDON 回调到client端
                client.onBindMethod(res);
            } catch (RemoteException e) {
                Slog.w(TAG, "Client died receiving input method " + args.arg2);
            } finally {
                // Dispose the channel if the input method is not local to this process
                // because the remote proxy will get its own copy when unparceled.
                if (res.channel != null && Binder.isProxy(client)) {
                    res.channel.dispose();
                }
            }
            args.recycle();
            return true;
        }

输入法和view绑定

IMMS
创建完session后,回调onSessionCreated输入法和view绑定
    void onSessionCreated(IInputMethod method, IInputMethodSession session,
            InputChannel channel) {
        synchronized (mMethodMap) {
            if (mCurMethod != null && method != null
                    && mCurMethod.asBinder() == method.asBinder()) {
                if (mCurClient != null) {
                    clearClientSessionLocked(mCurClient);
                    mCurClient.curSession = new SessionState(mCurClient,
                            method, session, channel);
                            GORDON 绑定
                    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();
    }

    @GuardedBy("mMethodMap")
    @NonNull
    InputBindResult attachNewInputLocked(
            /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
        if (!mBoundToMethod) {
        GORDON 如果没有绑定则绑定,否则执行下面操作
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
            mBoundToMethod = true;
        }

        final Binder startInputToken = new Binder();
        final StartInputInfo info = new StartInputInfo(mCurToken, mCurId, startInputReason,
                !initial, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,
                mCurSeq);
        mStartInputMap.put(startInputToken, info);
        mStartInputHistory.addEntry(info);

        final SessionState session = mCurClient.curSession;
        executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
                MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
                startInputToken, session, mCurInputContext, mCurAttribute));
        if (mShowRequested) {
            if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
            showCurrentInputLocked(getAppShowFlags(), null);
        }
        return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
                session.session, (session.channel != null ? session.channel.dup() : null),
                mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
    }
 GORDON 绑定输入法
            case MSG_BIND_INPUT:
                args = (SomeArgs)msg.obj;
                try {
                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
                } catch (RemoteException e) {
                }
                args.recycle();
                return true;
GORDON 启动输入法
         case MSG_START_INPUT: {
                final int missingMethods = msg.arg1;
                final boolean restarting = msg.arg2 != 0;
                args = (SomeArgs) msg.obj;
                final IBinder startInputToken = (IBinder) args.arg1;
                final SessionState session = (SessionState) args.arg2;
                final IInputContext inputContext = (IInputContext) args.arg3;
                final EditorInfo editorInfo = (EditorInfo) args.arg4;
                try {
                    setEnabledSessionInMainThread(session);
                    session.method.startInput(startInputToken, inputContext, missingMethods,
                            editorInfo, restarting);
                } catch (RemoteException e) {
                }
                args.recycle();
                return true;
            }

IMS
        @Override
        public void startInput(InputConnection ic, EditorInfo attribute) {
            if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
            doStartInput(ic, attribute, false);
        }

    void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
        if (!restarting) {
            doFinishInput();
        }
        mInputStarted = true;
        mStartedInputConnection = ic;
        mInputEditorInfo = attribute;
        initialize();
        if (DEBUG) Log.v(TAG, "CALL: onStartInput");
        onStartInput(attribute, restarting);
        if (mWindowVisible) {
            if (mShowInputRequested) {
                if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
                mInputViewStarted = true;
 GORDON 输入法需要在此实现view的内容
                onStartInputView(mInputEditorInfo, restarting);
                startExtractingText(true);
            } else if (mCandidatesVisibility == View.VISIBLE) {
                if (DEBUG) Log.v(TAG, "CALL: onStartCandidatesView");
                mCandidatesViewStarted = true;
                onStartCandidatesView(mInputEditorInfo, restarting);
            }
        }
    }

绑定完成了但是还没有显示输入法,系统会调用windowGainFocus来显示输入法
程序焦点获取事件导致输入法显示
从上面可以知道程序获得焦点时,程序端会先间接的调用IMMS的startInput将焦点View绑定到输入法,然后会调用IMMS的windowGainFocus函数,这个函数就可能显示输入法, 是否显示输入法由焦点view的属性决定。过程流程图如下
Android9.0输入法框架(1)_第3张图片

你可能感兴趣的:(系统框架,android,java,apache)