android 鍵盤(pán)輸入流程

最近項(xiàng)目需要,需要做一個(gè)輸入驗(yàn)證碼的組件,就如同下圖展示的樣子。

image.png

每個(gè)驗(yàn)證碼數(shù)字是個(gè)獨(dú)立的view,不能通過(guò)一個(gè)editText來(lái)完成,基于這種實(shí)現(xiàn)就需要獲取到每次鍵盤(pán)點(diǎn)擊事件的內(nèi)容,還有監(jiān)聽(tīng)刪除事件,這樣才能實(shí)現(xiàn)每次輸入完成自動(dòng)將焦點(diǎn)放到后一個(gè)view。
https://github.com/yan010/VerificationCodeInputView
這里是一個(gè)我寫(xiě)的驗(yàn)證碼開(kāi)源庫(kù),大家可以直接使用,下面開(kāi)始給搭建講解具體的鍵盤(pán)輸入法輸入流程。
看到這樣的ui跟要求,大家的第一反應(yīng)可能就是監(jiān)聽(tīng)view的setOnKeyListener,但是經(jīng)過(guò)實(shí)驗(yàn)發(fā)現(xiàn)在原生鍵盤(pán)上不能監(jiān)聽(tīng)到刪除事件,這樣就會(huì)導(dǎo)致刪除后不能控制當(dāng)前的焦點(diǎn)。
這時(shí)需要找到一種方法通用的方法來(lái)獲取鍵盤(pán)的點(diǎn)擊事件。在尋找這種方法之前需要完整的來(lái)看一下整個(gè)鍵盤(pán)喚起輸入到關(guān)閉的整個(gè)流程。
喚起鍵盤(pán)通過(guò)InputMethodManager類的showSoftInput方法,那么就從這方法往下看,

public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
        checkFocus();
        synchronized (mH) {
            if (mServedView != view && (mServedView == null
                    || !mServedView.checkInputConnectionProxy(view))) {
                return false;
            }

            try {
                return mService.showSoftInput(mClient, flags, resultReceiver);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }
mService = IInputMethodManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));    

上面代碼mService是InputMethodManagerService的代理類,實(shí)現(xiàn)了binder機(jī)制,所以mService是binder的客戶端類,執(zhí)行showSoftInput,這時(shí)就要進(jìn)入InputMethodManagerService類來(lái)看在服務(wù)端是怎么執(zhí)行的

InputMethodManagerService.class
    @Override
    public boolean showSoftInput(IInputMethodClient client, int flags,
            ResultReceiver resultReceiver) {
        if (!calledFromValidUser()) {
            return false;
        }
        int uid = Binder.getCallingUid();
        long ident = Binder.clearCallingIdentity();
        try {
            synchronized (mMethodMap) {
                if (mCurClient == null || client == null
                        || mCurClient.client.asBinder() != client.asBinder()) {
                    try {
                        // We need to check if this is the current client with
                        // focus in the window manager, to allow this call to
                        // be made before input is started in it.
                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
                            return false;
                        }
                    } catch (RemoteException e) {
                        return false;
                    }
                }

                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
                return showCurrentInputLocked(flags, resultReceiver);
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
        ...
            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
                    resultReceiver));
           ...
    }

看到到這里會(huì)給handler發(fā)一條消息通知展示鍵盤(pán)。

case MSG_SHOW_SOFT_INPUT:
                args = (SomeArgs)msg.obj;
                try {
                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
                            + msg.arg1 + ", " + args.arg2 + ")");
                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
                } catch (RemoteException e) {
                }
                args.recycle();
                return true;

可以看到在handler中會(huì)執(zhí)行一個(gè)IInputMethod的showSoftInput方法,IInputMethod是個(gè)接口他的實(shí)例類是前邊的mCurMethod,

mCurMethod = IInputMethod.Stub.asInterface(service);

mCurMethod是IInputMethod的代理類,這時(shí)我們就要看InputMethodService類中的showSoftInput實(shí)現(xiàn)了。

/**
         * Handle a request by the system to show the soft input area.
         */
        public void showSoftInput(int flags, ResultReceiver resultReceiver) {
            if (DEBUG) Log.v(TAG, "showSoftInput()");
            boolean wasVis = isInputViewShown();
            if (dispatchOnShowInputRequested(flags, false)) {
                try {
                    showWindow(true);
                } catch (BadTokenException e) {
                }
            }
            clearInsetOfPreviousIme();
            // If user uses hard keyboard, IME button should always be shown.
            boolean showing = isInputViewShown();
            mImm.setImeWindowStatus(mToken, mStartInputToken,
                    IME_ACTIVE | (showing ? IME_VISIBLE : 0), mBackDisposition);
            if (resultReceiver != null) {
                resultReceiver.send(wasVis != isInputViewShown()
                        ? InputMethodManager.RESULT_SHOWN
                        : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN
                                : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
            }
        }

public void showWindow(boolean showInput) {
        ...
            showWindowInner(showInput);
        ...
    }

void showWindowInner(boolean showInput) {
       ...
        initialize();
        updateFullscreenMode();
        updateInputViewShown();
       ...
    }

public void updateInputViewShown() {
        boolean isShown = mShowInputRequested && onEvaluateInputViewShown();
        if (mIsInputViewShown != isShown && mWindowVisible) {
            mIsInputViewShown = isShown;
            mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE);
            if (mInputView == null) {
                initialize();
                View v = onCreateInputView();
                if (v != null) {
                    setInputView(v);
                }
            }
        }
    }

public View onCreateInputView() {
        return null;
    }

最終通過(guò)onCreateInputView方法返回一個(gè)view類,這個(gè)類在當(dāng)前類中是個(gè)空實(shí)現(xiàn),需要子類繼承并重寫(xiě)這個(gè)方法,這樣就展示出了一個(gè)鍵盤(pán)。
到這里鍵盤(pán)的創(chuàng)建過(guò)程已經(jīng)完成。
這時(shí)如果有點(diǎn)擊鍵盤(pán)的操作時(shí),事件是怎么完成的呢?
先看InputMethodService中獲取到鍵盤(pán)點(diǎn)擊事件的方法sendKeyChar

 public void sendKeyChar(char charCode) {
        switch (charCode) {
            case '\n': // Apps may be listening to an enter key to perform an action
                if (!sendDefaultEditorAction(true)) {
                    sendDownUpKeyEvents(KeyEvent.KEYCODE_ENTER);
                }
                break;
            default:
                // Make sure that digits go through any text watcher on the client side.
                if (charCode >= '0' && charCode <= '9') {
                    sendDownUpKeyEvents(charCode - '0' + KeyEvent.KEYCODE_0);
                } else {
                    InputConnection ic = getCurrentInputConnection();
                    if (ic != null) {
                        ic.commitText(String.valueOf(charCode), 1);
                    }
                }
                break;
        }
    }
public void sendDownUpKeyEvents(int keyEventCode) {
        InputConnection ic = getCurrentInputConnection();
        if (ic == null) return;
        long eventTime = SystemClock.uptimeMillis();
        ic.sendKeyEvent(new KeyEvent(eventTime, eventTime,
                KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
                KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
        ic.sendKeyEvent(new KeyEvent(eventTime, SystemClock.uptimeMillis(),
                KeyEvent.ACTION_UP, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
                KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
    }

方法中會(huì)根據(jù)傳過(guò)來(lái)的字符區(qū)分類型進(jìn)行不同處理,可以看到回退的處理會(huì)被單獨(dú)處理,其他字符如果是數(shù)字會(huì)在進(jìn)行一步處理,通過(guò)sendKeyEvent方法傳遞給InputConnection連接對(duì)象,其他的則執(zhí)行commitText。那么這時(shí)就需要關(guān)注這個(gè)InputConnection對(duì)象的內(nèi)部實(shí)現(xiàn),因?yàn)檫@是鍵盤(pán)返回參數(shù)的第一優(yōu)先級(jí)處理的對(duì)象,查看TextView會(huì)發(fā)現(xiàn)內(nèi)部會(huì)有一個(gè)onCreateInputConnection方法,會(huì)創(chuàng)建系統(tǒng)寫(xiě)好的EditableInputConnection對(duì)象,onCreateInputConnection這個(gè)方法是個(gè)可被重寫(xiě)的方法這也是完成上邊所說(shuō)需求的關(guān)鍵方法,我們自己創(chuàng)建一個(gè)InputConnection子類,重寫(xiě)其中方法就可以攔截鍵盤(pán)事件。
回到輸入流程我們查看EditableInputConnection的內(nèi)部實(shí)現(xiàn)sendKeyEvent的過(guò)程

 public boolean sendKeyEvent(KeyEvent event) {
        mIMM.dispatchKeyEventFromInputMethod(mTargetView, event);
        return false;
    }

方法中會(huì)執(zhí)行InputMethodManager的dispatchKeyEventFromInputMethod方法

public void dispatchKeyEventFromInputMethod(@Nullable View targetView,
            @NonNull KeyEvent event) {
        synchronized (mH) {
            ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
            if (viewRootImpl == null) {
                if (mServedView != null) {
                    viewRootImpl = mServedView.getViewRootImpl();
                }
            }
            if (viewRootImpl != null) {
                viewRootImpl.dispatchKeyFromIme(event);
            }
        }
    }

看到上邊代碼會(huì)獲取到目標(biāo)view的ViewRootImpl對(duì)象,執(zhí)行ViewRootImpl的dispatchKeyFromIme方法

public void dispatchKeyFromIme(KeyEvent event) {
        Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY_FROM_IME, event);
        msg.setAsynchronous(true);
        mHandler.sendMessage(msg);
    }
case MSG_DISPATCH_KEY_FROM_IME: {
                if (LOCAL_LOGV) Log.v(
                    TAG, "Dispatching key "
                    + msg.obj + " from IME to " + mView);
                KeyEvent event = (KeyEvent)msg.obj;
                if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
                    // The IME is trying to say this event is from the
                    // system!  Bad bad bad!
                    //noinspection UnusedAssignment
                    event = KeyEvent.changeFlags(event, event.getFlags() &
                            ~KeyEvent.FLAG_FROM_SYSTEM);
                }
                enqueueInputEvent(event, null, QueuedInputEvent.FLAG_DELIVER_POST_IME, true);
            } break;
void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        ...
        if (processImmediately) {
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
    }
void doProcessInputEvents() {
       ...
            deliverInputEvent(q);
       ...
    }
private void deliverInputEvent(QueuedInputEvent q) {
        ...
        InputStage stage;
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

        if (stage != null) {
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }

經(jīng)過(guò)一系列的傳遞最終stage對(duì)象進(jìn)行分發(fā),stage對(duì)象有很多子類,這里的stage的最終實(shí)現(xiàn)類是ViewPostImeInputStage

 public final void deliver(QueuedInputEvent q) {
            if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
                forward(q);
            } else if (shouldDropInputEvent(q)) {
                finish(q, false);
            } else {
                apply(q, onProcess(q));
            }
        }
//重寫(xiě)了onProcess方法
  @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    return processTrackballEvent(q);
                } else {
                    return processGenericMotionEvent(q);
                }
            }
        }

       private int processKeyEvent(QueuedInputEvent q) {
            final KeyEvent event = (KeyEvent)q.mEvent;

            // Deliver the key to the view hierarchy.
            if (mView.dispatchKeyEvent(event)) {
                return FINISH_HANDLED;
            }
            ....
      }

重寫(xiě)了onProcess方法并執(zhí)行了mView.dispatchKeyEvent(event),mView對(duì)象可以知道是DecorView對(duì)象,這時(shí)事件也就傳遞到了Decorview中

 @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        final int keyCode = event.getKeyCode();
        final int action = event.getAction();
        final boolean isDown = action == KeyEvent.ACTION_DOWN;

        if (isDown && (event.getRepeatCount() == 0)) {
            // First handle chording of panel key: if a panel key is held
            // but not released, try to execute a shortcut in it.
            if ((mWindow.mPanelChordingKey > 0) && (mWindow.mPanelChordingKey != keyCode)) {
                boolean handled = dispatchKeyShortcutEvent(event);
                if (handled) {
                    return true;
                }
            }

            // If a panel is open, perform a shortcut on it without the
            // chorded panel key
            if ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
                if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
                    return true;
                }
            }
        }

        if (!mWindow.isDestroyed()) {
            final Window.Callback cb = mWindow.getCallback();
            final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                    : super.dispatchKeyEvent(event);
            if (handled) {
                return true;
            }
        }

        return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
                : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
    }

在Decorview中獲取當(dāng)前window的callback,這個(gè)callback會(huì)回調(diào)到activity中,當(dāng)不存在回調(diào)時(shí)則會(huì)執(zhí)行父類的dispatchKeyEvent方法,如果window已經(jīng)被銷毀的情況,則執(zhí)行phonewindow的onKeyDown和onKeyUp,下邊分別分析下邊三種情況。
第一種回調(diào)activity的dispatchKeyEvent方法。

    public boolean dispatchKeyEvent(KeyEvent event) {
        onUserInteraction();

        // Let action bars open menus in response to the menu key prioritized over
        // the window handling it
        final int keyCode = event.getKeyCode();
        if (keyCode == KeyEvent.KEYCODE_MENU &&
                mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
            return true;
        }

        Window win = getWindow();
        if (win.superDispatchKeyEvent(event)) {
            return true;
        }
        View decor = mDecor;
        if (decor == null) decor = win.getDecorView();
        return event.dispatch(this, decor != null
                ? decor.getKeyDispatcherState() : null, this);
    }

方法中會(huì)優(yōu)先執(zhí)行window的superDispatchKeyEvent方法看是否處理掉了這個(gè)事件

PhoneWindow.java
 @Override
    public boolean superDispatchKeyEvent(KeyEvent event) {
        return mDecor.superDispatchKeyEvent(event);
    }
DecorView.java
    public boolean superDispatchKeyEvent(KeyEvent event) {
        // Give priority to closing action modes if applicable.
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
            final int action = event.getAction();
            // Back cancels action modes first.
            if (mPrimaryActionMode != null) {
                if (action == KeyEvent.ACTION_UP) {
                    mPrimaryActionMode.finish();
                }
                return true;
            }
        }

        return super.dispatchKeyEvent(event);
    }

ViewGroup.java
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 1);
        }

        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
        }
        return false;
    }

這時(shí)其實(shí)就跟第二種情況super.dispatchKeyEvent(event)一樣了,第二種情況則是從這里開(kāi)始。執(zhí)行到ViewGroup中,如果此ViewGroup是focused或者具體的大小被設(shè)置了(有邊界),則執(zhí)行父類view的dispatchKeyEvent,如果子類中有設(shè)置了foucus的對(duì)象,則執(zhí)行foucus的dispatchKeyEvent,如果都沒(méi)有則返回false。

    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 0);
        }

        // Give any attached key listener a first crack at the event.
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
            return true;
        }

        if (event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this)) {
            return true;
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

方法中會(huì)優(yōu)先調(diào)用onKeyListener,如果它非空且view是ENABLED狀態(tài),監(jiān)聽(tīng)器優(yōu)先觸發(fā),沒(méi)有設(shè)置監(jiān)聽(tīng)則調(diào)用KeyEvent.dispatch方法,并將view對(duì)象本身作為參數(shù)傳遞進(jìn)去,view的各種callback方法在這里被觸發(fā)。這里我們進(jìn)入event.dispatch方法來(lái)看下實(shí)現(xiàn)

    public final boolean dispatch(Callback receiver, DispatcherState state,
            Object target) {
        switch (mAction) {
            case ACTION_DOWN: {
                mFlags &= ~FLAG_START_TRACKING;
                if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
                        + ": " + this);
                boolean res = receiver.onKeyDown(mKeyCode, this);
                if (state != null) {
                    if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
                        if (DEBUG) Log.v(TAG, "  Start tracking!");
                        state.startTracking(this, target);
                    } else if (isLongPress() && state.isTracking(this)) {
                        try {
                            if (receiver.onKeyLongPress(mKeyCode, this)) {
                                if (DEBUG) Log.v(TAG, "  Clear from long press!");
                                state.performedLongPress(this);
                                res = true;
                            }
                        } catch (AbstractMethodError e) {
                        }
                    }
                }
                return res;
            }
            case ACTION_UP:
                if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
                        + ": " + this);
                if (state != null) {
                    state.handleUpEvent(this);
                }
                return receiver.onKeyUp(mKeyCode, this);
            case ACTION_MULTIPLE:
                final int count = mRepeatCount;
                final int code = mKeyCode;
                if (receiver.onKeyMultiple(code, count, this)) {
                    return true;
                }
                if (code != KeyEvent.KEYCODE_UNKNOWN) {
                    mAction = ACTION_DOWN;
                    mRepeatCount = 0;
                    boolean handled = receiver.onKeyDown(code, this);
                    if (handled) {
                        mAction = ACTION_UP;
                        receiver.onKeyUp(code, this);
                    }
                    mAction = ACTION_MULTIPLE;
                    mRepeatCount = count;
                    return handled;
                }
                return false;
        }
        return false;
    }

會(huì)執(zhí)行相應(yīng)view的callback的onKeyDown,onKeyUp,onKeyMultiple方法,同時(shí)處理內(nèi)部的狀態(tài)信息。這時(shí)會(huì)執(zhí)行activity和View的onKeyDown方法。

 public boolean onKeyDown(int keyCode, KeyEvent event)  {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (getApplicationInfo().targetSdkVersion
                    >= Build.VERSION_CODES.ECLAIR) {
                event.startTracking();
            } else {
                onBackPressed();
            }
            return true;
        }

        if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
            return false;
        } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
            Window w = getWindow();
            if (w.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
                    w.performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event,
                            Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
                return true;
            }
            return false;
        } else if (keyCode == KeyEvent.KEYCODE_TAB) {
            // Don't consume TAB here since it's used for navigation. Arrow keys
            // aren't considered "typing keys" so they already won't get consumed.
            return false;
        } else {
            // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
            boolean clearSpannable = false;
            boolean handled;
            if ((event.getRepeatCount() != 0) || event.isSystem()) {
                clearSpannable = true;
                handled = false;
            } else {
                handled = TextKeyListener.getInstance().onKeyDown(
                        null, mDefaultKeySsb, keyCode, event);
                if (handled && mDefaultKeySsb.length() > 0) {
                    // something useable has been typed - dispatch it now.

                    final String str = mDefaultKeySsb.toString();
                    clearSpannable = true;

                    switch (mDefaultKeyMode) {
                    case DEFAULT_KEYS_DIALER:
                        Intent intent = new Intent(Intent.ACTION_DIAL,  Uri.parse("tel:" + str));
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);
                        break;
                    case DEFAULT_KEYS_SEARCH_LOCAL:
                        startSearch(str, false, null, false);
                        break;
                    case DEFAULT_KEYS_SEARCH_GLOBAL:
                        startSearch(str, false, null, true);
                        break;
                    }
                }
            }
            if (clearSpannable) {
                mDefaultKeySsb.clear();
                mDefaultKeySsb.clearSpans();
                Selection.setSelection(mDefaultKeySsb,0);
            }
            return handled;
        }
    }

經(jīng)過(guò)這些處理如果還是沒(méi)有完成,則開(kāi)始第三種情況mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)執(zhí)行window的onKeyDowm。
到這里也就完整的完成了整個(gè)輸入流程的講解。其中有很大篇幅講解的是android系統(tǒng)中key事件的傳遞流程,這個(gè)可以跟輸入法流程分開(kāi)來(lái)看。

  1. View的各種KeyEvent.Callback接口早于Activity的對(duì)應(yīng)接口被調(diào)用;

  2. 整個(gè)處理環(huán)節(jié)中只要有一處表明處理掉了,則處理結(jié)束,不在往下傳遞;

  3. 各種Callback接口的處理優(yōu)先級(jí)低于監(jiān)聽(tīng)器,也就是說(shuō)各種onXXXListener的方法優(yōu)先被調(diào)用。

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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