谷歌拼音输入法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 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<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 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咯~

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





  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
pinyinime输入法是一种基于拼音输入输入法软件,其源码是指该输入法的程序代码。输入法是为了方便用户使用手机或电脑输入文字而开发的一种应用程序。pinyinime输入法源码包含了该输入法的所有逻辑和功能实现。 pinyinime输入法源码通常包括以下几个部分:拼音转换模块、词库、界面设计和用户交互等。拼音转换模块是输入法的核心部分,负责将用户输入拼音转换为对应的汉字或词组。词库包含了大量的常用词汇和短语,用于辅助拼音转换的匹配。界面设计部分则是指输入法界面的布局和样式设计,主要包括键盘、候选词列表等组件。用户交互包括用户输入的处理和系统响应等。 pinyinime输入法源码的实现主要依赖于编程语言和相关开发技术。例如,Java是一种常用的编程语言,在Android平台上使用Java语言进行开发;C++也是一种常用的编程语言,用于在Windows平台上进行输入法开发。开发者可以利用各种工具和框架,如Android Studio、Visual Studio等,来编写和调试输入法源码。 通过研究pinyinime输入法源码,开发者可以了解到拼音输入法的原理和实现方式。同时,开发者也可以根据自己的需求对源码进行修改和扩展,以定制化符合特定用户需求的输入法软件。输入法源码输入法开发和定制的基础,对于想要深入了解和参与输入法开发的人来说,研究和学习源码是非常有价值的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值