Android InputMethod 源码分析,显示输入法流程

1.简介

本文基于 android N,借鉴 http://blog.csdn.net/huangyabin001/article/details/28434989
,记录一下输入法显示的流程,相当于一篇读书笔记,方便记忆与学习

大体流程如下:

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

2. InputMethodManager 创建

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

// WindowManagerGlobal.java
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    // 生成 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;
        }
    }

// InputMethodManager.java
    /**
     * Retrieve the global InputMethodManager instance, creating it if it
     * doesn't already exist.
     * @hide
     */
    public static InputMethodManager getInstance() {
        synchronized (InputMethodManager.class) {
            if (sInstance == null) {
                IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
                IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
                sInstance = new InputMethodManager(service, Looper.getMainLooper());
            }
            return sInstance;
        }
    }

3. window 获得焦点

这里写图片描述
流程图上传后不太清晰,在csdn中上传了原图:http://download.csdn.net/detail/jieqiong1/9835293

// WindowManagerService.java
    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
        // 计算焦点 window
        WindowState newFocus = computeFocusedWindowLocked();
        if (mCurrentFocus != newFocus) {
            Trace.traceBegin(Trace.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);
            mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
            ...
            return true;
        }
        return false;
    }

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

    // 找出 top 需要获得焦点的 window
    WindowState findFocusedWindowLocked(DisplayContent displayContent) {
        final WindowList windows = displayContent.getWindowList();
        for (int i = windows.size() - 1; i >= 0; i--) {
            final WindowState win = windows.get(i);

            if (localLOGV || DEBUG_FOCUS) Slog.v(
                TAG_WM, "Looking for focus: " + i
                + " = " + win
                + ", flags=" + win.mAttrs.flags
                + ", canReceive=" + win.canReceiveKeys());

            // 判断 window 是否可以获取焦点
            if (!win.canReceiveKeys()) {
                continue;
            }

            // win.mAppToken != null win描述的是一个Activity窗口
            AppWindowToken wtoken = win.mAppToken;

            // If this window's application has been removed, just skip it.
            if (wtoken != null && (wtoken.removed || wtoken.sendingToBottom)) {
                if (DEBUG_FOCUS) Slog.v(TAG_WM, "Skipping " + wtoken + " because "
                        + (wtoken.removed ? "removed" : "sendingToBottom"));
                continue;
            }

            // Descend through all of the app tokens and find the first that either matches
            // win.mAppToken (return win) or mFocusedApp (return null).
            // mFocusedApp 是 top Activity,下边的逻辑是为了确保焦点window的app 必须是焦点程序上的,主要是为了检测出错误
            if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING &&
                    mFocusedApp != null) {
                ArrayList<Task> tasks = displayContent.getTasks();
                for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
                    AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
                    int tokenNdx = tokens.size() - 1;
                    for ( ; tokenNdx >= 0; --tokenNdx) {
                        final AppWindowToken token = tokens.get(tokenNdx);
                        if (wtoken == token) {
                            break;
                        }
                        if (mFocusedApp == token && token.windowsAreFocusable()) {
                            // Whoops, we are below the focused app whose windows are focusable...
                            // No focus for you!!!
                            if (localLOGV || DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM,
                                    "findFocusedWindow: Reached focused app=" + mFocusedApp);
                            return null;
                        }
                    }
                    if (tokenNdx >= 0) {
                        // Early exit from loop, must have found the matching token.
                        break;
                    }
                }
            }

            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: Found new focus @ " + i +
                        " = " + win);
            return win;
        }

        if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: No focusable windows.");
        return null;
    }





    @Override
    public void handleMessage(Message msg) {
        if (DEBUG_WINDOW_TRACE) {
            Slog.v(TAG_WM, "handleMessage: entry what=" + msg.what);
        }
        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() == Display.DEFAULT_DISPLAY) {
                        accessibilityController = mAccessibilityController;
                    }

                    lastFocus = mLastFocus;
                    newFocus = mCurrentFocus;
                    if (lastFocus == newFocus) {
                        // Focus is not changing, so nothing to do.
                        return;
                    }
                    mLastFocus = newFocus;
                    if (DEBUG_FOCUS_LIGHT) Slog.i(TAG_WM, "Focus moving from " + lastFocus +
                            " to " + newFocus);
                    if (newFocus != null && lastFocus != null
                            && !newFocus.isDisplayedLw()) {
                        //Slog.i(TAG_WM, "Delaying loss of focus...");
                        mLosingFocus.add(lastFocus);
                        lastFocus = null;
                    }
                }

                // 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);
                    // 通知新的焦点程序 获得了焦点
                    newFocus.reportFocusChangedSerialized(true, mInTouchMode);
                    notifyFocusChanged();
                }

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


// WindowState.java
    /**
     * Report a focus change.  Must be called with no locks held, and consistently
     * from the same serialized thread (such as dispatched from a handler).
     */
    public void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
        try {
            // 通过 Binder 告知 client端 其获得或失去了焦点
            mClient.windowFocusChanged(focused, inTouchMode);
        } catch (RemoteException e) {
        }
        if (mFocusCallbacks != null) {
            final int N = mFocusCallbacks.beginBroadcast();
            for (int i=0; i<N; i++) {
                IWindowFocusObserver obs = mFocusCallbacks.getBroadcastItem(i);
                try {
                    if (focused) {
                        obs.focusGained(mWindowId.asBinder());
                    } else {
                        obs.focusLost(mWindowId.asBinder());
                    }
                } catch (RemoteException e) {
                }
            }
            mFocusCallbacks.finishBroadcast();
        }
    }

4.程序变更焦点,程序获得焦点变更事件

这里写图片描述

// ViewRootImpl.java
    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);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_WINDOW_FOCUS_CHANGED: {
                if (mAdded) {
                    boolean hasWindowFocus = msg.arg1 != 0;
                    mAttachInfo.mHasWindowFocus = hasWindowFocus;

                    profileRendering(hasWindowFocus);

                    if (hasWindowFocus) {
                        ...
                    }

                    mLastWasImTarget = WindowManager.LayoutParams
                            .mayUseInputMethod(mWindowAttributes.flags);

                    InputMethodManager imm = InputMethodManager.peekInstance();
                    if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                        imm.onPreWindowFocus(mView, hasWindowFocus);
                    }
                    if (mView != null) {
                        mAttachInfo.mKeyDispatchState.reset();
                        // 6.1 调用根 view的 dispatchWindowFocusChanged(),通知view程序获得焦点
                        mView.dispatchWindowFocusChanged(hasWindowFocus);
                        mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
                    }

                    // 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()) {
                            // 6.2 通知 InputMethodManager 该 window 获得焦点
                            imm.onPostWindowFocus(mView, mView.findFocus(),
                                    mWindowAttributes.softInputMode,
                                    !mHasHadWindowFocus, mWindowAttributes.flags);
                        }
                        // Clear the forward bit.  We can just do this directly, since
                        // the window manager doesn't care about it.
                        mWindowAttributes.softInputMode &=
                                ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
                        ((WindowManager.LayoutParams)mView.getLayoutParams())
                                .softInputMode &=
                                    ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
                        mHasHadWindowFocus = true;
                    }
                }
            } break;
        ...
        }

    }

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

6.1 之后的流程
这里写图片描述

// ViewGroup.java
    @Override
    public void dispatchWindowFocusChanged(boolean hasFocus) {
        super.dispatchWindowFocusChanged(hasFocus);
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            children[i].dispatchWindowFocusChanged(hasFocus);
        }
    }
// View.java
    /**
     * Called when the window containing this view gains or loses window focus.
     * ViewGroups should override to route to their children.
     *
     * @param hasFocus True if the window containing this view now has focus,
     *        false otherwise.
     */
    public void dispatchWindowFocusChanged(boolean hasFocus) {
        onWindowFocusChanged(hasFocus);
    }

    /**
     * Called when the window containing this view gains or loses focus.  Note
     * that this is separate from view focus: to receive key events, both
     * your view and its window must have focus.  If a window is displayed
     * on top of yours that takes input focus, then your own window will lose
     * focus but the view focus will remain unchanged.
     *
     * @param hasWindowFocus True if the window containing this view now has
     *        focus, false otherwise.
     */
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        InputMethodManager imm = InputMethodManager.peekInstance();
        if (!hasWindowFocus) {
            if (isPressed()) {
                setPressed(false);
            }
            if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
                imm.focusOut(this);
            }
            removeLongPressCallback();
            removeTapCallback();
            onFocusLost();
        } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
            // 获得焦点的 view 通过 InputMethodManager 向 Service 通知自己获得焦点
            imm.focusIn(this);
        }
        refreshDrawableState();
    }


// InputMethodManager.java
    /**
     * Call this when a view receives focus.
     * @hide
     */
    public void focusIn(View view) {
        synchronized (mH) {
            focusInLocked(view);
        }
    }


// InputMethodManager.java
    /**
     * Call this when a view receives focus.
     * @hide
     */
    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");
  • 11
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值