InputMethod流程解读

InputMethod(以Android4.4.3为例)

1.默认输入法的启动流程:

SystemServer会启动InputMethodManagerService,在InputMethodManagerService中会启动action为"android.view.InputMethod"的Service,同时会设置组件名称来决定启动哪个输入法。

/frameworks/base/services/java/com/android/server/InputMethodManagerService.java
1194    InputBindResult startInputInnerLocked() {
1195        if (mCurMethodId == null) {
1196            return mNoBinding;
1197        }
1198
1199        if (!mSystemReady) {
1200            // If the system is not yet ready, we shouldn't be running third
1201            // party code.
1202            return new InputBindResult(null, null, mCurMethodId, mCurSeq);
1203        }
1204
1205        InputMethodInfo info = mMethodMap.get(mCurMethodId);
1206        if (info == null) {
1207            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1208        }
1209
1210        unbindCurrentMethodLocked(false, true);
1211        //InputMethod.SERVICE_INTERFACE ="android.view.InputMethod"
1212        mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
1213        mCurIntent.setComponent(info.getComponent());
1214        mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
1215                com.android.internal.R.string.input_method_binding_label);
1216        mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
1217                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
1218        if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
1219                | Context.BIND_NOT_VISIBLE | Context.BIND_SHOWING_UI)) {
1220            mLastBindTime = SystemClock.uptimeMillis();
1221            mHaveConnection = true;
1222            mCurId = info.getId();
1223            mCurToken = new Binder();
1224            try {
1225                if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
1226                mIWindowManager.addWindowToken(mCurToken,
1227                        WindowManager.LayoutParams.TYPE_INPUT_METHOD);
1228            } catch (RemoteException e) {
1229            }
1230            return new InputBindResult(null, null, mCurId, mCurSeq);
1231        } else {
1232            mCurIntent = null;
1233            Slog.w(TAG, "Failure connecting to input method service: "
1234                    + mCurIntent);
1235        }
1236        return null;
1237    }

我们需要关注的是PinyinIME,在启动Service时如果进程没有创建,那么会首先创建进程,这里就是创建PinyinIME这个apk的进程,同时启动com.android.inputmethod.pinyin.PinyinIME这个service。在这个service的onCreate中调用了startPinyinDecoderService启动了AndroidManifest.xml中声明的另一个Sevcie采用显示启动的方式。

177    public void onCreate() {
178        mEnvironment = Environment.getInstance();
179        if (mEnvironment.needDebug()) {
180            Log.d(TAG, "onCreate.");
181        }
182        super.onCreate();
183
184        startPinyinDecoderService();
185        mImEn = new EnglishInputProcessor();
186        Settings.getInstance(PreferenceManager
187                .getDefaultSharedPreferences(getApplicationContext()));
188
189        mInputModeSwitcher = new InputModeSwitcher(this);
190        mChoiceNotifier = new ChoiceNotifier(this);
191        mGestureListenerSkb = new OnGestureListener(false);
192        mGestureListenerCandidates = new OnGestureListener(true);
193        mGestureDetectorSkb = new GestureDetector(this, mGestureListenerSkb);
194        mGestureDetectorCandidates = new GestureDetector(this,
195                mGestureListenerCandidates);
196
197        mEnvironment.onConfigurationChanged(getResources().getConfiguration(),
198                this);
199    }

2.软键盘View的创建流程

时序图如下:

当点击EditTextView时会回调下面这些函数:

 /packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/PinyinIME.java
1093    @Override
1094    public View onCreateInputView() {
1095        if (mEnvironment.needDebug()) {
1096            Log.d(TAG, "onCreateInputView.");
1097        }
1098        LayoutInflater inflater = getLayoutInflater();
1099        mSkbContainer = (SkbContainer) inflater.inflate(R.layout.skb_container,
1100                null);
1101        mSkbContainer.setService(this);
1102        mSkbContainer.setInputModeSwitcher(mInputModeSwitcher);
1103        mSkbContainer.setGestureDetector(mGestureDetectorSkb);
1104        return mSkbContainer;
1105    }
1106
1107    @Override
1108    public void onStartInput(EditorInfo editorInfo, boolean restarting) {
1109        if (mEnvironment.needDebug()) {
1110            Log.d(TAG, "onStartInput " + " ccontentType: "
1111                    + String.valueOf(editorInfo.inputType) + " Restarting:"
1112                    + String.valueOf(restarting));
1113        }
1114        updateIcon(mInputModeSwitcher.requestInputWithHkb(editorInfo));
1115        resetToIdleState(false);
1116    }
1117
1118    @Override
1119    public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
1120        if (mEnvironment.needDebug()) {
1121            Log.d(TAG, "onStartInputView " + " contentType: "
1122                    + String.valueOf(editorInfo.inputType) + " Restarting:"
1123                    + String.valueOf(restarting));
1124        }
1125        updateIcon(mInputModeSwitcher.requestInputWithSkb(editorInfo));
1126        resetToIdleState(false);
1127        mSkbContainer.updateInputMode();
1128        setCandidatesViewShown(false);
1129    }

首先InputMethodService会依次回调onCreateInputView onStartInputView在onCreateInputView中会加载skb_container并设置一些参数和服务接着会回调onStartInputView,在其中调用mSkbContainer.updateInputMode()去加载当前的键盘界面。

3.英文模式下按下A键到显示在view上的过程:

时序图如下:

当你在键盘上按到键的view时会回调到SkbContainer的onTouchEvent,接着会把你按下的位置的坐标通过 mSkv.onKeyRelease或者mSkv.onKeyPress传给 SoftKeyboardView,接着SoftKeyboardView返回你按下的键的softkey对象最后通过responseKeyEvent调用PinyinIME这个Service的responseSoftKeyEvent将softkey回传给PinyinIME Service。接着PinyinIME Service将需要回传给系统的softkey调用onKeyup,onKeyDown,在两个函数都会调用 processKey来获取按下键的keycode或者效果如果是带有KeyCode的键被按下了,那么会获取对应的字符"A-Z,或者a-z"并最终调用commitResultText通过InputConnection的commitText将字符传给系统让其在EditText显示出来.

4.中文打字的流程:

时序图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y88qSIIa-1643352364692)(C:\Users\GD07729.WHGDJT\Pictures\中文模式下打字的流程.png)]

中文打字需要用到PinyinDecoderService这个服务是在PInyinIME 在onCreate是调用startPinyinDecoderService启动的.

在这个Service中主要工作都是通过JNI调用native函数完成的.在Servcie链接上后就将代理交给了mDecInfo去完成相关工作。当处于中文模式时,在ProcessKey函数中会有如下分支:

/packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/PinyinIME.java
		   if (mInputModeSwitcher.isEnglishWithSkb()) {
295            return mImEn.processKey(getCurrentInputConnection(), event,
296                    mInputModeSwitcher.isEnglishUpperCaseWithSkb(), realAction);
297        } else if (mInputModeSwitcher.isChineseText()) {
298            if (mImeState == ImeState.STATE_IDLE ||
299                    mImeState == ImeState.STATE_APP_COMPLETION) {
300                mImeState = ImeState.STATE_IDLE;
301                return processStateIdle(keyChar, keyCode, event, realAction);
302            } else if (mImeState == ImeState.STATE_INPUT) { 
    			   //在中文模式下打字会走到这边
303                return processStateInput(keyChar, keyCode, event, realAction);
304            } else if (mImeState == ImeState.STATE_PREDICT) {
305                return processStatePredict(keyChar, keyCode, event, realAction);
306            } else if (mImeState == ImeState.STATE_COMPOSING) {
307                return processStateEditComposing(keyChar, keyCode, event,
308                        realAction);
309            }
310        } else {
311            if (0 != keyChar && realAction) {
312                commitResultText(String.valueOf((char) keyChar));
313            }
314        }

processStateInput会将字母经过mDecInfo传送给native层去做imSearch的动作接着通过imGetChoiceList获取到匹配的结果流程的栈调用如下:

D/PinyinIME( 1708): getCandiagtesForCache was called
D/PinyinIME( 1708): java.lang.Throwable
D/PinyinIME( 1708): at com.android.inputmethod.pinyin.PinyinIME D e c o d i n g I n f o . g e t C a n d i a g t e s F o r C a c h e ( P i n y i n I M E . j a v a : 1922 ) D / P i n y i n I M E ( 1708 ) : a t c o m . a n d r o i d . i n p u t m e t h o d . p i n y i n . P i n y i n I M E DecodingInfo.getCandiagtesForCache(PinyinIME.java:1922) D/PinyinIME( 1708): at com.android.inputmethod.pinyin.PinyinIME DecodingInfo.getCandiagtesForCache(PinyinIME.java:1922)D/PinyinIME(1708):atcom.android.inputmethod.pinyin.PinyinIMEDecodingInfo.preparePage(PinyinIME.java:1988)
D/PinyinIME( 1708): at com.android.inputmethod.pinyin.PinyinIME D e c o d i n g I n f o . u p d a t e D e c I n f o F o r S e a r c h ( P i n y i n I M E . j a v a : 1885 ) D / P i n y i n I M E ( 1708 ) : a t c o m . a n d r o i d . i n p u t m e t h o d . p i n y i n . P i n y i n I M E DecodingInfo.updateDecInfoForSearch(PinyinIME.java:1885) D/PinyinIME( 1708): at com.android.inputmethod.pinyin.PinyinIME DecodingInfo.updateDecInfoForSearch(PinyinIME.java:1885)D/PinyinIME(1708):atcom.android.inputmethod.pinyin.PinyinIMEDecodingInfo.chooseDecodingCandidate(PinyinIME.java:1816)
D/PinyinIME( 1708): at com.android.inputmethod.pinyin.PinyinIME$DecodingInfo.access$100(PinyinIME.java:1511)
D/PinyinIME( 1708): at com.android.inputmethod.pinyin.PinyinIME.chooseAndUpdate(PinyinIME.java:844)
D/PinyinIME( 1708): at com.android.inputmethod.pinyin.PinyinIME.processSurfaceChange(PinyinIME.java:752)
D/PinyinIME( 1708): at com.android.inputmethod.pinyin.PinyinIME.processStateInput(PinyinIME.java:486)
D/PinyinIME( 1708): at com.android.inputmethod.pinyin.PinyinIME.processKey(PinyinIME.java:309)
D/PinyinIME( 1708): at com.android.inputmethod.pinyin.PinyinIME.onKeyUp(PinyinIME.java:250)
D/PinyinIME( 1708): at com.android.inputmethod.pinyin.PinyinIME.responseSoftKeyEvent(PinyinIME.java:994)
D/PinyinIME( 1708): at com.android.inputmethod.pinyin.SkbContainer.responseKeyEvent(SkbContainer.java:313)
D/PinyinIME( 1708): at com.android.inputmethod.pinyin.SkbContainer.onTouchEvent(SkbContainer.java:523)

/packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/PinyinIME.java
453    private boolean processStateInput(int keyChar, int keyCode, KeyEvent event,
454            boolean realAction) {
        //ALT键被按下
    	...
        //"a-z或/"被按下时
476        if (keyChar >= 'a' && keyChar <= 'z' || keyChar == '\''
477                && !mDecInfo.charBeforeCursorIsSeparator()
478                || keyCode == KeyEvent.KEYCODE_DEL) {
479            if (!realAction) return true;
480            return processSurfaceChange(keyChar, keyCode);
481        }
        ...          
}
737    private boolean processSurfaceChange(int keyChar, int keyCode) {
738        if (mDecInfo.isSplStrFull() && KeyEvent.KEYCODE_DEL != keyCode) {
739            return true;
740        }
741
742        if ((keyChar >= 'a' && keyChar <= 'z')
743                || (keyChar == '\'' && !mDecInfo.charBeforeCursorIsSeparator())
744                || (((keyChar >= '0' && keyChar <= '9') || keyChar == ' ') && ImeState.STATE_COMPOSING == mImeState)) {
745            //把当前的字符加入到mDecInfo的mSurface这个StringBuffer中
               mDecInfo.addSplChar((char) keyChar, false);
    		  //查找匹配项并更新
746            chooseAndUpdate(-1);
747        } else if (keyCode == KeyEvent.KEYCODE_DEL) {
748            mDecInfo.prepareDeleteBeforeCursor();
749            chooseAndUpdate(-1);
750        }
751        return true;
752    }

824    private void chooseAndUpdate(int candId) {
          //不是中文输入模式
825        if (!mInputModeSwitcher.isChineseText()) {
826            String choice = mDecInfo.getCandidate(candId);
827            if (null != choice) {
828                commitResultText(choice);
829            }
830            resetToIdleState(false);
831            return;
832        }
833		   //普通打字时是STATE_INPUT
834        if (ImeState.STATE_PREDICT != mImeState) {
835            // Get result candidate list, if choice_id < 0, do a new decoding.
836            // If choice_id >=0, select the candidate, and get the new candidate
837            // list.
838            mDecInfo.chooseDecodingCandidate(candId);
839        }
    	   //chooseDecodingCandidate会将匹配项的第一项给ComposingStr所以下面的代码会走到
844        if (mDecInfo.getComposingStr().length() > 0) {
845            String resultStr;
846            resultStr = mDecInfo.getComposingStrActivePart();
847
                 ....
868                if (mDecInfo.mCandidatesList.size() > 0) {
    				   //chooseDecodingCandidate会将匹配项的list加入到mCandidatesList中所以会显示CandidateView
869                    showCandidateWindow(false);
870                } else {
871                    resetToIdleState(false);
872                }
		}
1773        private void chooseDecodingCandidate(int candId) {
1774            if (mImeState != ImeState.STATE_PREDICT) {
1775                resetCandidates();
1776                int totalChoicesNum = 0;
1777                try {
1778                    if (candId < 0) {
1779                        if (length() == 0) {
1780                            totalChoicesNum = 0;
1781                        } else {
1782                            if (mPyBuf == null)
1783                                mPyBuf = new byte[PY_STRING_MAX];
    							//length() 和charAt()的对象都是mSurface
    							//这里循环取出mSurface的字符并转成byte传给native层去匹配中文项
1784                            for (int i = 0; i < length(); i++)
1785                                mPyBuf[i] = (byte) charAt(i);
1786                            mPyBuf[length()] = 0;
1787
1788                            if (mPosDelSpl < 0) {
                                    //调用PinyinDecoderService的imSearch并返回页数
1789                                totalChoicesNum = mIPinyinDecoderService
1790                                        .imSearch(mPyBuf, length());
1791                            } else {
1792                                boolean clear_fixed_this_step = true;
1793                                if (ImeState.STATE_COMPOSING == mImeState) {
1794                                    clear_fixed_this_step = false;
1795                                }
1796                                totalChoicesNum = mIPinyinDecoderService
1797                                        .imDelSearch(mPosDelSpl, mIsPosInSpl,
1798                                                clear_fixed_this_step);
1799                                mPosDelSpl = -1;
1800                            }
1801                        }
1802                    } else {
1803                        totalChoicesNum = mIPinyinDecoderService
1804                                .imChoose(candId);
1805                    }
1806                } catch (RemoteException e) {
1807                }
    	            //从native层获取匹配项的list并更新显示到CandidatesView中
1808                updateDecInfoForSearch(totalChoicesNum);
1809            }
1810        }

updateDecInfoForSearch则会调用 preparePage,而preparePage会调用 getCandiagtesForCache从native层获取匹配项的list

/packages/inputmethods/PinyinIME/src/com/android/inputmethod/pinyin/PinyinIME.java
			private void getCandiagtesForCache() {
1914            int fetchStart = mCandidatesList.size();
1915            int fetchSize = mTotalChoicesNum - fetchStart;
1916            if (fetchSize > MAX_PAGE_SIZE_DISPLAY) {
1917                fetchSize = MAX_PAGE_SIZE_DISPLAY;
1918            }
1919            try {
1920                List<String> newList = null;
    				//state为STATE_INPUT
1921                if (ImeState.STATE_INPUT == mImeState ||
1922                        ImeState.STATE_IDLE == mImeState ||
1923                        ImeState.STATE_COMPOSING == mImeState){
    				   //调用native的imGetChoiceList来获取匹配项List
1924                    newList = mIPinyinDecoderService.imGetChoiceList(
1925                            fetchStart, fetchSize, mFixedLen);
1926                } else if (ImeState.STATE_PREDICT == mImeState) {
1927                    newList = mIPinyinDecoderService.imGetPredictList(
1928                            fetchStart, fetchSize);
1929                } else if (ImeState.STATE_APP_COMPLETION == mImeState) {
1930                    newList = new ArrayList<String>();
1931                    if (null != mAppCompletions) {
1932                        for (int pos = fetchStart; pos < fetchSize; pos++) {
1933                            CompletionInfo ci = mAppCompletions[pos];
1934                            if (null != ci) {
1935                                CharSequence s = ci.getText();
1936                                if (null != s) newList.add(s.toString());
1937                            }
1938                        }
1939                    }
1940                }
1941                mCandidatesList.addAll(newList);
1942            } catch (RemoteException e) {
1943                Log.w(TAG, "PinyinDecoderService died", e);
1944            }
1945        }

5.生命周期:

STATE_IDLE,STATE_COMPOSING , STATE_INPUT ,STATE_PREDICT ,STATE_APP_COMPLETION

符号和功能键和英文模式下的输入无法改变IME的state

开始都是STATE_IDLE 按下键能显示中文字符转到STATE_INPUT 按下显示的中文字符转到STATE_PREDICT 如果中文显示区域没有文字但是ComposingView不为空那么会转到STATE_COMPOSING ,只要中文字符和ComposingView都为空那么就转入STATE_IDLE 模式

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值