一、字符输入起始:

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

<span style="font-size:18px;"><span style="font-size:18px;"><span style="font-size:18px;"> // Implementation of {@link KeyboardActionListener}.@Overridepublic 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());}</span></span></span>

首先通过createSoftwareKeypressEvent方法创建输入事件(Event),接着就开始调用InputLogic中的onCodeInput进行具体的字符输入操作(核心处理流程)

二、核心处理流程:

<span style="font-size:18px;"><span style="font-size:18px;"> public InputTransaction onCodeInput(final SettingsValues settingsValues, final Event event,final int keyboardShiftMode,// TODO: remove these argumentsfinal 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;}
</span></span>

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

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

<span style="font-size:18px;"><span style="font-size:18px;">    /*** 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.*/@Nonnullpublic 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;}</span></span>

可以看到WordComposer中的processEvent是调用了CombinerChain中的processEvent来处理onCodeInput传入的Event(输入事件):

<span style="font-size:18px;"> /*** 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.*/@Nonnullpublic Event processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {mLog.debug("CombinerChain mCombinedText : " + mCombinedText);final ArrayList<Event> 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;}</span>

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

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

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

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

<span style="font-size:18px;">/*** 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();}}</span>

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 argumentsfinal 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 objectfinal 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 argumentfinal 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 settingsValuesfinal 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()) {// Likewisereturn;}}// 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);}}}

Android AOSP输入法(LatinIME)输入流程二相关推荐

  1. android latinime分析,Android AOSP输入法(LatinIME)大写判断分析

    LatinIME源码地址:https://android.googlesource.com/platform/packages/inputmethods/LatinIME/+/android-5.1. ...

  2. Android 修改输入法的输入语言

    Android原生系统中,默认的输入法是"Android键盘(AOSP)",此输入法包括55种输入语言,具体有哪一些,去看看原生系统里的就知道了,目测没有中文(不知道为啥). 还有 ...

  3. Android O: View的绘制流程(二):测量

    在前一篇博客Android O: View的绘制流程(一): 创建和加载中,  我们分析了系统创建和加载View的过程,这部分内容完成了View绘制的前置工作. 本文开始分析View的测量的流程. 一 ...

  4. Android audio切换设备通道流程(二十八)

    Android audio切换设备通道流程 1.frameworks/base/media/java/android/media/AudioManager.java public void setMo ...

  5. 在Android原生输入法LatinIME中添加自定义按键

    由于项目需求,需要修改android系统原生输入法.以下修改的是源码中的LatinIME/java工程. 示例添加的是隐藏软键盘的按键,具体的该在哪个位置添加,进入到相应的文件就明白了. A.将hid ...

  6. Android技术分享| 视频通话开发流程(二)

    多人呼叫 多人呼叫与点对点呼叫区别在于多人呼叫是一次呼叫1个以上的人,中途也可以再呼叫邀请别人加入通话. 整个呼叫的流程跟点对点呼叫类似,但也有些区别,需要添加额外的 API 逻辑来实现功能.下面我们 ...

  7. 【Android 应用开发】Android 工程修改包名流程 ( 修改 applicationId | 修改 package | 修改 R 资源引用 | 修改 BuildConfig 引用 )

    文章目录 一. Android 工程修改包名流程 二. 修改 applicationId 三. 修改 package 包名 四. AndroidManifest.xml 清单文件组件 五. 修改 R ...

  8. Android O: View的绘制流程(三):布局和绘制

    前一篇文章Android O: View的绘制流程(二):测量中,  我们分析了View的测量流程.  当View测量完毕后,就要开始进行布局和绘制相关的工作,  本篇文章就来分析下这部分流程. 一. ...

  9. Android AOSP LatinIME输入法自定义图片按钮

    文章目录 前言 一.引进图片按钮 二.添加映射 三.attrs.xml 四.KeyboardIconsSet.java 五.Constants.java 六.KeyboardCodesSet.java ...

最新文章

  1. CPU 内部结构解析
  2. 2021世界上“最猎奇”“最难的”六大编程语言
  3. Django rest_framework 实用技巧
  4. python爬虫知识点总结(十三)使用代理处理反扒抓取微信文章
  5. 2.4.1 算术逻辑单元ALU与加法器(串行加法器、并行加法器、全加器)
  6. RunTime类,后台快速打开浏览器
  7. JavaOne 2012覆盖率
  8. python聚类分析成绩反思_机器学习python实践——二分K-means聚类
  9. 如何创建一个自记录的Makefile
  10. NuGet:添加EntityFramework
  11. SpringBoot集成Redis缓存
  12. sql max同一行_超经典SQL题 | 做完这4道面试题你就过关了
  13. labview学习之“创建数组”函数
  14. Mysql经典面试题及答案
  15. 《深度学习》花书-读书笔记汇总贴(汇总19/19)
  16. 拦截图片代码 精易web浏览器_精易Web浏览器 UChk验证源码
  17. 阿里云磁盘异常爆满的原因排查及解决方法
  18. 用python写作文_Python3实现写作
  19. dell 2u服务器型号,DELL服务器规格参数
  20. python中使用cv2遍历图片像素点以及改变像素点的像素值

热门文章

  1. 测试用例设计方法_边界值分析法(游戏向)
  2. 【Hadoop】解决Hadoop Unable to load native-hadoop library问题
  3. 李敖 《关公曹操三角恋爱论》摘录
  4. Golang Window TUN 虚拟网卡
  5. ${pageContext.request.contextPath}访问不到,验证码刷新失败
  6. python实现自动化运维项目_Python自动化运维项目开发最佳实战
  7. 文件异步上传使用虚拟滚动el-table-virtual-scroll加载数据
  8. 张高兴的 Windows 10 IoT 开发笔记:使用 ADS1115 读取模拟信号
  9. 【哈工大版】动态ReLU:自适应参数化ReLU及Keras代码(调参记录11)
  10. 手把手教你如何将有线音箱改装成蓝牙音箱