谷歌拼音输入法PinyinIME源码修改----随着Setting中中英文的切换对应改变软键盘中英文输入且字符变换

项目中使用的是Google的输入法:谷歌拼音输入法,即PinyinIME。
客户提出需求:需要在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咯~

解决这两个问题,花了几天的时间,主要原因一个是对于这块的代码不熟悉,关于流程中传递的具体参数都是通过添加打印消息得来;最开始看代码的时候没有清晰的思路,就知道一个劲在那看,但是基本没得到很多有用的信息。还有一个就是看代码对代码的理解力没有太强。总的来说,解决了这个问题,心里还是挺开心的,还发了个朋友圈嘚瑟了一下,虽然略显幼稚,但是在攻克了几天的问题被解决的时候,这种成就感真的是太棒啦啦~





本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/37974.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安装google输入法后,左shift键不能切换中英文

解决方法 打开fcitx配置页 如图应有两个选项&#xff0c;即google拼音和 键盘英语&#xff0c;如不足&#xff0c;则选择左下角添加。 此后虽然可切换中英文&#xff0c;但是打开一个新页面后只按shift键却不能正常切换中英&#xff0c;必须要ctrl加空格后才能切换&#xff0c…

写在发现谷歌拼音输入法网站消失的今天

今天偶然发现谷歌拼音输入法PC版的网页消失了&#xff0c;我想以后可能都不会有了&#xff0c;除非谷歌重返大陆。 PC端的谷歌拼音输入法从2013年就已经停止更新&#xff0c;但这并不妨碍我继续使用它&#xff0c;之前它的官网一直都在&#xff0c;也提供下载。 之所以喜欢这…

Google推出拼音输入法了!

网址&#xff1a; http://tools.google.com/pinyin 下载地址&#xff1a;http://dl.google.com/pinyin/GooglePinyinInstaller.exe 用了一下&#xff0c;感觉挺好用的&#xff0c;同志们有兴趣的话&#xff0c;也试试看吧。恩 聪明的谷歌拼音输入法五大特色&#xff1a; …

网页上使用的输入法——Google Transliteration

简介 输入法通常是装在PC上用的&#xff0c;但是API有关部门的童鞋发现了专门在网页上用的输入法&#xff08;见上图&#xff09;。 这个插件的作用&#xff0c;按照官方说法&#xff1a; 您可以使用 Google 输入工具在网络中的任何位置以所选语言轻松地输入内容。 此接口采集自…

Python实战之12306抢票

实战&#xff1a;12306抢票 注意&#xff1a;代码运行之后&#xff0c;需要手动使用12306APP扫码登录 代码如下&#xff1a; import csv from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.support.ui import…

python实现12306全自动抢票

这个脚本目前只能刷一趟车的&#xff0c;人数可以是多个&#xff0c;支持选取作为类型等。 实现思路是splinter.browser模拟浏览器登陆和操作&#xff0c;由于12306的验证码不好自动识别&#xff0c;所以&#xff0c;验证码需要用户进行手动识别&#xff0c;并进行登陆操作&am…

小年到了,回家抢票太难,用Python做个脚本12306自动查票以及自动购票....

今天就是小年了&#xff0c;听说还有人买不到票&#xff1f;不要慌&#xff0c;今天咱们来用Python做一个自动查票抢票的脚本&#xff0c;24小时抢票&#xff0c;谁抢的过你&#xff01;源码包已打包文件夹获取方式&#xff1a;点击这里【 Python全套资料】 即可获取。 准备工作…

C++写的12306抢票软件

写在前面的话 每年逢年过节&#xff0c;一票难求读者肯定不陌生。这篇文章&#xff0c;我们带领读者从零实现一款12306刷票软件&#xff0c;其核心原理还是通过发送http请求模拟登录12306网站的购票的过程&#xff0c;最后买到票。 郑重申明一下&#xff1a;这里介绍的技术仅供…

Python抢票神器

又到了一年一度的抢票大战&#xff0c;本来就辛苦劳累了一年&#xff0c;想着可以早点订到票跟家里人团聚。所以&#xff0c;许多人宁愿多花些钱去找黄牛买票。 但今年各种抢票软件的横行&#xff0c;还有官方出的加速包&#xff0c;导致连黄牛都不敢保证能买到票。你无奈的只能…

chatgpt赋能python:如何利用Python抢票

如何利用Python抢票 Python是一种非常流行的编程语言&#xff0c;可以轻松编写自动化工具。如果你想要在抢票热潮中获得胜利&#xff0c;这篇文章将向你展示如何使用Python编写程序来自动抢票。 抢票原理 在开始编写程序之前&#xff0c;我们需要了解如何抢票。抢票的原理是…

python写一个简单的12306抢票

引言 每逢过年就到了12306抢票高峰期&#xff0c;自己总想研究一下12306购票的流程&#xff0c;虽然网上已经很多资料&#xff0c;但是总比不过自己的亲身体会&#xff0c;于是便琢磨着写一个抢票软件&#xff0c;本人比较熟悉python&#xff0c;所以软件是用python写的。 使…

Python3.6实现12306火车票自动抢票(内含源码)

最近在学Python&#xff0c;刚好过完年啦&#xff01;大家应该都需要买高铁票继续去当打工人了吧&#xff01;所以用Python写了这个12306抢票脚本&#xff0c;分享出来&#xff0c;与大家共同交流和学习&#xff0c;有不对的地方&#xff0c;请大家多多指正。话不多说&#xff…

12306抢票软件实现(二)

近期好多人私信我&#xff0c;能不能需要分享完整代码。为了帮助大家&#xff0c;我以及将代码整理和教学视频我已经上传了CSDN&#xff0c;欢迎私信交流&#xff0c;共同学习共同进步&#xff1a;代码及视频下载地址 求给五星好评&#xff0c;谢谢啦 前面已经介绍了实现登录1…

用 Python 代码自动抢火车票

市场上很多火车票抢票软件大家应该非常熟悉&#xff0c;但很少有人研究具体是怎么实现的&#xff0c;所以觉得很神秘&#xff0c;其实很简单。下面使用Python模拟抢票程序&#xff0c;给大家揭秘抢票到底是怎么回事。 该代码仅供参考&#xff0c;主要用于大家沟通交流&#xff…

python最新抢票脚本

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;&#x1f4e3; 系列专栏&#xff1a;python网络爬虫&…

手把手教你用python一键抢12306火车票(附代码)

哈喽&#xff0c;哈喽~&#xff0c;一年一度的抢火车票大战正式拉开序幕… 然饿大多数人碰到的是这种情况&#xff1a;当你满心期待摩拳擦掌准备抢票的时候&#xff0c;你会发现一票难求&#xff01;想回趟家真难&#xff01; 那么作为程序猿的你&#xff0c;当然要用程序猿的…

chatgpt赋能python:Python怎么抢票?

Python怎么抢票&#xff1f; Python是一种伟大的编程语言&#xff0c;因为它可以使我们的生活更加便利。有10年python编程经验的工程师会告诉你&#xff0c;利用Python编程可以轻松地抢到大热节目或演出的票。本文将介绍使用Python编写抢票机器人的方法。 概述 抢票机器人是…

chatgpt赋能python:Python自动抢票:让你的购票经历更加简单便捷!

Python自动抢票&#xff1a;让你的购票经历更加简单便捷&#xff01; 随着互联网的发展&#xff0c;越来越多的人选择在网上购买火车、飞机等交通工具的票。但是&#xff0c;一些火爆的车次、航班往往在开售后仅仅几秒钟内就被抢完&#xff0c;让很多想出行的人非常苦恼。在这…

基于微信小程序的餐厅点餐软件设计及开发

目 录 摘要 I Abstract II 1 前言 1 1.1 选题背景 1 1.2 研究的目的和意义 1 2 相关技术简介 3 2.1 系统的实现架构 3 2.2 系统使用的技术 3 2.2.1 Eclipse 3 2.2.2 Java 4 2.2.3 MySQL简介 5 2.2.4 微信小程序简介 6 2.3 系统使用的开发环境 6 3 系统需求分析 7 3.1 功能需求 …

餐饮行业移动管理系统—Pad点餐系统

餐饮行业移动管理系统—Pad点餐系统 员工可通过PC端查询或管理饭店信息&#xff0c;即使更新信息&#xff0c;客户可以进行方便快捷的点菜操作。 功能要求&#xff1a; PC端功能&#xff1a;管理菜谱&#xff0c;餐厅&#xff0c;员工&#xff0c;订单信息&#xff1b;服务端…