基礎(chǔ)部分
1: 圖片內(nèi)存大小小結(jié)
a: 圖片:是占用內(nèi)存的大戶,尤其是手機游戲圖片資源眾多。對圖片資源在內(nèi)存中占用量的計算成為J2ME游戲開發(fā)者的經(jīng)常性工作,CoCoMo來解釋一下如何計算圖片在內(nèi)存中的占用量:內(nèi)存占用量=寬*高*像素字節(jié)數(shù),其中像素字節(jié)數(shù)因機型而異。
例如一張64*64的圖片在7210上的內(nèi)存占用量=64*64*1.5=6144(字節(jié))=6K、在S60上的內(nèi)存占用量=64*64*2=8192 (字節(jié))=8K。像素字節(jié)數(shù)因機型而異,例如 7210是4096色機型,也就是說用12位來表示一個像素,所以乘上1.5,而S60是65536色的機型,用16位來表示一個像素,所以乘上2。
b:Xcode中使用instruments 查看圖片內(nèi)存的問題
如果使用的是模擬器那么默認是小屏幕的,所以最大圖片是1024 *1024 * 4 = 4 M (1024 是圖片的寬高, 4表示的是圖片的存儲類型為4字節(jié)的。也就是 RGBA8888)
如果你加載了圖片那么就是使用了4M的內(nèi)存。如果你需要渲染那么還需要4M的內(nèi)存。
加載一般都是 **load (NSString *)filename ,
渲染一般都是 Node addChild (Node)
2: 引用計數(shù)問題
引用計數(shù)增加的情況 : a: alloc 對象會使得對象引用數(shù) +1
b:調(diào)用retain (具體細說一些實例如下)
->比如你是cocos2d用戶的會看到 addchild 會使子節(jié)點的引用計數(shù)+1
->CCArray 的addObject 也會使元素的引用計數(shù)+1
總結(jié)一下就是: 凡是添加到結(jié)合中的元素或者子節(jié)點不需要再去retain ,只需要在建立的時候調(diào)用release
減少的情況 : 調(diào)用release 使引用計數(shù) -1(具體細說一些實例如下)
-> 集合調(diào)用remove/removeChildByTag 等等變形的
-> 創(chuàng)建的時候調(diào)用autorelease 。注意:如果你的對象是局部對象,而且創(chuàng)建的時候使用的是autorelease,
那么在離開方法的時候如果你沒有retain 那么這個對象將被dealloc(引用計數(shù)-1了)
官網(wǎng)的介紹:
•
You own any object you create by allocating memory for it or copying it.
Related methods:alloc,allocWithZone:,copy,copyWithZone:,mutableCopy,mutableCopyWithZone:
If you are not the creator of an object, but want to ensure it stays in memory for you to use, you can express an ownership interest in it.
Related method:retain
If you own an object, either by creating it or expressing an ownership interest, you are responsible for releasing it when you no longer need it.
Related methods:release,autorelease
Conversely, if you are not the creator of an object and have not expressed an ownership interest, you mustnotrelease it.
3 :參考文檔
一,IOS與圖片內(nèi)存
在IOS上,圖片會被自動縮放到2的N次方大小。比如一張1024*1025的圖片,占用的內(nèi)存與一張1024*2048的圖片是一致的。圖片占用內(nèi)存大小的計算的公式是;長*寬*4。這樣一張512*512占用的內(nèi)存就是 512*512*4 = 1M。其他尺寸以此類推。(ps:IOS上支持的最大尺寸為2048*2048)。
,cocos2d-x的圖片緩存
Cocos2d-x 在構(gòu)造一個精靈的時候會使用spriteWithFile或者spriteWithSpriteFrameName等無論用哪種方式,cocos2d-x都會將這張圖片加載到緩存中。如果是第一次加載這個圖片,那就會先將這張圖片加載到緩存,然后從緩存讀取。如果緩存中已經(jīng)存在,則直接從緩存中提取,免除了加載過程。
圖片的緩存主要由以下兩個類來處理:CCSpriteFrameCache, CCTextureCache
CCSpriteFrameCache加載的是一張拼接過的大圖,每一個小圖只是大圖中的一個區(qū)域,這些區(qū)域信息都在plist文件中保存。用的時候只需要根據(jù)小圖的名稱就可以加載到這個區(qū)域。
CCTextureCache 是普通的圖片緩存,我們所有直接加載的圖片都會默認放到這個緩存中,以提高調(diào)用效率。
因此,每次加載一張圖片,或者通過plist加載一張拼接圖時,都會將整張圖片加載到內(nèi)存中。如果不去釋放,那就會一直占用著。
三,渲染內(nèi)存。
不要以為,計算內(nèi)存時,只計算加載到緩存中的內(nèi)存就可以了。以一張1024*1024的圖片為例。
CCSprite *pSprite = CCSprite::spriteWithFile("a.png");
調(diào)用上邊這行代碼以后,可以在LEAKS工具中看到,增加了大約4M的內(nèi)存。然后接著調(diào)用
addChild(pSprite);
這時,內(nèi)存又增加了4M。也就是,一張圖片,如果需要渲染的話,那它所占用的內(nèi)存將要X2。
再看看通過plist加載的圖片,比如這張大圖尺寸為2048*2048。想要加載其中的一張32*32的小圖片
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("b.plist");
此時內(nèi)存增加16M (汗)
CCSprite *pSpriteFrame = CCSprite::spriteWithSpriteFrameName("b1.png");
b.png 大小為32*32,想著也就是增加一點點內(nèi)存,可實際情況是增加16M內(nèi)存。也就是只要渲染了其中的一部分,那么整張圖片都要一起被加載。
但是情況不是那么的糟糕,這些已經(jīng)渲染的圖片,如果再次加載的話,內(nèi)存是不會再繼續(xù)升高的,比如又增加了100個b.plist的另一個區(qū)域,圖片內(nèi)存還是共增加16+16 = 32M,而不會繼續(xù)上升。
四,緩存釋放
如果游戲有很多場景,在切換場景的時候可以把前一個場景的內(nèi)存全部釋放,防止總內(nèi)存過高.
CCTextureCache::sharedTextureCache()->removeAllTextures();釋放到目前為止所有加載的圖片
CCTextureCache::sharedTextureCache()->removeUnusedTextures();將引用計數(shù)為1的圖片釋放掉CCTextureCache::sharedTextureCache()->removeTexture();單獨釋放某個圖片
CCSpriteFrameCache與 CCTextureCache 釋放的方法差不多。
值得注意的是釋放的時機,一般在切換場景的時候釋放資源,如果從A場景切換到B場景,調(diào)用的函數(shù)順序為B::init()---->A::exit()---->B::onEnter()可如果使用了切換效果,比如CTransitionJumpZoom::transitionWithDuration這樣的函數(shù),則函數(shù)的調(diào)用順序變?yōu)锽::init()---->B::onEnter()---->A::exit()而且第二種方式會有一瞬間將兩個場景的資源疊加在一起,如果不采取過度,很可能會因為內(nèi)存吃緊而崩潰。
有時強制釋放全部資源時,會使某個正在執(zhí)行的動畫失去引用而彈出異常,可以調(diào)用CCActionManager::sharedManager()->removeAllActions();來解決。
五,內(nèi)存優(yōu)化
優(yōu)化的心得就是盡量去拼接圖片,使圖片邊長盡可能的保持2的N次方并且裝的很滿。但要注意,有邏輯關(guān)系的圖片盡量打包在一張大圖里,另外一點就是打包的時候要考慮到層的分布。因為為了渲染效率可能會用到CCSpriteBatchNode;同一個BatchNode里的圖片都是位于一個層級的,因此必須根據(jù)各個圖片的層級關(guān)系,打包到不同的plist里。有時內(nèi)存和效率不可以兼得,只能盡量平衡了。
六,其他
最后附一個各代IOS設(shè)備的內(nèi)存限制情況
設(shè)備 建議內(nèi)存 最大內(nèi)存
iPad2/iPhone4s/iphone4 170-180mb 512mb
iPad/iPod touch3,4/iphone3gs 40-80mb 256mb
iPod touch1,2/iPhone3g/iPhone1 25mb 128mb
上述建議內(nèi)存只是一些人自己測試的結(jié)果,可用的RAM不大于最大內(nèi)存的一半,如果程序超過最大內(nèi)存的一半,則可能會掛掉。
另外在LEAKS里查看模擬器中和真機總的內(nèi)存,會有較大出入。在模擬器中的結(jié)果與實際更接近一些。
七, 泄漏的情況
我所碰到的主要內(nèi)存泄露的方式:
1、最常見的就是,申請了引用,然后最后忘記釋放。具體么就是,使用OC的 alloc, retain, copy, new, C的malloc, realloc, C++的new等,然后沒有對應(yīng)的release, free, delete。這是單向泄露。
2、retain cycle,對于OC這種使用計數(shù)的方式,可能會存在retain cycle。兩個條件,一、就是A中retain了B,B又retain了A,各自給對方計數(shù)增加,這個環(huán)可以變?yōu)楹芏鄬?,就是A->B, B->C, C->D, .... Z->A,當(dāng)然假如中間層越多,檢測難度就越大。二、計數(shù)減少的操作是在dealloc中,而dealloc被調(diào)用則需要計數(shù)為0。 這兩個條件相加,導(dǎo)致計數(shù)鎖定,內(nèi)存泄露。
實戰(zhàn)演練
如何查找內(nèi)存泄漏 ?
一:對工具的使用來查找
1、首先使用分析編譯,Analyze build,查看歸類當(dāng)中的memory警告。
這個一般能發(fā)現(xiàn)局部變量中忘記release,或者被中途打斷release的。
2、然后就是直接使用Instruments中的leak監(jiān)測。
申請了內(nèi)存,然后已經(jīng)沒有指向這塊內(nèi)存的指針存在,可以認為是leak了。這個檢測一般是檢測這個狀態(tài)。
3、通過Instruments中allocation的mark heap。
進行不斷的重復(fù)操作,在每次場景結(jié)束后,標(biāo)記內(nèi)存。假如操作場景沒有泄露,內(nèi)存增加應(yīng)該是0。這個檢測是檢測標(biāo)記點之間有哪些對象增加。另外,需要多mark幾次才會準確,不要mark兩次看到有內(nèi)存增加就去找問題。
Instruments中都是可以看到其中存在什么對象,調(diào)用歷史,調(diào)用堆棧。這時候大致確定在那個類當(dāng)中的那個對象泄露了。
二 ,重載法。
雖然知道了哪個類泄露了,但是有時候并不知道具體是那邊的計數(shù)出現(xiàn)問題。我自己的方法是,假如是自己編寫的類,那就重載retain和release方法,然后加斷點。以此來監(jiān)測是什么地方retain了這個對象,卻沒有對應(yīng)釋放。
如何修改內(nèi)存泄漏呢 ?
1、缺啥補啥。缺release的,就補release,缺free的就加個free。
2、合理使用autorelease。對于返回給上層使用的;或者alloc對象到release中間有return等打斷操作的。建議使用autorelease。
3、合理使用assign。retain cycle,本質(zhì)就是多余的雙向retain。打個比方就是應(yīng)該確定哪個對象是根,哪一個是枝葉,枝葉不用去管理根,只需要知道根在那邊就可以了。所以把那些純粹是定位用的變量,屬性都改成assign方式,例如delegate。
PS:
假如對于Instruments的使用不是很清楚,可以看這個視頻
游戲中我遇到的一個非常難查的泄漏這里貢獻出來 :
對于cocos2d的用戶如果使用了CCMenu ,而且也重寫了CCScene中的onExsit 函數(shù)來檢測離開場景的時候的一些變化。但是忘了去調(diào)用super onExsit
這時候CCMenu自己注冊了一個事件delegate 就無法釋放導(dǎo)致CCMenu一直無法釋放。當(dāng)加載到了其他場景的時候事件總會不對。就是因為這個導(dǎo)致的
解決辦法自然是調(diào)用super onExsit 。因為在這里他釋放了delegate
更多信息請查看IT技術(shù)專欄