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.中文打字的流程:
时序图如下:
中文打字需要用到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 模式