现在的位置: 首页 > 综合 > 正文

CCLabelTTF 创建 crash

2017年11月11日 ⁄ 综合 ⁄ 共 5140字 ⁄ 字号 评论关闭

公司引擎版本是2.1.4,这个问题在2.2.3上已经修复,其他版本不详

    之前解决了tab的那个问题之后,又来一个问题,还是label相关的。

    表现为,在完成任务之后,会弹出一个奖励提示框,里面会提示获得的奖励,但是有几个任务,一点完成,就会crash。怀疑是内存重复释放之类的问题,最后层层追踪之后,发现和其他的都无关,就是每次调用CCLabelTTF::create()之后就直接crash,悲催的开始跟进。

    从 CCLabelTTF::create() -> CCLabelTTF::initWithString() -> CCLabelTTF::setString() -> CCLabelTTF::updateTexture()
跟到 CCTexture。到这里:

bool CCLabelTTF::updateTexture()
{
    CCTexture2D *tex;
    tex = new CCTexture2D();
    
    if (!tex)
        return false;
    #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    
        ccFontDefinition texDef = _prepareTextDefinition(true);
        CCLog("%s..........", m_string.c_str());
        tex->initWithString( m_string.c_str(), &texDef ); 
    #else
    … … … … 
    #endif
    … … … … 
    return true;
}

    这个texDef是封装的一个环境描述类,主要就是写了字体,字号,label长宽高这些,因为如果label只设置宽度不设置高度,引擎会自动处理换行。所以需要这些。然后继续往下:

bool CCTexture2D::initWithString(const char *text, ccFontDefinition *textDefinition)
{
        … … … … 
        CCImage* pImage = new CCImage();
        do
        {
            CC_BREAK_IF(NULL == pImage);

            bRet = pImage->initWithStringShadowStroke(text,
                                                      (int)textDefinition->m_dimensions.width,
                                                      (int)textDefinition->m_dimensions.height,
                                                      eAlign,
                                                      textDefinition->m_fontName.c_str(),
                                                      textDefinition->m_fontSize,
                                                      textDefinition->m_fontFillColor.r / 255,
                                                      textDefinition->m_fontFillColor.g / 255,
                                                      textDefinition->m_fontFillColor.b / 255,
                                                      shadowEnabled,
                                                      shadowDX,
                                                      shadowDY,
                                                      shadowOpacity,
                                                      shadowBlur,
                                                      strokeEnabled,
                                                      strokeColorR,
                                                      strokeColorG,
                                                      strokeColorB,
                                                      strokeSize);
            
            CC_BREAK_IF(!bRet);
            bRet = initWithImage(pImage);
            
        } while (0);
        
        CC_SAFE_RELEASE(pImage);        // crashes here
        return bRet;
        … … … … 
}

        这里就诡异了,通过之前那个tab问题的经验,估计又是创建image出错了,但是这次还真不是。通过暴力log流,发现是crash在
release那里。之前的层层判断都能过,那么如果crash在这里,只能说明内存被破坏造成了野指针,然后开始跟initWithStringShadowStroke,发现这个函数就是从java去取一个image而已,调用的是 引擎目录/cocos2dx/platform/android/src 下面的 Cocos2dxBitmap.java 的
initWithStringShadowStroke函数,继续看这个函数:

	public static void createTextBitmapShadowStroke() {
		
		//重构字符
		pString = Cocos2dxBitmap.refactorString(pString);
		//创建画笔
		final Paint paint = Cocos2dxBitmap.newPaint(pFontName, pFontSize,
				horizontalAlignment);
		//计算纹理大小和一些相关的东西
		final TextProperty textProperty = Cocos2dxBitmap.computeTextProperty(
				pString, pWidth, pHeight, paint);
		//确定纹理高度
		final int bitmapTotalHeight = (pHeight == 0 ? textProperty.mTotalHeight
				: pHeight);

		//设置阴影
		if (shadow) {
			。。。 。。。 。。。 。。。
		}
		
		//画字
		final Bitmap bitmap = Bitmap.createBitmap(textProperty.mMaxWidth
				+ (int) bitmapPaddingX, bitmapTotalHeight
				+ (int) bitmapPaddingY, Bitmap.Config.ARGB_8888);

		// 设置粗体
		if (stroke) {
			。。。 。。。 。。。 。。。
		}

		Cocos2dxBitmap.initNativeObject(bitmap);
	}

    这个函数太长,精简之后,就是上面这些关键点。先说这个函数的流程,把字符串,根据里面的 \n ,先把他断行,然后如果一行太长,超过label的长度,就把他分成2行显示,然后这些处理好了之后,就可以算出这个image需要的大小,这些都是在计算纹理大小这一步处理的。然后交给android创建bitmap,然后开始往上面写字,把这个图画出来。到这里之后,发现是在计算纹理大小那里奔溃的,继续跟:

	private static TextProperty computeTextProperty(final String pString,
			final int pWidth, final int pHeight, final Paint pPaint) {
		
		… … … … 
		final String[] lines = Cocos2dxBitmap.splitString(pString, pWidth,
				pHeight, pPaint);
		… … … … 
		return new TextProperty(maxContentWidth, h, lines);
	}

    这里就会把这个字符串做拆行的具体处理了:

<span style="color:#333333;">	private static String[] splitString(final String pString,
			final int pMaxWidth, final int pMaxHeight, final Paint pPaint) {
		
		if (pMaxWidth != 0) {
			final LinkedList<String> strList = new LinkedList<String>();
			int iIndex=0;
			for (final String line : lines) {
				/*
				 * 如果一行的长度大于label宽度,就拆成2行
				 */
				final int lineWidth = (int) FloatMath.ceil(pPaint
						.measureText(line));
				if (lineWidth > pMaxWidth) {
					strList.addAll(Cocos2dxBitmap.</span><span style="color:#ff0000;">divideStringWithMaxWidth</span><span style="color:#333333;">(
							line, pMaxWidth, pPaint));
				} else {
					strList.add(line);
				}
				/* Should not exceed the max height. */
				if (maxLines > 0 && strList.size() >= maxLines) {
					break;
				}
				iIndex++;
			}
			/* Remove exceeding lines. */
			if (maxLines > 0 && strList.size() > maxLines) {
				while (strList.size() > maxLines) {
					strList.removeLast();
				}
			}

			ret = new String[strList.size()];
			strList.toArray(ret);
		} else if (pMaxHeight != 0 && lines.length > maxLines) {
			… … … … 
		} else {
			… … … … 
		}

		return ret;
	}</span>

    上面这个函数,首先是按 \n 把文字拆航,如果label的宽度是0,那么就只按 \n 分行,走else。如果label的高度不为0,也就是说只指定了高度没指定宽度,拆行后的行数大于label指定高度能显示的内容,走 else if , 如果指定了宽度,就走if里面的。然后里面的处理,会判断按 \n 拆行过后的单行字符串,如果长度大于label宽度,就调用divideStringWithMaxWidth 把它分成多行:

	private static LinkedList<String> divideStringWithMaxWidth(
			final String pString, final int pMaxWidth, final Paint pPaint) {
		
		final int charLength = pString.length();
		int start = 0;
		int tempWidth = 0;
		final LinkedList<String> strList = new LinkedList<String>();
		//遍历从start到i的字符串的显示长度
		for (int i = 1; i <= charLength; ++i) 
		{
			tempWidth = (int) FloatMath.ceil(pPaint.measureText(pString, start,
					i));
			//如果长度大于或等于label宽度,拆行
			if (tempWidth >= pMaxWidth) 
			{
				//找空格最后一次出现的位置
				final int lastIndexOfSpace = pString.substring(0, i)
						.lastIndexOf(" ");
				//如果找到了空格(字符串有可能不包含空格),并且空格不是第一个字符,则截取start到空格之前的
				if (lastIndexOfSpace != -1 && lastIndexOfSpace > start) 
				{
					strList.add(pString.substring(start, lastIndexOfSpace));
					i = lastIndexOfSpace + 1; // skip space
				} 
				//没找到空格,直接按字符个数拆分
				else
				{
					if (tempWidth > pMaxWidth) 
					{
						strList.add(pString.substring(start, i - 1));
						--i;
					}
					else 
					{
						strList.add(pString.substring(start, i));
					}
				}
				//**************如果拆行过后下一行的开头有空格,则去掉空格**************
				if (pString.length() > 0 && i < pString.length()) {
					while (pString.charAt(i) == ' ') {
						++i;
					}
				}
				start = i;
			}
		}

		if (start < charLength) {
			strList.add(pString.substring(start));
		}
		return strList;
	}

    请注意上面我怒写了很多星号的地方。注意里面那个while,有没有写错啊,有木有啊。++i 之后 i 有木有可能越界啊~~有木有啊~~!!

    做如下修改后万事大吉:

while (i<charLength&&pString.charAt(i) == ' ') {
	++i;
}

    

    这个bug要出现,几率还是有限,条件还比较苛刻,要断行之后刚好多出一个空格才行。


    PS:改完之后觉得自己发现一个牛B bug,准备去论坛提交,突然想起这个引擎版本很落后,找了2.2.3的对比了一下,发现已经被修正了。。。。。

抱歉!评论已关闭.