Android P输入法框架系统--view如何触发绑定

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

1、输入法整体框架图

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

2、InputMethodManager创建

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

//frameworks/base/core/java/android/view/ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {  
    mContext = context;  
    mWindowSession = WindowManagerGlobal.getWindowSession();  
}  

//frameworks/base/core/java/android/view/WindowManagerGlobal.java
public static IWindowSession getWindowSession() {  
    synchronized (WindowManagerGlobal.class) {  
        if (sWindowSession == null) {  
            try {  
                //这个进程的InputMethodManager实例就生成了  
                InputMethodManager imm = InputMethodManager.getInstance();  
                IWindowManager windowManager = getWindowManagerService();  
            } catch (RemoteException e) {  
                Log.e(TAG, "Failed to open window session", e);  
            }  
        }  
        return sWindowSession;  
    }  
}  
  
public static InputMethodManager getInstance() {  
    synchronized (InputMethodManager.class) {  
        if (sInstance == null) {  
            // InputMethodManager其实就是一个Binder service的proxy  
            IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);  
            IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);  
            sInstance = new InputMethodManager(service, Looper.getMainLooper());  
        }  
        return sInstance;  
    }  
}  

3、APP程序的Window获得焦点

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

//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {  
    //计算焦点window  ----见3.1
    WindowState newFocus = computeFocusedWindowLocked();  
    if (mCurrentFocus != newFocus) {  
        //焦点window发生变化,post一个message来通知程序焦点发生变化了  
        mH.removeMessages(H.REPORT_FOCUS_CHANGE);  
        mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE); //见3.2
        //省略.....
        return true;  
    }  
    return false;  
}  

3.1 焦点window计算

//frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java  
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);
        final WindowState win = dc.findFocusedWindow();
        if (win != null) {
            if (forceDefaultDisplay && !dc.isDefaultDisplay) {
                EventLog.writeEvent(0x534e4554, "71786287", win.mOwnerUid, "");
                continue;
            }
            return win;
        }
    }
    return null;
}

//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
WindowState findFocusedWindow() {
       mTmpWindow = null;

       forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);

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

//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
@Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
    if (mChildren.isEmpty()) {
        // The window has no children so we just return it.
        return applyInOrderWithImeWindows(callback, traverseTopToBottom);
    }

    if (traverseTopToBottom) {//因为traverseTopToBottom为true,故走这个分支
        return forAllWindowTopToBottom(callback);
    } else {
        return forAllWindowBottomToTop(callback);
    }
}

private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback,
       boolean traverseTopToBottom) {
   if (traverseTopToBottom) {
  //此处的callback实际上是传入的mFindFocusedWindow,故callback.apply(this)为回调
       if (applyImeWindowsIfNeeded(callback, traverseTopToBottom)
               || callback.apply(this)) {
           return true;
       }
   } else {
       if (callback.apply(this)
               || applyImeWindowsIfNeeded(callback, traverseTopToBottom)) {
           return true;
       }
   }
   return false;
}

//callback.apply(this)为回调函数实现
//frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
    final AppWindowToken focusedApp = mService.mFocusedApp;
    if (DEBUG_FOCUS) Slog.v(TAG_WM, "Looking for focus: " + w
            + ", flags=" + w.mAttrs.flags + ", canReceive=" + w.canReceiveKeys());

    if (!w.canReceiveKeys()) {
        return false;
    }

    final AppWindowToken wtoken = w.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"));
        return false;
    }

    if (focusedApp == null) {
        if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp=null"
                + " using new focus @ " + w);
        mTmpWindow = w;
        return true;
    }

    if (!focusedApp.windowsAreFocusable()) {
        // Current focused app windows aren't focusable...
        if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM, "findFocusedWindow: focusedApp windows not"
                + " focusable using new focus @ " + w);
        mTmpWindow = w;
        return true;
    }

    // Descend through all of the app tokens and find the first that either matches
    // win.mAppToken (return win) or mFocusedApp (return null).
    if (wtoken != null && w.mAttrs.type != TYPE_APPLICATION_STARTING) {
        if (focusedApp.compareTo(wtoken) > 0) {
            // App stack below focused app stack. No focus for you!!!
            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG_WM,
                    "findFocusedWindow: Reached focused app=" + focusedApp);
            mTmpWindow = null;
            return true;
        }
    }

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

3.2 焦点window切换

//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
//接下来系统开始通知程序端哪个window获得了焦点。    
final class H extends Handler {  
    @Override  
    public void handleMessage(Message msg) {  
        switch (msg.what) {  
            case REPORT_FOCUS_CHANGE: {  
                WindowState lastFocus;  
                WindowState newFocus;  
  
				//省略。。。
                if (newFocus != null) {  
                    //通知新的焦点程序其获得了焦点  
                    newFocus.reportFocusChangedSerialized(true, mInTouchMode);  
                    notifyFocusChanged();  
                }  
  
                if (lastFocus != null) {  
                    //通知老的焦点程序其获得了焦点  
                    lastFocus.reportFocusChangedSerialized(false, mInTouchMode);  
                }  
            } break;  
 }  
  
//frameworks/base/services/core/java/com/android/server/wm/WindowState.java  
 public void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {  
    try {  
        //这个就是通过Binder告知client其获得或失去了焦点  
        //mClient.windowFocusChanged会调回到ViewRootImpl中的W实例
        mClient.windowFocusChanged(focused, inTouchMode);  
    } catch (RemoteException e) {  
    }
    //省略。。。
}  

通知应用APP程序获得焦点改变事件

//ViewRootImpl.java  
static class W extends IWindow.Stub {  
    @Override  
    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {  
        final ViewRootImpl viewAncestor = mViewAncestor.get();  
        if (viewAncestor != null) {  
            viewAncestor.windowFocusChanged(hasFocus, inTouchMode);  
        }  
    }  
  
//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);  
}  
  
//ViewRootImpl.java  
//程序获得焦点会通过调用mView.dispatchWindowFocusChanged和  
//imm.onWindowFocus来通知IMMS焦点信息发生改变,需要更新输入法了  
final class ViewRootHandler extends Handler {  
    @Override  
    public void handleMessage(Message msg) {  
        switch (msg.what) {  
        case MSG_WINDOW_FOCUS_CHANGED: {  
            handleWindowFocusChanged();
        } break;  
}  
private void handleWindowFocusChanged() {
	//省略...
        if (mView != null) {
            mAttachInfo.mKeyDispatchState.reset();
			//调用根view的dispatchWindowFocusChanged函数通知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()) {
				//通知imm该window获得焦点
                imm.onPostWindowFocus(mView, mView.findFocus(),
                        mWindowAttributes.softInputMode,
                        !mHasHadWindowFocus, mWindowAttributes.flags);
            }
			//省略...
        }
	//省略...
}
   
//上面的根view就是DecorView,它只是调用父类ViewGroup  
//的dispatchWindowFocusChanged  
//ViewGroup.java  
@Override  
public void dispatchWindowFocusChanged(boolean hasFocus) {  
    super.dispatchWindowFocusChanged(hasFocus);  
    final int count = mChildrenCount;  
    final View[] children = mChildren;  
    //让每个子view处理window焦点改变时间  
    //但是只有获得焦点的view才会处理这个时间  
    for (int i = 0; i < count; i++) {  
        children[i].dispatchWindowFocusChanged(hasFocus);  
    }  
}  
 
//View.java 
public void dispatchWindowFocusChanged(boolean hasFocus) {
     onWindowFocusChanged(hasFocus);
 } 
 
public void onWindowFocusChanged(boolean hasWindowFocus) {  
    InputMethodManager imm = InputMethodManager.peekInstance();  
    if (!hasWindowFocus) {  
    } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {  
        //获得焦点的view通过 InputMethodManager向service通知自己获得焦点  
        imm.focusIn(this);  
    }  
}  

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

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

//frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java
public void focusIn(View view) {  
    synchronized (mH) {  
        focusInLocked(view);  
    }  
}  
  
void focusInLocked(View view) {   
    //保存焦点view变量  
    mNextServedView = view;  
    scheduleCheckFocusLocked(view);  
}  
  
static void scheduleCheckFocusLocked(View view) {  
    ViewRootImpl viewRootImpl = view.getViewRootImpl();  
    if (viewRootImpl != null) {  
        viewRootImpl.dispatchCheckFocus();  
    }  
}  

//frameworks/base/core/java/android/view/ViewRootImpl.java
public void dispatchCheckFocus() {  
    if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) {  
        // This will result in a call to checkFocus() below.  
        mHandler.sendEmptyMessage(MSG_CHECK_FOCUS);  
    }  
}  

public void handleMessage(Message msg) {
    switch (msg.what) {
    //省略。。。
        case MSG_CHECK_FOCUS: {  
            InputMethodManager imm = InputMethodManager.peekInstance();  
            if (imm != null) {  
                imm.checkFocus();  
            }  
        } break;  
    //省略。。。
}

//frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java  
public void checkFocus() {  
    if (checkFocusNoStartInput(false)) {  
        startInputInner(InputMethodClient.START_INPUT_REASON_CHECK_FOCUS, null, 0, 0, 0);
    }  
}  
  
//frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java
boolean startInputInner(@InputMethodClient.StartInputReason final int startInputReason,
        IBinder windowGainingFocus, int controlFlags, int softInputMode,
        int windowFlags) {
    final View view;
    synchronized (mH) {
		//获得上面的焦点view
        view = mServedView;
    }

	//省略。。。

	//创建数据通信连接接口,这个会传送到InputMethodService  
    //InputMethodService后面就通过这个connection将输入法的字符传递给该view  
    InputConnection ic = view.onCreateInputConnection(tba);
    if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);

    synchronized (mH) {
        //省略。。。
        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();
            }
			//将InputConnection封装为binder对象,这个是真正可以实现跨进程通  
            //信的封装类  
            servedContext = new ControlledInputConnectionWrapper(
                    icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this);
        } else {
            servedContext = null;
            missingMethodFlags = 0;
        }
        mServedInputConnectionWrapper = servedContext;

        try {
             //通知IMMS处理view绑定输入法事件
            final InputBindResult res = mService.startInputOrWindowGainedFocus(
                    startInputReason, mClient, windowGainingFocus, controlFlags, softInputMode,
                    windowFlags, tba, servedContext, missingMethodFlags,
                    view.getContext().getApplicationInfo().targetSdkVersion);
				//省略。。。
        } catch (RemoteException e) {
            Log.w(TAG, "IME died: " + mCurId, e);
        }
    }

    return true;
}  

到此view是如何获取焦点,并通知IMMS去绑定输入法的流程已经分析完成了。下一步将分析IMMS是如何处理view绑定输入法事件。未完待续。。。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值