一、字符输入起始:

LatinIME字符输入的初始方法是LatinIME类中的onCodeInput方法:

 // Implementation of {@link KeyboardActionListener}.    @Override    public void onCodeInput(final int codePoint, final int x, final int y,            final boolean isKeyRepeat) {        final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();        // x and y include some padding, but everything down the line (especially native        // code) needs the coordinates in the keyboard frame.        // TODO: We should reconsider which coordinate system should be used to represent        // keyboard event. Also we should pull this up -- LatinIME has no business doing        // this transformation, it should be done already before calling onCodeInput.        final int keyX = mainKeyboardView.getKeyX(x);        final int keyY = mainKeyboardView.getKeyY(y);        final int codeToSend;        if (Constants.CODE_SHIFT == codePoint) {            // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for            // alphabetic shift and shift while in symbol layout.            final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();            if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {                codeToSend = codePoint;            } else {                codeToSend = Constants.CODE_SYMBOL_SHIFT;            }        } else {            codeToSend = codePoint;        }        if (Constants.CODE_SHORTCUT == codePoint) {            mSubtypeSwitcher.switchToShortcutIME(this);            // Still call the *#onCodeInput methods for readability.        }        final Event event = createSoftwareKeypressEvent(codeToSend, keyX, keyY, isKeyRepeat);        final InputTransaction completeInputTransaction =                mInputLogic.onCodeInput(mSettings.getCurrent(), event,                        mKeyboardSwitcher.getKeyboardShiftMode(),                        mKeyboardSwitcher.getCurrentKeyboardScriptId(), mHandler);        updateStateAfterInputTransaction(completeInputTransaction);        mKeyboardSwitcher.onCodeInput(codePoint, getCurrentAutoCapsState(),                getCurrentRecapitalizeState());    }
首先通过createSoftwareKeypressEvent方法创建输入事件(Event),接着就开始调用InputLogic中的onCodeInput进行具体的字符输入操作(核心处理流程)

二、核心处理流程:

 public InputTransaction onCodeInput(final SettingsValues settingsValues, final Event event,            final int keyboardShiftMode,            // TODO: remove these arguments            final int currentKeyboardScriptId, final LatinIME.UIHandler handler) {        final Event processedEvent = mWordComposer.processEvent(event);        final InputTransaction inputTransaction = new InputTransaction(settingsValues,                processedEvent, SystemClock.uptimeMillis(), mSpaceState,                getActualCapsMode(settingsValues, keyboardShiftMode));        if (processedEvent.mKeyCode != Constants.CODE_DELETE                || inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {            mDeleteCount = 0;        }        mLastKeyTime = inputTransaction.mTimestamp;        mConnection.beginBatchEdit();        if (!mWordComposer.isComposingWord()) {            // TODO: is this useful? It doesn't look like it should be done here, but rather after            // a word is committed.            mIsAutoCorrectionIndicatorOn = false;        }        // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state.        if (processedEvent.mCodePoint != Constants.CODE_SPACE) {            cancelDoubleSpacePeriodCountdown();        }        Event currentEvent = processedEvent;        while (null != currentEvent) {            if (currentEvent.isConsumed()) {                handleConsumedEvent(currentEvent, inputTransaction);            } else if (currentEvent.isFunctionalKeyEvent()) {                handleFunctionalEvent(currentEvent, inputTransaction, currentKeyboardScriptId,                        handler);            } else {                handleNonFunctionalEvent(currentEvent, inputTransaction, handler);            }            currentEvent = currentEvent.mNextEvent;        }        if (!inputTransaction.didAutoCorrect() && processedEvent.mKeyCode != Constants.CODE_SHIFT                && processedEvent.mKeyCode != Constants.CODE_CAPSLOCK                && processedEvent.mKeyCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)            mLastComposedWord.deactivate();        if (Constants.CODE_DELETE != processedEvent.mKeyCode) {            mEnteredText = null;        }        mConnection.endBatchEdit();        return inputTransaction;    }

这其中使用到的RichInputConnection类,是通过组合的方式引入了InputConnection类,InputConnection类是Android中连接输入法与调用输入法控件之间的关键类,它提供了诸如提交字符、获取光标左右文本、删除光标附近字符等重要的方法。

下面就来分析下onCodeInput方法,所有的Event最终都是通过CombinerChain这个类来管理的,而onCodeInput首先是调用WordComposer中的processEvent方法来处理第一步创建的Event。而WordComposer在源码中的解释是存储当前构成词以及类似于临近key code之类信息的地方。

    /**     * Process an event and return an event, and return a processed event to apply.     * @param event the unprocessed event.     * @return the processed event. Never null, but may be marked as consumed.     */    @Nonnull    public Event processEvent(final Event event) {        final Event processedEvent = mCombinerChain.processEvent(mEvents, event);        // The retained state of the combiner chain may have changed while processing the event,        // so we need to update our cache.        refreshTypedWordCache();        mEvents.add(event);        return processedEvent;    }
可以看到WordComposer中的processEvent是调用了CombinerChain中的processEvent来处理onCodeInput传入的Event(输入事件):

 /**     * Process an event through the combining chain, and return a processed event to apply.     * @param previousEvents the list of previous events in this composition     * @param newEvent the new event to process     * @return the processed event. It may be the same event, or a consumed event, or a completely     *   new event. However it may never be null.     */    @Nonnull    public Event processEvent(final ArrayList previousEvents, final Event newEvent) {        mLog.debug("CombinerChain mCombinedText : " + mCombinedText);        final ArrayList modifiablePreviousEvents = new ArrayList<>(previousEvents);        Event event = newEvent;        for (final Combiner combiner : mCombiners) {            // A combiner can never return more than one event; it can return several            // code points, but they should be encapsulated within one event.            event = combiner.processEvent(modifiablePreviousEvents, event);            mLog.debug("processEvent circle num : " + mCombiners.size());            if (event.isConsumed()) {                mLog.debug("event consumed");                // If the event is consumed, then we don't pass it to subsequent combiners:                // they should not see it at all.                break;            }        }        updateStateFeedback();        return event;    }

可以看到,CombinerChain会将处理好的Event返回给WordComposer,而WordComposer会进一步处理返回的Event,最后将处理好的Event在返回给InputLogic(这两个类的具体逻辑后面再进行分析)。

InputLogic接到处理好的Event后,会创建一个InputTransaction(它封装了输入事件的一个单一事务)。接下来是判断是否是删除操作以及设定时间戳。

在上面的步骤都完成后,onCodeInput就调用RichInputConnection的beginBatchEdit方法开始进行字符的输入。

while语句中是具体的处理流程,首先是判断currentEvent的状态,并根据不同的状态调用不同的处理方法,handleConsumedEvent(暂置):好像是再次处理已经处理过的事件(?),打的log中没有出现过,所以不确定其处理的是什么类型的事件。

/**     * Handle a consumed event.     *     * Consumed events represent events that have already been consumed, typically by the     * combining chain.     *     * @param event The event to handle.     * @param inputTransaction The transaction in progress.     */    private void handleConsumedEvent(final Event event, final InputTransaction inputTransaction) {        // A consumed event may have text to commit and an update to the composing state, so        // we evaluate both. With some combiners, it's possible than an event contains both        // and we enter both of the following if clauses.        final CharSequence textToCommit = event.getTextToCommit();        mLog.debug("handleConsumedEvent textToCommit : " + textToCommit + ", isComposing : " + mWordComposer.isComposingWord());        if (!TextUtils.isEmpty(textToCommit)) {            mConnection.commitText(textToCommit, 1);            inputTransaction.setDidAffectContents();        }        if (mWordComposer.isComposingWord()) {            setComposingTextInternal(mWordComposer.getTypedWord(), 1);            inputTransaction.setDidAffectContents();            // 通知更新建议词(候选词)            inputTransaction.setRequiresUpdateSuggestions();        }    }

handleFunctionalEvent:处理的是一些功能性的字符(如CODE_DELETE),其实就是哪些值为负的那些CODE。isFunctionalKeyEvent是根据Event中的mCodePoint是否为NOT_A_CODE_POINT来判断的,而LatinIME的createSoftwareKeypressEvent方法中是根据待处理的CODE是否为负来设置Event的mCodePoint是否为NOT_A_CODE_POINT的。

    /**     * Handle a functional key event.     *     * A functional event is a special key, like delete, shift, emoji, or the settings key.     * Non-special keys are those that generate a single code point.     * This includes all letters, digits, punctuation, separators, emoji. It excludes keys that     * manage keyboard-related stuff like shift, language switch, settings, layout switch, or     * any key that results in multiple code points like the ".com" key.     *     * @param event The event to handle.     * @param inputTransaction The transaction in progress.     */    private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction,            // TODO: remove these arguments            final int currentKeyboardScriptId, final LatinIME.UIHandler handler) {        switch (event.mKeyCode) {            case Constants.CODE_DELETE:                handleBackspaceEvent(event, inputTransaction, currentKeyboardScriptId);                // Backspace is a functional key, but it affects the contents of the editor.                inputTransaction.setDidAffectContents();                break;            case Constants.CODE_SHIFT:                performRecapitalization(inputTransaction.mSettingsValues);                inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);                if (mSuggestedWords.isPrediction()) {                    inputTransaction.setRequiresUpdateSuggestions();                }                break;            case Constants.CODE_CAPSLOCK:                // Note: Changing keyboard to shift lock state is handled in                // {@link KeyboardSwitcher#onCodeInput(int)}.                break;            case Constants.CODE_SYMBOL_SHIFT:                // Note: Calling back to the keyboard on the symbol Shift key is handled in                // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.                break;            case Constants.CODE_SWITCH_ALPHA_SYMBOL:                // Note: Calling back to the keyboard on symbol key is handled in                // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.                break;            case Constants.CODE_SETTINGS:                onSettingsKeyPressed();                break;            case Constants.CODE_SHORTCUT:                // We need to switch to the shortcut IME. This is handled by LatinIME since the                // input logic has no business with IME switching.                break;            case Constants.CODE_ACTION_NEXT:                performEditorAction(EditorInfo.IME_ACTION_NEXT);                break;            case Constants.CODE_ACTION_PREVIOUS:                performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);                break;            case Constants.CODE_LANGUAGE_SWITCH:                handleLanguageSwitchKey();                break;            case Constants.CODE_SHIFT_ENTER:                // TODO: remove this object                final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_ENTER,                        event.mKeyCode, event.mX, event.mY, event.isKeyRepeat());                handleNonSpecialCharacterEvent(tmpEvent, inputTransaction, handler);                // Shift + Enter is treated as a functional key but it results in adding a new                // line, so that does affect the contents of the editor.                inputTransaction.setDidAffectContents();                break;            default:                throw new RuntimeException("Unknown key code : " + event.mKeyCode);        }    }
handleNonFunctionalEvent:处理一些普通的字符。

/**     * Handle an event that is not a functional event.     *     * These events are generally events that cause input, but in some cases they may do other     * things like trigger an editor action.     *     * @param event The event to handle.     * @param inputTransaction The transaction in progress.     */    private void handleNonFunctionalEvent(final Event event,            final InputTransaction inputTransaction,            // TODO: remove this argument            final LatinIME.UIHandler handler) {        inputTransaction.setDidAffectContents();        switch (event.mCodePoint) {            case Constants.CODE_ENTER:                final EditorInfo editorInfo = getCurrentInputEditorInfo();                final int imeOptionsActionId =                        InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo);                if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) {                    // Either we have an actionLabel and we should performEditorAction with                    // actionId regardless of its value.                    performEditorAction(editorInfo.actionId);                } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) {                    // We didn't have an actionLabel, but we had another action to execute.                    // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast,                    // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it                    // means there should be an action and the app didn't bother to set a specific                    // code for it - presumably it only handles one. It does not have to be treated                    // in any specific way: anything that is not IME_ACTION_NONE should be sent to                    // performEditorAction.                    performEditorAction(imeOptionsActionId);                } else {                    // No action label, and the action from imeOptions is NONE: this is a regular                    // enter key that should input a carriage return.                    handleNonSpecialCharacterEvent(event, inputTransaction, handler);                }                break;            default:                handleNonSpecialCharacterEvent(event, inputTransaction, handler);                break;        }    }
1. 首先,看下 handleFunctionalEvent方法:它是根据Event的mKeyCode的类型分别进行处理:

    1) Constatns.CODE_DELETE:删除键,通过handleBackspaceEvent进行处理

        

/**     * Handle a press on the backspace key.     * @param event The event to handle.     * @param inputTransaction The transaction in progress.     */    private void handleBackspaceEvent(final Event event, final InputTransaction inputTransaction,            // TODO: remove this argument, put it into settingsValues            final int currentKeyboardScriptId) {        mLog.debug("handleBackspaceEvent");        mSpaceState = SpaceState.NONE;        mDeleteCount++;        // In many cases after backspace, we need to update the shift state. Normally we need        // to do this right away to avoid the shift state being out of date in case the user types        // backspace then some other character very fast. However, in the case of backspace key        // repeat, this can lead to flashiness when the cursor flies over positions where the        // shift state should be updated, so if this is a key repeat, we update after a small delay.        // Then again, even in the case of a key repeat, if the cursor is at start of text, it        // can't go any further back, so we can update right away even if it's a key repeat.        final int shiftUpdateKind =                event.isKeyRepeat() && mConnection.getExpectedSelectionStart() > 0                ? InputTransaction.SHIFT_UPDATE_LATER : InputTransaction.SHIFT_UPDATE_NOW;        inputTransaction.requireShiftUpdate(shiftUpdateKind);                mLog.debug("SelectionStart : " + mConnection.getExpectedSelectionStart());        mLog.debug("ComposingWord : " + mWordComposer.isCursorFrontOrMiddleOfComposingWord());        if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {            // If we are in the middle of a recorrection, we need to commit the recorrection            // first so that we can remove the character at the current cursor position.            resetEntireInputState(mConnection.getExpectedSelectionStart(),                    mConnection.getExpectedSelectionEnd(), true /* clearSuggestionStrip */);            // When we exit this if-clause, mWordComposer.isComposingWord() will return false.        }        if (mWordComposer.isComposingWord()) {            if (mWordComposer.isBatchMode()) {                final String rejectedSuggestion = mWordComposer.getTypedWord();                mWordComposer.reset();                mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);                if (!TextUtils.isEmpty(rejectedSuggestion)) {                    mDictionaryFacilitator.removeWordFromPersonalizedDicts(rejectedSuggestion);                }            } else {                mWordComposer.applyProcessedEvent(event);            }            if (mWordComposer.isComposingWord()) {                setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1);            } else {                mConnection.commitText("", 1);            }            inputTransaction.setRequiresUpdateSuggestions();        } else {            mLog.debug("not composing");            if (mLastComposedWord.canRevertCommit()) {                mLog.debug("canRevertCommit");                revertCommit(inputTransaction, inputTransaction.mSettingsValues);                return;            }            if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {                // Cancel multi-character input: remove the text we just entered.                // This is triggered on backspace after a key that inputs multiple characters,                // like the smiley key or the .com key.                mConnection.deleteSurroundingText(mEnteredText.length(), 0);                mEnteredText = null;                // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.                // In addition we know that spaceState is false, and that we should not be                // reverting any autocorrect at this point. So we can safely return.                return;            }            if (SpaceState.DOUBLE == inputTransaction.mSpaceState) {                cancelDoubleSpacePeriodCountdown();                if (mConnection.revertDoubleSpacePeriod()) {                    // No need to reset mSpaceState, it has already be done (that's why we                    // receive it as a parameter)                    inputTransaction.setRequiresUpdateSuggestions();                    mWordComposer.setCapitalizedModeAtStartComposingTime(                            WordComposer.CAPS_MODE_OFF);                    return;                }            } else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {                if (mConnection.revertSwapPunctuation()) {                    // Likewise                    return;                }            }            // No cancelling of commit/double space/swap: we have a regular backspace.            // We should backspace one char and restart suggestion if at the end of a word.            if (mConnection.hasSelection()) {                // If there is a selection, remove it.                final int numCharsDeleted = mConnection.getExpectedSelectionEnd()                        - mConnection.getExpectedSelectionStart();                mConnection.setSelection(mConnection.getExpectedSelectionEnd(),                        mConnection.getExpectedSelectionEnd());                mConnection.deleteSurroundingText(numCharsDeleted, 0);            } else {                // There is no selection, just delete one character.                if (Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) {                    // This should never happen.                    Log.e(TAG, "Backspace when we don't know the selection position");                }                if (inputTransaction.mSettingsValues.isBeforeJellyBean() ||                        inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()) {                    // There are two possible reasons to send a key event: either the field has                    // type TYPE_NULL, in which case the keyboard should send events, or we are                    // running in backward compatibility mode. Before Jelly bean, the keyboard                    // would simulate a hardware keyboard event on pressing enter or delete. This                    // is bad for many reasons (there are race conditions with commits) but some                    // applications are relying on this behavior so we continue to support it for                    // older apps, so we retain this behavior if the app has target SDK < JellyBean.                    sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);                    if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {                        sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);                    }                } else {                    final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();                    if (codePointBeforeCursor == Constants.NOT_A_CODE) {                        // HACK for backward compatibility with broken apps that haven't realized                        // yet that hardware keyboards are not the only way of inputting text.                        // Nothing to delete before the cursor. We should not do anything, but many                        // broken apps expect something to happen in this case so that they can                        // catch it and have their broken interface react. If you need the keyboard                        // to do this, you're doing it wrong -- please fix your app.                        mConnection.deleteSurroundingText(1, 0);                        return;                    }                    final int lengthToDelete =                            Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;                    mConnection.deleteSurroundingText(lengthToDelete, 0);                    if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {                        final int codePointBeforeCursorToDeleteAgain =                                mConnection.getCodePointBeforeCursor();                        if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {                            final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(                                    codePointBeforeCursorToDeleteAgain) ? 2 : 1;                            mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);                        }                    }                }            }            if (inputTransaction.mSettingsValues                    .isSuggestionsEnabledPerUserSettings()                    && inputTransaction.mSettingsValues.mSpacingAndPunctuations                            .mCurrentLanguageHasSpaces                    && !mConnection.isCursorFollowedByWordCharacter(                            inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {                restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,                        true /* shouldIncludeResumedWordInSuggestions */, currentKeyboardScriptId);            }        }    }



更多相关文章

  1. Android简单获取经纬度的方法
  2. Android拨打电话的两种实现方法
  3. android 基本的画图方法
  4. 事件总线EventBus Android开源库的使用
  5. Handler 内部类导致的内存泄露修改方法
  6. android listview的item里面的imageview的点击事件
  7. Android 在程序中重启APP的方法
  8. android studio AppCompatActivity中onCreate的两种方法
  9. Android截图方法

随机推荐

  1. Android(安卓)UI设计系统-android select
  2. Android学习笔记之mainfest文件中android
  3. Android(安卓)用户界面
  4. Ubuntu编译Android整个系统以及编译指定
  5. Android中使用Intent实现界面跳转
  6. android界面开发小结——android笔记---
  7. 关于android.R.id.text1
  8. Android(安卓)Studio解决plugin with id
  9. Basics of Android(安卓): Part IV – An
  10. 安卓巴士Android开发神贴整理