项目中使用的是Google的输入法:谷歌拼音输入法,即PinyinIME。
客户提出需求:需要在Setting中切换中英文的时候,输入法对应成中英文输入,并且字符也对应成中英文,即Setting中设置为中文的时候,输入法中对应输入中文,且布局中显示的字符也对应成中文。
由于水平有限,加上平时输入法这块看得比较少,解决这个问题还是花了不少时间。
首先,看到
PinyinIME中,根本就没有定义中文的string,于是就把对应的中文的string加上去。加上去后,点击打开输入法,有个谷歌拼音输入法设置界面,里面的字符是可以跟随Setting中中英文切换而变化的。于是我自信地以为已经OK了。图样图森破~
接着就开始处理Setting中切换中英文输入法对应中英文输入的问题。
先了解输入法的大概流程。
在PinyinIME中,PinyinIME是继承InputMethodService的,是一个Service,其在Manifest中声明为:
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 view
mFloatingContainer = (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 window
if (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.Stub
implements 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 label
final 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对应的id
case 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池中直接得到一个Softkeyboard
break;
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 layout
if (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); //看mSoftKeyboards
if (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); //没有的时候才Load
if (skb != null) {
if (skb.getCacheFlag()) {
skb.setCacheId(skbCacheId);
mSoftKeyboards.add(skb); //还没有加载过的,则加载后保存在mSoftKeyboards
}
}
return skb;
}
return null;
}
}
private Vector mSoftKeyboards = new Vector();
于是查看是否有清除掉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 layout
SkbPool skbPool = SkbPool.getInstance();
skbPool.resetCachedSkb();
skbPool = null;
//zhangmq add for refresh skb layout end
super.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是怎么load
if (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咯~
解决这两个问题,花了几天的时间,主要原因一个是对于这块的代码不熟悉,关于流程中传递的具体参数都是通过添加打印消息得来;最开始看代码的时候没有清晰的思路,就知道一个劲在那看,但是基本没得到很多有用的信息。还有一个就是看代码对代码的理解力没有太强。总的来说,解决了这个问题,心里还是挺开心的,还发了个朋友圈嘚瑟了一下,虽然略显幼稚,但是在攻克了几天的问题被解决的时候,这种成就感真的是太棒啦啦~