? ? ? ? Android的內(nèi)存優(yōu)化是性能優(yōu)化中很重要的一部分袄膏,而避免OOM又是內(nèi)存優(yōu)化中比較核心的一點(diǎn)乱投。本文是個(gè)人工作中的總結(jié)和參考了一些其他人的博客,主要用來(lái)記錄資料復(fù)習(xí)萤衰。
? ? ? ? 1艰争、java坏瞄,android內(nèi)存分配與回收機(jī)制;
? ? ? ? 2甩卓、android內(nèi)存泄露常見(jiàn)原因與OOM鸠匀;
? ? ? ? 3、內(nèi)存分析工具M(jìn)AT 和 studio?Monitor
一逾柿、java , android內(nèi)存分配與回收機(jī)制:
? ? 1缀棍、靜態(tài)存儲(chǔ)、棧區(qū)机错、堆區(qū)
? ??????Java/Android 程序運(yùn)行時(shí)的內(nèi)存分配有三種策略爬范,分別是靜態(tài)的,棧式的和堆式的弱匪,對(duì)應(yīng)的三種存儲(chǔ)策略使用的內(nèi)存空間主要分別是靜態(tài)存儲(chǔ)區(qū)(方法區(qū))青瀑、堆區(qū)和棧區(qū):
靜態(tài)存儲(chǔ)區(qū)(方法區(qū))
內(nèi)存在程序編譯的時(shí)候就已經(jīng)分配好,這塊內(nèi)存在程序整個(gè)運(yùn)行期間都存在,它主要是用來(lái)存放靜態(tài)數(shù)據(jù)斥难、全局 static 數(shù)據(jù)和常量枝嘶;
棧區(qū)
在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)部局部變量的存儲(chǔ)單元都可以在棧上創(chuàng)建哑诊,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放群扶,棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高镀裤,但是分配的內(nèi)存容量有限竞阐;
堆區(qū)
亦稱為動(dòng)態(tài)內(nèi)存分配,Java/Android 程序在適當(dāng)?shù)臅r(shí)候使用 new 關(guān)鍵字申請(qǐng)所需要大小的對(duì)象內(nèi)存淹禾,然后通過(guò) GC 決定在不需要這塊對(duì)象內(nèi)存的時(shí)候回收它,但是由于我們的疏忽導(dǎo)致該對(duì)象在不需要繼續(xù)使用的之后茴扁,GC 仍然沒(méi)辦法回收該內(nèi)存區(qū)域铃岔,這就代表發(fā)生了內(nèi)存泄漏。
2峭火、堆區(qū)和棧區(qū)的區(qū)別:?
在函數(shù)中定義的一些基本類(lèi)型的變量和對(duì)象的引用變量(也就是局部變量的引用)都是在函數(shù)的棧內(nèi)存分配的毁习,當(dāng)在一段代碼塊中定義一個(gè)變量時(shí),Java 就在棧中為這個(gè)變量分配內(nèi)存空間卖丸,當(dāng)超過(guò)變量的作用域后纺且,Java 會(huì)自動(dòng)釋放掉為該變量分配的內(nèi)存空間,該內(nèi)存空間可以立刻被重新使用稍浆;堆內(nèi)存用于存放所有由 new 創(chuàng)建的對(duì)象(內(nèi)容包括該對(duì)象其中的所有成員變量)和數(shù)組载碌,在堆中分配的內(nèi)存是由 GC 來(lái)管理的,在堆中產(chǎn)生了一個(gè)對(duì)象或者數(shù)組后衅枫,還可以在棧中生成一個(gè)引用指向這個(gè)堆中對(duì)象的內(nèi)存區(qū)域嫁艇,以后就可以通過(guò)棧中這個(gè)引用變量來(lái)訪問(wèn)堆中的這個(gè)引用指向的對(duì)象或者數(shù)組。
3弦撩、?Dalvik到ART的進(jìn)化
????????Android在4.4之前一直使用的Dalvik虛擬機(jī)作為App的運(yùn)行VM的, 4.4中引入了ART作為開(kāi)發(fā)者備選, 5.0起正式將ART作為默認(rèn)VM了步咪。其中?Dalvik采用的是JIT技術(shù),在應(yīng)用程序啟動(dòng)時(shí)益楼,JIT通過(guò)進(jìn)行連續(xù)的性能分析來(lái)優(yōu)化程序代碼的執(zhí)行,猾漫,在程序運(yùn)行的過(guò)程中,,Dalvik在不斷的進(jìn)行將字節(jié)碼編譯成機(jī)器碼的工作感凤。而ART 取自 Android RunTime. Android用其取代Dalvik悯周,主要目的就是為了提升運(yùn)行性能。所以陪竿,ART相比Dalvik有幾個(gè)關(guān)鍵的提升:
引入AOT(ahead-of-time)預(yù)編譯技術(shù)
在安裝apk的過(guò)程中, ART會(huì)使用dex2oat程序所有的字節(jié)碼預(yù)編譯成了機(jī)器碼. 應(yīng)用程序運(yùn)行過(guò)程中無(wú)需進(jìn)行實(shí)時(shí)的編譯工作, 只需要進(jìn)行直接調(diào)用. 故而提高了應(yīng)用程序的運(yùn)行效率.
提高GC效率
由原來(lái)的兩次GC暫停減少為一次.
以較少的GC時(shí)間回收最近分配的, 短命的對(duì)象.
提升GC工程學(xué), 使并發(fā)GC更及時(shí).
壓縮GC, 以減少后臺(tái)內(nèi)存使用和內(nèi)存碎片.
4队橙、Dalvik分配內(nèi)存的過(guò)程
????????Dalvik 虛擬機(jī)實(shí)現(xiàn)了一個(gè) dvmAllocObject 函數(shù),每當(dāng) Dalvik 虛擬機(jī)需要為對(duì)象分配內(nèi)存時(shí),就會(huì)調(diào)用函數(shù) dvmAllocObject捐康,例如仇矾,當(dāng) Dalvik 虛擬機(jī)的解釋器遇到一個(gè) new 指令時(shí),它就會(huì)調(diào)用函數(shù) dvmAllocObject解总;
????????函數(shù) dvmAllocObject 調(diào)用函數(shù) dvmMalloc 從 Java 堆中分配一塊指定大小的內(nèi)存給新創(chuàng)建的對(duì)象使用贮匕,如果分配成功,那么接下來(lái)就先使用宏 DVM_OBJECT_INIT 來(lái)初始化新創(chuàng)建對(duì)象的成員變量 clazz花枫,使得新創(chuàng)建的對(duì)象可以與某個(gè)特定的類(lèi)關(guān)聯(lián)起來(lái)刻盐,接著再調(diào)用函數(shù) dvmTrackAllocation 記錄當(dāng)前的內(nèi)存分配信息,以便通知 DDMS劳翰。函數(shù) dvmMalloc 返回的只是一塊內(nèi)存地址敦锌,這是沒(méi)有類(lèi)型的,但是由于每一個(gè) Java 對(duì)象都是從 Object 類(lèi)繼承下來(lái)的佳簸,因此函數(shù) dvmAllocObject 可以將獲得的沒(méi)有類(lèi)型的內(nèi)存塊強(qiáng)制轉(zhuǎn)換為一個(gè) Object 對(duì)象乙墙;
????????dvmMalloc 函數(shù)接著調(diào)用到了另一個(gè)函數(shù) tryMalloc ,真正執(zhí)行內(nèi)存分配操作的就是這個(gè) tryMalloc 函數(shù)生均,dvmMalloc 函數(shù)操作如果分配內(nèi)存成功听想,則記錄當(dāng)前線程成功分配的內(nèi)存字節(jié)數(shù)和對(duì)象數(shù)等信息;否則的話马胧,就記錄當(dāng)前線程失敗分配的內(nèi)存字節(jié)數(shù)和對(duì)象等信息汉买,方便通過(guò) DDMS 等工具對(duì)內(nèi)存使用信息進(jìn)行統(tǒng)計(jì),同時(shí)會(huì)調(diào)用函數(shù) throwOOME 拋出一個(gè) OOM 異常佩脊;
5蛙粘、Dalvik進(jìn)行GC的過(guò)程
Google IO 2011 大會(huì)的圖就很好的展示了Android 4.4 版本之下的回收策略
????????圖中的每個(gè)圓節(jié)點(diǎn)代表對(duì)象的內(nèi)存資源,箭頭代表可達(dá)路徑威彰,當(dāng)一個(gè)圓節(jié)點(diǎn)和 GC Roots 存在可達(dá)的路徑時(shí)组题,表示當(dāng)前它指向的內(nèi)存資源正在被引用,虛擬機(jī)是無(wú)法對(duì)其進(jìn)行回收的(圖中的黃色節(jié)點(diǎn))抱冷;反過(guò)來(lái)崔列,如果當(dāng)前的圓節(jié)點(diǎn)和 GC Roots 不存在可達(dá)路徑,則意味著這塊對(duì)象的內(nèi)存資源不再被程序引用旺遮,系統(tǒng)虛擬機(jī)可以在 GC 的時(shí)候?qū)⑵鋬?nèi)存回收掉赵讯。具體點(diǎn)來(lái)說(shuō),Java/Android 的內(nèi)存垃圾回收機(jī)制是從程序的主要運(yùn)行對(duì)象(如靜態(tài)對(duì)象/寄存器/棧上指向的內(nèi)存對(duì)象等耿眉,對(duì)應(yīng)上面的 GC Roots)開(kāi)始檢查調(diào)用鏈边翼,當(dāng)遍歷一遍后得到上述這些無(wú)法回收的對(duì)象和他們所引用的對(duì)象鏈組成無(wú)法回收的對(duì)象集合,而剩余其他的孤立對(duì)象(集)就作為垃圾被 GC 回收鸣剪。GC 為了能夠正確釋放對(duì)象组底,必須監(jiān)控每一個(gè)對(duì)象的運(yùn)行狀態(tài)丈积,包括對(duì)象的申請(qǐng)、引用债鸡、被引用江滨、賦值等。監(jiān)視對(duì)象狀態(tài)是為了更加準(zhǔn)確地厌均、及時(shí)地釋放對(duì)象唬滑,而釋放對(duì)象的根本原則就是該對(duì)象不再被引用。
6棺弊、ART 的GC過(guò)程
????????ART 為新創(chuàng)建對(duì)象分配內(nèi)存的過(guò)程和 Dalvik VM 幾乎是一樣的晶密,區(qū)別僅僅在于垃圾收集的方式和策略不一樣。 ART 運(yùn)行時(shí)為從 DEX 字節(jié)碼翻譯得到的 Native 代碼提供的一個(gè)函數(shù)調(diào)用表中模她,有一個(gè) pAllocObject 接口是用來(lái)分配對(duì)象的稻艰,當(dāng) ART 運(yùn)行時(shí)以 Quick 模式運(yùn)行在 ARM 體系結(jié)構(gòu)時(shí),上述提到的 pAllocObject 接口由函數(shù) art_quick_alloc_object 來(lái)實(shí)現(xiàn)侈净,art_quick_alloc_object 是一段匯編代碼尊勿,最終經(jīng)過(guò)一系列的調(diào)用之后最終會(huì)調(diào)用 ART 運(yùn)行時(shí)內(nèi)部的 Heap 對(duì)象的成員函數(shù) AllocObject 在堆上分配對(duì)象(具體的過(guò)程:ART運(yùn)行時(shí)為新創(chuàng)建對(duì)象分配內(nèi)存的過(guò)程分析),其中要分配的大小保存在當(dāng)前 Class 對(duì)象的成員變量 object_size_ 中用狱。(具體參考博客:http://blog.csdn.net/self_study/article/details/61919483)
7运怖、ART相對(duì)Dalivk的優(yōu)勢(shì)
????????在 Android 4.4 版本以及之后就使用了 ART 運(yùn)行時(shí)拼弃,在安裝的時(shí)候就將應(yīng)用翻譯成機(jī)器碼執(zhí)行夏伊,效率比起以前的 Dalvik 虛擬機(jī)更高,但是缺點(diǎn)就是安裝之后的應(yīng)用體積變大和安裝的時(shí)間會(huì)變長(zhǎng)吻氧,不過(guò)相對(duì)于優(yōu)點(diǎn)來(lái)說(shuō)溺忧,這點(diǎn)缺點(diǎn)不算什么。ART 運(yùn)行時(shí)與 Dalvik 虛擬機(jī)一樣盯孙,都使用了 Mark-Sweep 算法進(jìn)行垃圾回收鲁森,因此它們的垃圾回收流程在總體上是一致的,但是 ART 運(yùn)行時(shí)對(duì)堆的劃分更加細(xì)致振惰,因而在此基礎(chǔ)上實(shí)現(xiàn)了更多樣的回收策略歌溉。不同的策略有不同的回收力度,力度越大的回收策略每次回收的內(nèi)存就越多骑晶,并且它們都有各自的使用情景痛垛,這樣就可以使得每次執(zhí)行 GC 時(shí),可以最大限度地減少應(yīng)用程序停頓桶蛔。
二匙头、內(nèi)存泄露以及OOM原因及解決辦法:
? ?1、 內(nèi)存泄露
????????內(nèi)存對(duì)象的泄漏仔雷,會(huì)導(dǎo)致一些不再使用的對(duì)象無(wú)法及時(shí)釋放蹂析,這樣一方面占用了寶貴的內(nèi)存空間舔示,很容易導(dǎo)致后續(xù)需要分配內(nèi)存的時(shí)候,空閑空間不足而出現(xiàn)OOM电抚。顯然惕稻,這還使得每級(jí)Generation的內(nèi)存區(qū)域可用空間變小,GC就會(huì)更容易被觸發(fā)喻频,容易出現(xiàn)內(nèi)存抖動(dòng)缩宜,從而引起性能問(wèn)題(如圖15所示)。
圖15
最新的LeakCanary開(kāi)源控件甥温,可以很好的幫助我們發(fā)現(xiàn)內(nèi)存泄露的情況锻煌,更多關(guān)于LeakCanary的介紹,請(qǐng)看這里(中文使用說(shuō)明)姻蚓。另外也可以使用傳統(tǒng)的MAT工具查找內(nèi)存泄露宋梧,請(qǐng)參考這里(便捷的中文資料)。
1)注意Activity的泄漏
通常來(lái)說(shuō)狰挡,Activity的泄漏是內(nèi)存泄漏里面最嚴(yán)重的問(wèn)題捂龄,它占用的內(nèi)存多,影響面廣加叁,我們需要特別注意以下兩種情況導(dǎo)致的Activity泄漏:
內(nèi)部類(lèi)引用導(dǎo)致Activity的泄漏
最典型的場(chǎng)景是Handler導(dǎo)致的Activity泄漏倦沧,如果Handler中有延遲的任務(wù)或者是等待執(zhí)行的任務(wù)隊(duì)列過(guò)長(zhǎng),都有可能因?yàn)镠andler繼續(xù)執(zhí)行而導(dǎo)致Activity發(fā)生泄漏它匕。此時(shí)的引用關(guān)系鏈?zhǔn)荓ooper -> MessageQueue -> Message -> Handler -> Activity展融。為了解決這個(gè)問(wèn)題,可以在UI退出之前豫柬,執(zhí)行remove Handler消息隊(duì)列中的消息與runnable對(duì)象告希。或者是使用Static + WeakReference的方式來(lái)達(dá)到斷開(kāi)Handler與Activity之間存在引用關(guān)系的目的烧给。
Activity Context被傳遞到其他實(shí)例中燕偶,這可能導(dǎo)致自身被引用而發(fā)生泄漏。
內(nèi)部類(lèi)引起的泄漏不僅僅會(huì)發(fā)生在Activity上础嫡,其他任何內(nèi)部類(lèi)出現(xiàn)的地方指么,都需要特別留意!我們可以考慮盡量使用static類(lèi)型的內(nèi)部類(lèi)榴鼎,同時(shí)使用WeakReference的機(jī)制來(lái)避免因?yàn)榛ハ嘁枚霈F(xiàn)的泄露伯诬。
2)考慮使用Application Context而不是Activity Context
對(duì)于大部分非必須使用Activity Context的情況(Dialog的Context就必須是Activity Context),我們都可以考慮使用Application Context而不是Activity的Context檬贰,這樣可以避免不經(jīng)意的Activity泄露姑廉。
3)注意臨時(shí)Bitmap對(duì)象的及時(shí)回收
雖然在大多數(shù)情況下,我們會(huì)對(duì)Bitmap增加緩存機(jī)制翁涤,但是在某些時(shí)候桥言,部分Bitmap是需要及時(shí)回收的萌踱。例如臨時(shí)創(chuàng)建的某個(gè)相對(duì)比較大的bitmap對(duì)象,在經(jīng)過(guò)變換得到新的bitmap對(duì)象之后号阿,應(yīng)該盡快回收原始的bitmap并鸵,這樣能夠更快釋放原始bitmap所占用的空間。
需要特別留意的是Bitmap類(lèi)里面提供的createBitmap()方法扔涧,如圖16所示:
圖16 ?createBitmap()方法
這個(gè)函數(shù)返回的bitmap有可能和source bitmap是同一個(gè)园担,在回收的時(shí)候,需要特別檢查source bitmap與return bitmap的引用是否相同枯夜,只有在不等的情況下弯汰,才能夠執(zhí)行source bitmap的recycle方法。
4)注意監(jiān)聽(tīng)器的注銷(xiāo)
在Android程序里面存在很多需要register與unregister的監(jiān)聽(tīng)器湖雹,我們需要確保在合適的時(shí)候及時(shí)unregister那些監(jiān)聽(tīng)器咏闪。自己手動(dòng)add的listener,需要記得及時(shí)remove這個(gè)listener摔吏。
5)注意緩存容器中的對(duì)象泄漏
有時(shí)候鸽嫂,我們?yōu)榱颂岣邔?duì)象的復(fù)用性把某些對(duì)象放到緩存容器中,可是如果這些對(duì)象沒(méi)有及時(shí)從容器中清除征讲,也是有可能導(dǎo)致內(nèi)存泄漏的据某。例如,針對(duì)2.3的系統(tǒng)诗箍,如果把drawable添加到緩存容器癣籽,因?yàn)閐rawable與View的強(qiáng)應(yīng)用,很容易導(dǎo)致activity發(fā)生泄漏扳还。而從4.0開(kāi)始才避,就不存在這個(gè)問(wèn)題橱夭。解決這個(gè)問(wèn)題氨距,需要對(duì)2.3系統(tǒng)上的緩存drawable做特殊封裝,處理引用解綁的問(wèn)題棘劣,避免泄漏的情況俏让。
6)注意WebView的泄漏
Android中的WebView存在很大的兼容性問(wèn)題,不僅僅是Android系統(tǒng)版本的不同對(duì)WebView產(chǎn)生很大的差異茬暇,另外不同的廠商出貨的ROM里面WebView也存在著很大的差異首昔。更嚴(yán)重的是標(biāo)準(zhǔn)的WebView存在內(nèi)存泄露的問(wèn)題,請(qǐng)看這里糙俗。所以通常根治這個(gè)問(wèn)題的辦法是為WebView開(kāi)啟另外一個(gè)進(jìn)程勒奇,通過(guò)AIDL與主進(jìn)程進(jìn)行通信,WebView所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時(shí)機(jī)進(jìn)行銷(xiāo)毀巧骚,從而達(dá)到內(nèi)存的完整釋放赊颠。
7)注意Cursor對(duì)象是否及時(shí)關(guān)閉
在程序中我們經(jīng)常會(huì)進(jìn)行查詢數(shù)據(jù)庫(kù)的操作格二,但時(shí)常會(huì)存在不小心使用Cursor之后沒(méi)有及時(shí)關(guān)閉的情況。這些Cursor的泄露竣蹦,反復(fù)多次出現(xiàn)的話會(huì)對(duì)內(nèi)存管理產(chǎn)生很大的負(fù)面影響顶猜,我們需要謹(jǐn)記對(duì)Cursor對(duì)象的及時(shí)關(guān)閉。
2痘括、避免OOM
減小對(duì)象的內(nèi)存占用
避免OOM的第一步就是要盡量減少新分配出來(lái)的對(duì)象占用內(nèi)存的大小长窄,盡量使用更加輕量的對(duì)象。
1)使用更加輕量的數(shù)據(jù)結(jié)構(gòu)
例如纲菌,我們可以考慮使用ArrayMap/SparseArray而不是HashMap等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu)挠日。圖8演示了HashMap的簡(jiǎn)要工作原理,相比起Android專(zhuān)門(mén)為移動(dòng)操作系統(tǒng)編寫(xiě)的ArrayMap容器翰舌,在大多數(shù)情況下肆资,都顯示效率低下,更占內(nèi)存灶芝。通常的HashMap的實(shí)現(xiàn)方式更加消耗內(nèi)存郑原,因?yàn)樗枰粋€(gè)額外的實(shí)例對(duì)象來(lái)記錄Mapping操作。另外夜涕,SparseArray更加高效犯犁,在于他們避免了對(duì)key與value的自動(dòng)裝箱(autoboxing),并且避免了裝箱后的解箱女器。
關(guān)于更多ArrayMap/SparseArray的討論酸役,請(qǐng)參考《Android性能優(yōu)化典范(三)》的前三個(gè)段落。
2)避免在Android里面使用Enum
Android官方培訓(xùn)課程提到過(guò)“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”驾胆,具體原理請(qǐng)參考《Android性能優(yōu)化典范(三)》涣澡,所以請(qǐng)避免在Android里面使用到枚舉。
3)減小Bitmap對(duì)象的內(nèi)存占用
Bitmap是一個(gè)極容易消耗內(nèi)存的大胖子丧诺,減小創(chuàng)建出來(lái)的Bitmap的內(nèi)存占用可謂是重中之重入桂,通常來(lái)說(shuō)有以下2個(gè)措施:
inSampleSize:縮放比例,在把圖片載入內(nèi)存之前驳阎,我們需要先計(jì)算出一個(gè)合適的縮放比例抗愁,避免不必要的大圖載入。
decode format:解碼格式呵晚,選擇ARGB_8888/RBG_565/ARGB_4444/ALPHA_8蜘腌,存在很大差異。
4)使用更小的圖片
在涉及給到資源圖片時(shí)饵隙,我們需要特別留意這張圖片是否存在可以壓縮的空間撮珠,是否可以使用更小的圖片。盡量使用更小的圖片不僅可以減少內(nèi)存的使用金矛,還能避免出現(xiàn)大量的InflationException芯急。假設(shè)有一張很大的圖片被XML文件直接引用倘潜,很有可能在初始化視圖時(shí)會(huì)因?yàn)閮?nèi)存不足而發(fā)生InflationException,這個(gè)問(wèn)題的根本原因其實(shí)是發(fā)生了OOM志于。
內(nèi)存對(duì)象的重復(fù)利用
大多數(shù)對(duì)象的復(fù)用涮因,最終實(shí)施的方案都是利用對(duì)象池技術(shù),要么是在編寫(xiě)代碼時(shí)顯式地在程序里創(chuàng)建對(duì)象池伺绽,然后處理好復(fù)用的實(shí)現(xiàn)邏輯养泡。要么就是利用系統(tǒng)框架既有的某些復(fù)用特性,減少對(duì)象的重復(fù)創(chuàng)建奈应,從而降低內(nèi)存的分配與回收(如圖9所示)澜掩。
圖9 ?對(duì)象池技術(shù)
在Android上面最常用的一個(gè)緩存算法是LRU(Least Recently Use),簡(jiǎn)要操作原理如圖10所示杖挣。
圖10 ?LRU簡(jiǎn)要操作原理
1)復(fù)用系統(tǒng)自帶的資源
Android系統(tǒng)本身內(nèi)置了很多的資源肩榕,比如字符串、顏色惩妇、圖片株汉、動(dòng)畫(huà)、樣式以及簡(jiǎn)單布局等歌殃,這些資源都可以在應(yīng)用程序中直接引用乔妈。這樣做不僅能減少應(yīng)用程序的自身負(fù)重,減小APK的大小氓皱,還可以在一定程度上減少內(nèi)存的開(kāi)銷(xiāo)路召,復(fù)用性更好。但是也有必要留意Android系統(tǒng)的版本差異性波材,對(duì)那些不同系統(tǒng)版本上表現(xiàn)存在很大差異股淡、不符合需求的情況,還是需要應(yīng)用程序自身內(nèi)置進(jìn)去廷区。
2)注意在ListView/GridView等出現(xiàn)大量重復(fù)子組件的視圖里對(duì)ConvertView的復(fù)用唯灵,如圖11所示。
圖11
3)Bitmap對(duì)象的復(fù)用
在ListView與GridView等顯示大量圖片的控件里躲因,需要使用LRU的機(jī)制來(lái)緩存處理好的Bitmap早敬,如圖12所示忌傻。
圖12
利用inBitmap的高級(jí)特性提高Android系統(tǒng)在Bitmap分配與釋放執(zhí)行效率(注:3.0以及4.4以后存在一些使用限制上的差異)。使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經(jīng)存在的內(nèi)存區(qū)域水孩,新解碼的Bitmap會(huì)嘗試去使用之前那張Bitmap在Heap中所占據(jù)的pixel data內(nèi)存區(qū)域镰矿,而不是去問(wèn)內(nèi)存重新申請(qǐng)一塊區(qū)域來(lái)存放Bitmap。利用這種特性俘种,即使是上千張的圖片秤标,也只會(huì)僅僅只需要占用屏幕所能夠顯示的圖片數(shù)量的內(nèi)存大小绝淡,如圖13所示。
圖13 ?利用inBitmap的高級(jí)特性提高Android在Bitmap分配與釋放執(zhí)行效率
使用inBitmap需要注意幾個(gè)限制條件:
在SDK 11 -> 18之間苍姜,重用的Bitmap大小必須是一致的牢酵。例如給inBitmap賦值的圖片大小為100-100,那么新申請(qǐng)的Bitmap必須也為100-100才能夠被重用衙猪。從SDK 19開(kāi)始馍乙,新申請(qǐng)的Bitmap大小必須小于或者等于已經(jīng)賦值過(guò)的Bitmap大小。
新申請(qǐng)的Bitmap與舊的Bitmap必須有相同的解碼格式垫释。例如大家都是8888的丝格,如果前面的Bitmap是8888,那么就不能支持4444與565格式的Bitmap了棵譬。我們可以創(chuàng)建一個(gè)包含多種典型可重用Bitmap的對(duì)象池显蝌,這樣后續(xù)的Bitmap創(chuàng)建都能夠找到合適的“模板”去進(jìn)行重用,如圖14所示订咸。
圖14
另外曼尊,在2.x的系統(tǒng)上,盡管Bitmap是分配在Native層脏嚷,但還是無(wú)法避免被計(jì)算到OOM的引用計(jì)數(shù)器里涩禀。這里提示一下,不少應(yīng)用會(huì)通過(guò)反射vBitmapFactory.Options里面的inNativeAlloc來(lái)達(dá)到擴(kuò)大使用內(nèi)存的目的然眼,但是如果大家都這么做艾船,對(duì)系統(tǒng)整體會(huì)造成一定的負(fù)面影響,建議謹(jǐn)慎采納高每。
4)避免在onDraw方法里面執(zhí)行對(duì)象的創(chuàng)建
類(lèi)似onDraw等頻繁調(diào)用的方法屿岂,一定需要注意避免在這里做創(chuàng)建對(duì)象的操作,因?yàn)樗麜?huì)迅速增加內(nèi)存的使用鲸匿,而且很容易引起頻繁的gc爷怀,甚至是內(nèi)存抖動(dòng)。
5)StringBuilder
在有些時(shí)候带欢,代碼中會(huì)需要使用到大量的字符串拼接的操作运授,這種時(shí)候有必要考慮使用StringBuilder來(lái)替代頻繁的“+”。
三乔煞、內(nèi)存分析工具M(jìn)AT 和 studio?Monitor
(參考博客:http://www.reibang.com/p/080473ae050b)