项目中使用的是Google的输入法:谷歌拼音输入法,即PinyinIME。
其中关于软键盘的是前面带skb的xml文件。查看其中的字符是否都是国际化写法,在value中是否有中文和英文对应的string。发现没有中文对应的string,且写法都是直接使用的中文字符串:
客户提出需求:需要在Setting中切换中英文的时候,输入法对应成中英文输入,并且字符也对应成中英文,即Setting中设置为中文的时候,输入法中对应输入中文,且布局中显示的字符也对应成中文。
由于水平有限,加上平时输入法这块看得比较少,解决这个问题还是花了不少时间。
首先,看到 PinyinIME中,根本就没有定义中文的string,于是就把对应的中文的string加上去。加上去后,点击打开输入法,有个谷歌拼音输入法设置界面,里面的字符是可以跟随Setting中中英文切换而变化的。于是我自信地以为已经OK了。图样图森破~
接着就开始处理Setting中切换中英文输入法对应中英文输入的问题。
先了解输入法的大概流程。
在PinyinIME中,PinyinIME是继承InputMethodService的,是一个Service,其在Manifest中声明为:
<service android:name=".PinyinIME"android:label="@string/ime_name"android:permission="android.permission.BIND_INPUT_METHOD"><intent-filter><action android:name="android.view.InputMethod" /></intent-filter><meta-data android:name="android.view.im" android:resource="@xml/method" /></service>
在 PinyinIME的onCreate方法中,
public void onCreate() {mEnvironment = Environment.getInstance(); //环境变量if (mEnvironment.needDebug()) {//Log.d(TAG, "onCreate.");}super.onCreate();startPinyinDecoderService(); mImEn = new EnglishInputProcessor();Settings.getInstance(PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));mInputModeSwitcher = new InputModeSwitcher(this); //改变输入模式的实例mChoiceNotifier = new ChoiceNotifier(this);mGestureListenerSkb = new OnGestureListener(false);mGestureListenerCandidates = new OnGestureListener(true);mGestureDetectorSkb = new GestureDetector(this, mGestureListenerSkb);mGestureDetectorCandidates = new GestureDetector(this,mGestureListenerCandidates);mEnvironment.onConfigurationChanged(getResources().getConfiguration(),this); //config有变化时会跑到}
接着调用onCreateInputView创建输入窗口:
public View onCreateInputView() {if (mEnvironment.needDebug()) {Log.d(TAG, "onCreateInputView.");}LayoutInflater inflater = getLayoutInflater();mSkbContainer = (SkbContainer) inflater.inflate(R.layout.skb_container,null); //inflate 布局文件mSkbContainer.setService(this); mSkbContainer.setInputModeSwitcher(mInputModeSwitcher);mSkbContainer.setGestureDetector(mGestureDetectorSkb);return mSkbContainer;}
关于这个方法的介绍(在父类中):
/*** Create and return the view hierarchy used for the input area (such as* a soft keyboard). This will be called once, when the input area is* first displayed. You can return null to have no input area; the default* implementation returns null.*
**/
然后调用onCreateCandidatesView:
public View onCreateCandidatesView() {if (mEnvironment.needDebug()) {Log.d(TAG, "onCreateCandidatesView.");}LayoutInflater inflater = getLayoutInflater();// Inflate the floating container viewmFloatingContainer = (LinearLayout) inflater.inflate(R.layout.floating_container, null);// The first child is the composing view.mComposingView = (ComposingView) mFloatingContainer.getChildAt(0);mCandidatesContainer = (CandidatesContainer) inflater.inflate(R.layout.candidates_container, null);// Create balloon hint for candidates view.mCandidatesBalloon = new BalloonHint(this, mCandidatesContainer,MeasureSpec.UNSPECIFIED);mCandidatesBalloon.setBalloonBackground(getResources().getDrawable(R.drawable.candidate_balloon_bg));mCandidatesContainer.initialize(mChoiceNotifier, mCandidatesBalloon,mGestureDetectorCandidates);// The floating windowif (null != mFloatingWindow && mFloatingWindow.isShowing()) {mFloatingWindowTimer.cancelShowing();mFloatingWindow.dismiss();}mFloatingWindow = new PopupWindow(this);mFloatingWindow.setClippingEnabled(false);mFloatingWindow.setBackgroundDrawable(null);mFloatingWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);mFloatingWindow.setContentView(mFloatingContainer);setCandidatesViewShown(true);return mCandidatesContainer;}
由于不考虑候选窗口问题,所以就不分析这个方法了。
接着调用onStartInputView,显示出软键盘窗口。
public void onStartInputView(EditorInfo editorInfo, boolean restarting) {if (mEnvironment.needDebug()) {Log.d(TAG, "onStartInputView " + " contentType: "+ String.valueOf(editorInfo.inputType) + " Restarting:"+ String.valueOf(restarting));}Log.d("zmq","PinyinIME onStartInputView");updateIcon(mInputModeSwitcher.requestInputWithSkb(editorInfo)); //更新显示的软键盘resetToIdleState(false);mSkbContainer.updateInputMode(); //更新输入模式isShowInputView = true;setCandidatesViewShown(false);}
其中updateIcon的作用:更新显示软键盘(若是去掉这个函数,则软键盘显示不出来)。具体的实现:
private void updateIcon(int iconId) {if (iconId > 0) {showStatusIcon(iconId); //一般显示的时候iconId的值是大于0的,因为会有一个具体的iconId} else {hideStatusIcon();}}
showStatusIcon方法是在InputManagerService.java中实现的:
public void showStatusIcon(int iconResId) {mStatusIcon = iconResId;mImm.showStatusIcon(mToken, getPackageName(), iconResId);}
其中mImm的类型是InputMethodManager,在 InputMethodManager.java中的实现为:
InputMethodManager mImm;public void showStatusIcon(IBinder imeToken, String packageName, int iconId) {try {mService.updateStatusIcon(imeToken, packageName, iconId);} catch (RemoteException e) {throw new RuntimeException(e);}}
其中mService的类型为:IInputMethodManager,其具体实现的地方为:InputMethodManagerService
public class InputMethodManagerService extends IInputMethodManager.Stubimplements ServiceConnection, Handler.Callback {
}
public void updateStatusIcon(IBinder token, String packageName, int iconId) {int uid = Binder.getCallingUid();long ident = Binder.clearCallingIdentity();try {if (token == null || mCurToken != token) {Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);return;}synchronized (mMethodMap) {if (iconId == 0) {if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");if (mStatusBar != null) {mStatusBar.setIconVisibility("ime", false);}} else if (packageName != null) {if (DEBUG) Slog.d(TAG, "show a small icon for the input method");CharSequence contentDescription = null;try {// Use PackageManager to load labelfinal PackageManager packageManager = mContext.getPackageManager();contentDescription = packageManager.getApplicationLabel(mIPackageManager.getApplicationInfo(packageName, 0,mSettings.getCurrentUserId()));} catch (RemoteException e) {/* ignore */}if (mStatusBar != null) {mStatusBar.setIcon("ime", packageName, iconId, 0,contentDescription != null? contentDescription.toString() : null); //放进去mStatusBar.setIconVisibility("ime", true); //显示出来InputMethodManagerService}}}} finally {Binder.restoreCallingIdentity(ident);}}
mStatusBar 的类型: StatusBarManagerService,具体实现为:
public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,String contentDescription) {enforceStatusBar();synchronized (mIcons) {int index = mIcons.getSlotIndex(slot);if (index < 0) {throw new SecurityException("invalid status bar icon slot: " + slot);}StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.OWNER, iconId,iconLevel, 0,contentDescription);//Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon);mIcons.setIcon(index, icon); // StatusBarIconList mIcons = new StatusBarIconList(); 存入一系列if (mBar != null) {try {mBar.setIcon(index, icon);} catch (RemoteException ex) {}}}}
在显示的时候就取出来,然后设置为Visible:(在 InputMethodManagerService中的 updateStatusIcon方法中 )
mStatusBar.setIconVisibility("ime", true);
public void setIconVisibility(String slot, boolean visible) {enforceStatusBar();synchronized (mIcons) {int index = mIcons.getSlotIndex(slot);if (index < 0) {throw new SecurityException("invalid status bar icon slot: " + slot);}StatusBarIcon icon = mIcons.getIcon(index); //取出来if (icon == null) {return;}if (icon.visible != visible) {icon.visible = visible; //设置显示if (mBar != null) {try {mBar.setIcon(index, icon);} catch (RemoteException ex) {}}}}}
具体显示那个软键盘,就需要看mInputModeSwitcher.requestInputWithSkb:
// Return the icon to update.public int requestInputWithSkb(EditorInfo editorInfo) {mShortMessageField = false;int newInputMode = MODE_SKB_CHINESE;switch (editorInfo.inputType & EditorInfo.TYPE_MASK_CLASS) {case EditorInfo.TYPE_CLASS_NUMBER:case EditorInfo.TYPE_CLASS_DATETIME:newInputMode = MODE_SKB_SYMBOL1_EN;break;case EditorInfo.TYPE_CLASS_PHONE:newInputMode = MODE_SKB_PHONE_NUM;break;case EditorInfo.TYPE_CLASS_TEXT:int v = editorInfo.inputType & EditorInfo.TYPE_MASK_VARIATION;if (v == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS|| v == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD|| v == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD|| v == EditorInfo.TYPE_TEXT_VARIATION_URI) {// If the application request English mode, we switch to it.newInputMode = MODE_SKB_ENGLISH_LOWER;} else {if (v == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {mShortMessageField = true;}// If the application do not request English mode, we will// try to keep the previous mode.int skbLayout = (mInputMode & MASK_SKB_LAYOUT);newInputMode = mInputMode;if (0 == skbLayout) {if ((mInputMode & MASK_LANGUAGE) == MASK_LANGUAGE_CN) {newInputMode = MODE_SKB_CHINESE;} else {newInputMode = MODE_SKB_ENGLISH_LOWER;}}//通过判断local来判断当前显示成中文还是英文输入Locale locale = mImeService.getResources().getConfiguration().locale;String language = locale.getLanguage();if (language.endsWith("zh")){newInputMode = MODE_SKB_CHINESE; }else{newInputMode = MODE_SKB_ENGLISH_LOWER; }}break;default:// Try to keep the previous mode.int skbLayout = (mInputMode & MASK_SKB_LAYOUT);newInputMode = mInputMode;if (0 == skbLayout) {if ((mInputMode & MASK_LANGUAGE) == MASK_LANGUAGE_CN) {newInputMode = MODE_SKB_CHINESE;} else {newInputMode = MODE_SKB_ENGLISH_LOWER;}}break;}mEditorInfo = editorInfo;saveInputMode(newInputMode);prepareToggleStates(true);return mInputIcon;}
这样就解决了随着Setting中中英文的切换对应改变软键盘中英文输入的问题。
接着看第二个问题: Setting中中英文的切换对应改变软键盘中字符,即是中文的时候,软键盘中显示字符为中文,例如:中文,发送,下一个等;英文的时候显示:English、Send、Next等。可是目前的情况是切换中英文,软键盘中字符不会发生改变,除非开关机,字符会跟最开始的语言一致。
首先看其布局文件:skb_container.xml,是在 onCreateInputView中inflate。这个只是一个container,真正的布局是在xml文件中。
其中关于软键盘的是前面带skb的xml文件。查看其中的字符是否都是国际化写法,在value中是否有中文和英文对应的string。发现没有中文对应的string,且写法都是直接使用的中文字符串:
然后修改其写法,增加中文对应的string文件。修改好后,满心期待以为OK了,结果,测试结果还是 软键盘中字符不会发生改变。
没有办法,虽然加了string,但是仍然木有改变,那就估计是源码中写法的问题了。继续看源码吧~
由于之前没有看过源码,对具体的实现也不清晰。于是就使用了最笨的方法:看哪里有用到xml文件。
发现在InputModeSwitcher的getSkbLayout方法和SkbContainer中的 updateSkbLayout方法中均有使用,然后看了下调用关系,发现在 SkbContainer 的 updateInputMode方法会先调用 getSkbLayout得到skb layout,然后根据判断是否更新 skb layout,即调用 updateSkbLayout。而 updateInputMode方法是在 方法PinyinIME的 onStartInputView中,用来更新输入模式:(正常代码顺序是从下往上)
public int getSkbLayout() {int layout = (mInputMode & MASK_SKB_LAYOUT);switch (layout) {case MASK_SKB_LAYOUT_QWERTY:return R.xml.skb_qwerty; //直接返回xml对应的idcase MASK_SKB_LAYOUT_SYMBOL1:return R.xml.skb_sym1;case MASK_SKB_LAYOUT_SYMBOL2:return R.xml.skb_sym2;case MASK_SKB_LAYOUT_SMILEY:return R.xml.skb_smiley;case MASK_SKB_LAYOUT_PHONE:return R.xml.skb_phone;}return 0;}
private void updateSkbLayout() {int screenWidth = mEnvironment.getScreenWidth();int keyHeight = mEnvironment.getKeyHeight();int skbHeight = mEnvironment.getSkbHeight();Resources r = mContext.getResources();if (null == mSkbFlipper) {mSkbFlipper = (ViewFlipper) findViewById(R.id.alpha_floatable);}mMajorView = (SoftKeyboardView) mSkbFlipper.getChildAt(0);SoftKeyboard majorSkb = null;SkbPool skbPool = SkbPool.getInstance();switch (mSkbLayout) {case R.xml.skb_qwerty:majorSkb = skbPool.getSoftKeyboard(R.xml.skb_qwerty,R.xml.skb_qwerty, screenWidth, skbHeight, mContext); //从skb池中直接得到一个Softkeyboardbreak;case R.xml.skb_sym1:majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym1, R.xml.skb_sym1,screenWidth, skbHeight, mContext);break;case R.xml.skb_sym2:majorSkb = skbPool.getSoftKeyboard(R.xml.skb_sym2, R.xml.skb_sym2,screenWidth, skbHeight, mContext);break;case R.xml.skb_smiley:majorSkb = skbPool.getSoftKeyboard(R.xml.skb_smiley,R.xml.skb_smiley, screenWidth, skbHeight, mContext);break;case R.xml.skb_phone:majorSkb = skbPool.getSoftKeyboard(R.xml.skb_phone,R.xml.skb_phone, screenWidth, skbHeight, mContext);break;default:}if (null == majorSkb || !mMajorView.setSoftKeyboard(majorSkb)) {return;}mMajorView.setBalloonHint(mBalloonOnKey, mBalloonPopup, false);mMajorView.invalidate();}
public void updateInputMode() {int skbLayout = mInputModeSwitcher.getSkbLayout(); //得到skb layoutif (mSkbLayout != skbLayout) {mSkbLayout = skbLayout;updateSkbLayout(); //更新Skb layout}mLastCandidatesShowing = false;if (null == mMajorView) return;SoftKeyboard skb = mMajorView.getSoftKeyboard();if (null == skb) return;skb.enableToggleStates(mInputModeSwitcher.getToggleStates());invalidate();return;}
mSkbContainer.updateInputMode();
具体的得到skb layout是在 SkbContainer的 方法updateSkbLayout中,
SkbPool skbPool = SkbPool.getInstance();
skbPool.getSoftKeyboard(R.xml.skb_sym1, R.xml.skb_sym1,screenWidth, skbHeight, mContext);
/*** Class used to cache previously loaded soft keyboard layouts. //用来缓存之前已经加载的软键盘布局*/
public class SkbPool {
}
说明在第一次加载过这些软键盘布局后,后面是不会再加载的而是直接使用的缓存布局。
这样就知道为什么切换中英文后,布局中的字符是不会改变的,而开关机后就改变了,因为只加载了一次呀。
// Try to find the keyboard in the pool with the cache id. If there is no// keyboard found, try to load it with the given xml id. //没有的时候才加载啊~public SoftKeyboard getSoftKeyboard(int skbCacheId, int skbXmlId,int skbWidth, int skbHeight, Context context) {for (int i = 0; i < mSoftKeyboards.size(); i++) {SoftKeyboard skb = mSoftKeyboards.elementAt(i); //看mSoftKeyboardsif (skb.getCacheId() == skbCacheId && skb.getSkbXmlId() == skbXmlId) {skb.setSkbCoreSize(skbWidth, skbHeight);skb.setNewlyLoadedFlag(false);return skb;}}if (null != context) {XmlKeyboardLoader xkbl = new XmlKeyboardLoader(context);SoftKeyboard skb = xkbl.loadKeyboard(skbXmlId, skbWidth, skbHeight); //没有的时候才Loadif (skb != null) {if (skb.getCacheFlag()) {skb.setCacheId(skbCacheId);mSoftKeyboards.add(skb); //还没有加载过的,则加载后保存在mSoftKeyboards}}return skb;}return null;}
}
private Vector<SoftKeyboard> mSoftKeyboards = new Vector<SoftKeyboard>();
于是查看是否有清除掉mSoftKeyboards的地方,在SkbPool中,有方法来清掉 mSoftKeyboards中存储的数据 :
public void resetCachedSkb() {mSoftKeyboards.clear();}
这样,思路就清晰了,因为每次在切换中英文后,PinyinIME都会重新启动,于是想到在 PinyinIME的 onDestroy方法中,调用resetCachedSkb方法来清除掉已经保存的数据: public void onDestroy() {if (mEnvironment.needDebug()) {Log.d(TAG, "onDestroy.");}unbindService(mPinyinDecoderServiceConnection);Settings.releaseInstance();//zhangmq add for refresh skb layoutSkbPool skbPool = SkbPool.getInstance();skbPool.resetCachedSkb();skbPool = null;//zhangmq add for refresh skb layout endsuper.onDestroy();}
但是但是但是,万万没想到,结果竟然还是不行!!!!!
明明 mSoftKeyboards都已经清掉了的,还是没有更新,说明 mSoftKeyboards中的layout还不是load的根源。
于是看 mSoftKeyboards在add的时候,是在哪里获得的数据,在 SkbPool的getSoftKeyboard方法中, mSoftKeyboards还没有的layout, mSoftKeyboards会add进去 :
if (null != context) {XmlKeyboardLoader xkbl = new XmlKeyboardLoader(context);SoftKeyboard skb = xkbl.loadKeyboard(skbXmlId, skbWidth, skbHeight); //就看loadKeyboard是怎么loadif (skb != null) {if (skb.getCacheFlag()) {skb.setCacheId(skbCacheId);mSoftKeyboards.add(skb);}}return skb;}
在XmlKeyboardLoader的loadKeyboard方法(只看需要关注的地方): public SoftKeyboard loadKeyboard(int resourceId, int skbWidth, int skbHeight) {if (null == mContext) return null;Resources r = mResources;SkbPool skbPool = SkbPool.getInstance(); //也会初始化SkbPool 说明后面也会用到缓存XmlResourceParser xrp = mContext.getResources().getXml(resourceId); //Xml解析mSkbTemplate = null;SoftKeyboard softKeyboard = null;Drawable skbBg;Drawable popupBg;Drawable balloonBg;SoftKey softKey = null;KeyCommonAttributes attrDef = new KeyCommonAttributes(xrp);KeyCommonAttributes attrSkb = new KeyCommonAttributes(xrp);KeyCommonAttributes attrRow = new KeyCommonAttributes(xrp);KeyCommonAttributes attrKeys = new KeyCommonAttributes(xrp);KeyCommonAttributes attrKey = new KeyCommonAttributes(xrp);mKeyXPos = 0;mKeyYPos = 0;mSkbWidth = skbWidth;mSkbHeight = skbHeight;try {mKeyXMargin = 0;mKeyYMargin = 0;mXmlEventType = xrp.next();while (mXmlEventType != XmlResourceParser.END_DOCUMENT) {mNextEventFetched = false;if (mXmlEventType == XmlResourceParser.START_TAG) {String attr = xrp.getName();// 1. Is it the root element, "keyboard"?if (XMLTAG_KEYBOARD.compareTo(attr) == 0) {// 1.1 Get the keyboard template id.int skbTemplateId = xrp.getAttributeResourceValue(null,XMLATTR_SKB_TEMPLATE, 0);// 1.2 Try to get the template from pool. If it is not// in, the pool will try to load it. //从pool中得到模板 没有的话就开始加载mSkbTemplate = skbPool.getSkbTemplate(skbTemplateId,mContext); //所以这里就是问题所在了if (null == mSkbTemplate|| !attrSkb.getAttributes(attrDef)) {return null;}boolean cacheFlag = getBoolean(xrp,XMLATTR_SKB_CACHE_FLAG, DEFAULT_SKB_CACHE_FLAG);boolean stickyFlag = getBoolean(xrp,XMLATTR_SKB_STICKY_FLAG,DEFAULT_SKB_STICKY_FLAG);boolean isQwerty = getBoolean(xrp, XMLATTR_QWERTY,false);boolean isQwertyUpperCase = getBoolean(xrp,XMLATTR_QWERTY_UPPERCASE, false);softKeyboard = new SoftKeyboard(resourceId,mSkbTemplate, mSkbWidth, mSkbHeight);softKeyboard.setFlags(cacheFlag, stickyFlag, isQwerty,isQwertyUpperCase);mKeyXMargin = getFloat(xrp, XMLATTR_KEY_XMARGIN,mSkbTemplate.getXMargin());mKeyYMargin = getFloat(xrp, XMLATTR_KEY_YMARGIN,mSkbTemplate.getYMargin());skbBg = getDrawable(xrp, XMLATTR_SKB_BG, null);popupBg = getDrawable(xrp, XMLATTR_POPUP_BG, null);balloonBg = getDrawable(xrp, XMLATTR_BALLOON_BG, null);if (null != skbBg) {softKeyboard.setSkbBackground(skbBg);}if (null != popupBg) {softKeyboard.setPopupBackground(popupBg);}if (null != balloonBg) {softKeyboard.setKeyBalloonBackground(balloonBg);}softKeyboard.setKeyMargins(mKeyXMargin, mKeyYMargin);} else if (XMLTAG_ROW.compareTo(attr) == 0) {if (!attrRow.getAttributes(attrSkb)) {return null;}// Get the starting positions for the row.mKeyXPos = getFloat(xrp, XMLATTR_START_POS_X, 0);mKeyYPos = getFloat(xrp, XMLATTR_START_POS_Y, mKeyYPos);int rowId = getInteger(xrp, XMLATTR_ROW_ID,KeyRow.ALWAYS_SHOW_ROW_ID);softKeyboard.beginNewRow(rowId, mKeyYPos);} else if (XMLTAG_KEYS.compareTo(attr) == 0) {if (null == softKeyboard) return null;if (!attrKeys.getAttributes(attrRow)) {return null;}String splitter = xrp.getAttributeValue(null,XMLATTR_KEY_SPLITTER);splitter = Pattern.quote(splitter);String labels = xrp.getAttributeValue(null,XMLATTR_KEY_LABELS);String codes = xrp.getAttributeValue(null,XMLATTR_KEY_CODES);if (null == splitter || null == labels) {return null;}String labelArr[] = labels.split(splitter);String codeArr[] = null;if (null != codes) {codeArr = codes.split(splitter);if (labelArr.length != codeArr.length) {return null;}}for (int i = 0; i < labelArr.length; i++) {softKey = new SoftKey();int keyCode = 0;if (null != codeArr) {keyCode = Integer.valueOf(codeArr[i]);}softKey.setKeyAttribute(keyCode, labelArr[i],attrKeys.repeat, attrKeys.balloon);softKey.setKeyType(mSkbTemplate.getKeyType(attrKeys.keyType), null, null);float left, right, top, bottom;left = mKeyXPos;right = left + attrKeys.keyWidth;top = mKeyYPos;bottom = top + attrKeys.keyHeight;if (right - left < 2 * mKeyXMargin) return null;if (bottom - top < 2 * mKeyYMargin) return null;softKey.setKeyDimensions(left, top, right, bottom);softKeyboard.addSoftKey(softKey);mKeyXPos = right;if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {return null;}}} else if (XMLTAG_KEY.compareTo(attr) == 0) {if (null == softKeyboard) {return null;}if (!attrKey.getAttributes(attrRow)) {return null;}int keyId = this.getInteger(xrp, XMLATTR_ID, -1);if (keyId >= 0) {softKey = mSkbTemplate.getDefaultKey(keyId);} else {softKey = getSoftKey(xrp, attrKey);}if (null == softKey) return null;// Update the position for next key.mKeyXPos = softKey.mRightF;if ((int) mKeyXPos * mSkbWidth > mSkbWidth) {return null;}// If the current xml event type becomes a starting tag,// it indicates that we have parsed too much to get// toggling states, and we started a new row. In this// case, the row starting position information should// be updated.if (mXmlEventType == XmlResourceParser.START_TAG) {attr = xrp.getName();if (XMLTAG_ROW.compareTo(attr) == 0) {mKeyYPos += attrRow.keyHeight;if ((int) mKeyYPos * mSkbHeight > mSkbHeight) {return null;}}}softKeyboard.addSoftKey(softKey);}} else if (mXmlEventType == XmlResourceParser.END_TAG) {String attr = xrp.getName();if (XMLTAG_ROW.compareTo(attr) == 0) {mKeyYPos += attrRow.keyHeight;if ((int) mKeyYPos * mSkbHeight > mSkbHeight) {return null;}}}// Get the next tag.if (!mNextEventFetched) mXmlEventType = xrp.next();}xrp.close();softKeyboard.setSkbCoreSize(mSkbWidth, mSkbHeight);return softKeyboard;} catch (XmlPullParserException e) {// Log.e(TAG, "Ill-formatted keybaord resource file");} catch (IOException e) {// Log.e(TAG, "Unable to read keyboard resource file");}return null;}
看其中的这句:
mSkbTemplate = skbPool.getSkbTemplate(skbTemplateId,mContext); //所以这里就是问题所在了
public SkbTemplate getSkbTemplate(int skbTemplateId, Context context) {for (int i = 0; i < mSkbTemplates.size(); i++) {SkbTemplate t = mSkbTemplates.elementAt(i); //直接在mSkbTemplates中取值。if (t.getSkbTemplateId() == skbTemplateId) {return t;}}if (null != context) {XmlKeyboardLoader xkbl = new XmlKeyboardLoader(context);SkbTemplate t = xkbl.loadSkbTemplate(skbTemplateId); //没有的时候才会加载if (null != t) {mSkbTemplates.add(t);return t;}}return null;}
我们一直都没有清理过这个mSkbTemplates,所以这个里面缓存的内容也一直存在。这样导致 mSoftKeyboards保存的内容也相当于没有清理过。这样就找到了问题的根源。相应对策:在清理 mSoftKeyboards的地方(在SkbPool中,有方法来清掉 mSoftKeyboards中存储的数据),加上清理 mSkbTemplates的方法:
public void resetCachedSkb() {mSkbTemplates.clear();mSoftKeyboards.clear();}
经过验证,OK咯~
解决这两个问题,花了几天的时间,主要原因一个是对于这块的代码不熟悉,关于流程中传递的具体参数都是通过添加打印消息得来;最开始看代码的时候没有清晰的思路,就知道一个劲在那看,但是基本没得到很多有用的信息。还有一个就是看代码对代码的理解力没有太强。总的来说,解决了这个问题,心里还是挺开心的,还发了个朋友圈嘚瑟了一下,虽然略显幼稚,但是在攻克了几天的问题被解决的时候,这种成就感真的是太棒啦啦~