如果你是 Android 開發(fā)者,一定聽過(guò)“內(nèi)存抖動(dòng)”這個(gè)詞,如果高頻地申請(qǐng)較大尺寸的內(nèi)存,則可能導(dǎo)致短時(shí)間內(nèi)頻繁觸發(fā) GC,造成內(nèi)存的頻繁申請(qǐng)和釋放,使用Profiler查看內(nèi)存使用時(shí),看起來(lái)就是一個(gè)抖動(dòng)的曲線。
學(xué)習(xí)自定義View的時(shí)候,估計(jì)會(huì)有老師傅和你說(shuō):
不要在 onDraw() 里創(chuàng)建對(duì)象, 因?yàn)檫@個(gè)函數(shù)調(diào)用特別頻繁,容易引起內(nèi)存抖動(dòng)。
你當(dāng)然不會(huì)在編碼的時(shí)候這么做,今天我想看看,到底能“抖”成什么樣子。。。
測(cè)試代碼
測(cè)試代碼如下,每次 onDraw()
都會(huì)創(chuàng)建一個(gè)20MB 的大對(duì)象,這里要注意:不建議使用 Bitmap 作為測(cè)試對(duì)象,因?yàn)樵诓煌?Android 版本 Bitmap 像素?cái)?shù)據(jù)存儲(chǔ)的位置不同,這會(huì)為測(cè)試添加新的變量。
override fun onDraw(canvas: Canvas?) {
if (flag) {
Trace.beginSection("MemoryThrashingView --> alloc")
val start = System.currentTimeMillis()
largeObject = LargeObject()
Log.d(TAG, "alloc onDraw: ${System.currentTimeMillis() - start}")
invalidate()
Trace.endSection()
}
}
//LargeObject
class LargeObject {
private int _20MB = 20 * 1024 * 1024;
private byte[] memory = new byte[_20MB];
}
測(cè)試設(shè)備
為了保證變量單一,測(cè)試統(tǒng)一使用模擬器,選擇了Android 4.4、Android 5.0、Android 8.1 三個(gè)版本,每個(gè)版本又分為256MB 和 512MB 兩個(gè)最大堆限制。
測(cè)試結(jié)果
Android 4.4 - 256MB heap
Android 4.4 - 512MB heap
Android 5.0 - 256MB heap
Android 5.0 - 512MB heap
Android 8.1 - 256MB heap
Android 8.1 - 512MB heap
我們可以看到,Kitkat(Dalvik) 在內(nèi)存抖動(dòng)的場(chǎng)景表現(xiàn)是災(zāi)難級(jí)的,每一幀都延時(shí),掉幀非常嚴(yán)重。ART 的表現(xiàn)要好很多,1秒內(nèi)會(huì)有1次到幾次的延時(shí),同時(shí),由于Lollipop的分代管理策略,在這個(gè)場(chǎng)景表現(xiàn)甚至優(yōu)于Android 8.1 的CC。
后記:
其實(shí)我也不清楚自己得出的結(jié)論有沒有那么可靠,畢竟為了方便控制變量,我統(tǒng)一使用了模擬器來(lái)測(cè)試,但我發(fā)現(xiàn)當(dāng)前電腦的負(fù)載情況也影響著模擬器的性能表現(xiàn),同一個(gè)模擬器性能差異巨大,后來(lái)我將電腦的內(nèi)存使用控制在60%左右,對(duì)比了256MB和512MB的性能表現(xiàn),其實(shí)增加了幾倍的測(cè)試工作量,希望能夠客觀一些。