今天測試發現了遊戲的一個問題,系統郵件,如果發了tab,在android上一開啟郵件內容就會crash。而且他們很確定是tab的問題。
憑我多個月的經驗(確實沒多年。。。)來看,從來沒聽說在android上會因為一個tab崩潰,而且如果有這個問題,肯定會有很多人遇到,估計早就吵翻天了,搜尋了一下,什麼可用資訊都沒有。
於是寫個測試工程測試了一下,分別在mac下和windows下,用文本編輯工具編輯了4個txt文檔,utf有bom和無bom,內容是“ tab abcd ”,發現都能正常顯示,也不會crash。如下:
unsigned long fSize = 0; unsigned char *data = CCFileUtils::sharedFileUtils()->getFileData("tab.txt", "rb", &fSize); CCLabelTTF *label = CCLabelTTF::create((const char*)data, "abc", 30); label->setPosition(ccp(visibleSize.width/2, visibleSize.height/2)); addChild(label);
然後開始怒跟代碼,發現工程中使用了一個自訂控制項,是為了實現一個富文字框的功能。所謂富文字框就是能顯示各種顏色,能顯示url,有的東西還能點的那種。但是這個東西的實現,其中有一步,是把一個utf8字串拆開,拆成單個字元,把每個字元的紋理做出來,然後來拼圖,拼成一個文字框。那麼tab肯定會被拆成單個字元,最後發現就是在產生這個tab的紋理的時候,crash了。堆棧如下:
順便科普一下labelTTF顯示的原理,大致就是,通過字型和字型大小,然後調用相應平台的api,產生一張圖(image),注意是一張圖啊,然後作為紋理,設定給一個精靈。從上面的堆棧來看,就是在產生這個image的時候crash了。<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+ICAgIMi7uvPV0rT6wuujrNTaQ0NUZXh0dXJlMkTPwtXStb3Jz8PmttHVu7XE1+6689K7sr2jrMjnz8KjujwvcD4KPHA+PC9wPgo8cHJlIGNsYXNzPQ=="brush:java;">bool CCTexture2D::initWithString(const char *text, const char *fontName, float fontSize, const CCSize& dimensions, CCTextAlignment hAlignment, CCVerticalTextAlignment vAlignment){ … … … … do { CCImage* pImage = new CCImage(); CC_BREAK_IF(NULL == pImage); bRet = pImage->initWithString(text, (int)dimensions.width, (int)dimensions.height, eAlign, fontName, (int)fontSize); CC_BREAK_IF(!bRet); bRet = initWithImage(pImage); CC_SAFE_RELEASE(pImage); } while (0); … … … … return bRet; } 這裡第一步是,new一個image出來,然後用文字去init這個image,然後再用這個image去init這個texture。我們接著看init這個image這裡,android下的代碼是這樣的:
bool CCImage::initWithString( const char * pText, int nWidth/* = 0*/, int nHeight/* = 0*/, ETextAlign eAlignMask/* = kAlignCenter*/, const char * pFontName/* = nil*/, int nSize/* = 0*/){ bool bRet = false; do { CC_BREAK_IF(! pText); BitmapDC &dc = sharedBitmapDC(); CC_BREAK_IF(! dc.getBitmapFromJava(pText, nWidth, nHeight, eAlignMask, pFontName, nSize)); m_pData = dc.m_pData; CC_BREAK_IF(! m_pData); m_nWidth = (short)dc.m_nWidth; m_nHeight = (short)dc.m_nHeight; m_bHasAlpha = true; m_bPreMulti = true; m_nBitsPerComponent = 8; bRet = true; } while (0); return bRet;} 其中重點是getBitmapFromJava這個函數,如下
bool getBitmapFromJava(const char *text, int nWidth, int nHeight, CCImage::ETextAlign eAlignMask, const char * pFontName, float fontSize) { return getBitmapFromJavaShadowStroke(text, nWidth, nHeight, eAlignMask, pFontName, fontSize ); } 從這裡就知道了,肯定是把文字,字型,字型大小這些東西,叫給java層,讓它們調用系統相關的東西,返回一個bitmap回來,然後就是下一步,把這個返回的image去初始化texture。但是不需要下一步了,因為到這就crash了,得到如下log:
從這個log看,大概意思就是java層在建立bitmap的時候,需要一個圖片的寬高,但是這個大小等於0了。
但是之前測試是可以顯示tab的,仔細想想,之前的測試有一個問題,就是之前的測試都是tab加其他字元的混合,這個地方因為富文字框的處理,是單個tab字元,那麼再用單個tab測試一下。果然就和上面一樣crash了。
再回到這個crash log本身,這個要產生的圖,就是寫了字的那個圖,那麼這個圖片的寬高應該取決於字元的內容和字元的大小,怎麼會是0呢。難道是因為tab在android上面會被自動忽略。馬上再寫個工程測試:
unsigned long fSize = 0; unsigned char *data = CCFileUtils::sharedFileUtils()->getFileData("tab.txt", "rb", &fSize); CCLabelTTF *label = CCLabelTTF::create((const char*)data, "abc", 30); label->setPosition(ccp(visibleSize.width/2, visibleSize.height/2)); CCLayerColor *col = CCLayerColor::create(ccc4(123, 255,0 , 255)); col->setContentSize(label->getContentSize()); col->ignoreAnchorPointForPosition(false); col->setAnchorPoint(ccp(0.5, 0.5)); col->setPosition(ccp(label->getPosition().x,label->getPosition().y)); addChild(col); addChild(label);
這個測試和之前那個的區別,主要在於在文字後面加了一個同大小的色塊,這樣我們就能看到紋理的實際範圍(早該如此,其實是開始2了沒想到。。。),果然發現不管如何加tab,tab都不會顯示,紋理的大小和不加tab的大小是一樣的。
如果在cocos2dx上是這樣,但是他最後會調用到android系統的java層去產生這個bitmap,說明這個不是cocos2dx的問題,那麼在android應用上,tab會不會被忽略呢,果斷開個工程再測試,如下:
tabTest Hello world! Settings
果然還是沒顯示tab,哈哈哈哈哈,收工~~
總結:android系統會忽略tab,在cocos2dx裡面用CCLabelTTF的時候,如果常值內容只有一個tab字元,會crash。