Android內存管理之道
相信一步步走過(guò)來(lái)的Android從業(yè)者,每個(gè)人都會(huì )遇到OOM的情況。如何避免和防范OOM的出現,對于每一個(gè)程序員來(lái)說(shuō)確實(shí)是一門(mén)必不可少的能力。今天我們就談?wù)勗贏(yíng)ndroid平臺下內存的管理之道,開(kāi)始今天的主題之前,先再次回顧兩個(gè)概念。
本文引用地址:http://dyxdggzs.com/article/201609/304819.htm內存泄漏:對象在內存heap堆中中分配的空間,當不再使用或沒(méi)有引用指向的情況下,仍不能被GC正?;厥盏那闆r。多數出現在不合理的編碼情況下,比如在A(yíng)ctivity中注冊了一個(gè)廣播接收器,但是在頁(yè)面關(guān)閉的時(shí)候進(jìn)行unRegister,就會(huì )出現內存溢出的現象。通常情況下,大量的內存泄漏會(huì )造成OOM。
OOM:即OutOfMemoery,顧名思義就是指內存溢出了。內存溢出是指APP向系統申請超過(guò)最大閥值的內存請求,系統不會(huì )再分配多余的空間,就會(huì )造成OOM error。在我們Android平臺下,多數情況是出現在圖片不當處理加載的時(shí)候。
內存管理之道嘛,無(wú)非就是先理解并找出內存泄漏的原因,再基于這些反式去合理的編碼,去防范進(jìn)而避免內存開(kāi)銷(xiāo)過(guò)大的情形。學(xué)習如何合理的管理內存,最好先了解內存分配的機制和原理。只有深層次的理解了內部的原理,才能真正避免OOM的發(fā)生。但是本文就不介紹Jvm/Davilk內存分配的機制了,如有興趣,請查看歷史消息,以前做過(guò)題為《JVM運行時(shí)數據區域分析》的分享。
Android APP的所能申請的最大內存大小是多少,有人說(shuō)是16MB,有人又說(shuō)是24MB。這種事情,還是親自用自己的手機測試下比較靠譜。測試方式也比較簡(jiǎn)單,Java中有個(gè)Runtime類(lèi),主要用作APP與運行環(huán)境交互,APP并不會(huì )為我們創(chuàng )建Runtime的實(shí)例,但是Java為我們提供了單例獲取的方式Runtime.getRuntime()。通過(guò)maxMemory()方法獲取系統可為APP分配的最大內存,totalMemory()獲取APP當前所分配的內存heap空間大小。我手上有兩部手機,一部Oppo find7,運行Color OS,實(shí)測最大內存分配為192MB;一部天語(yǔ)v9,運行小米系統,實(shí)測最大內存分配為100MB。這下看出點(diǎn)眉目了吧,由于A(yíng)ndroid是開(kāi)源系統,不同的手機廠(chǎng)商其實(shí)是擁有修改這部分權限能力的,所以就造成了不同品牌和不同系統的手機,對于A(yíng)PP的內存支持也是不一樣的,和IOS的恒久100MB是不同的。一般來(lái)說(shuō),手機內存的配置越高,廠(chǎng)商也會(huì )調大手機支持的內存最大閥值,尤其是現在旗艦機滿(mǎn)天發(fā)布的情況下。但是開(kāi)發(fā)者為了考慮開(kāi)發(fā)出的APP的內存兼容性,無(wú)法保證APP運行在何種手機上,只能從編碼角度來(lái)優(yōu)化內存了。
下面我們逐條來(lái)分析Android內存優(yōu)化的關(guān)鍵點(diǎn)。
1、萬(wàn)惡的static
static是個(gè)好東西,聲明賦值調用就是那么的簡(jiǎn)單方便,但是伴隨而來(lái)的還有性能問(wèn)題。由于static聲明變量的生命周期其實(shí)是和APP的生命周期一樣的,有點(diǎn)類(lèi)似與Application。如果大量的使用的話(huà),就會(huì )占據內存空間不釋放,積少成多也會(huì )造成內存的不斷開(kāi)銷(xiāo),直至掛掉。static的合理使用一般用來(lái)修飾基本數據類(lèi)型或者輕量級對象,盡量避免修復集合或者大對象,常用作修飾全局配置項、工具類(lèi)方法、內部類(lèi)。
2、無(wú)關(guān)引用
很多情況下,我們需求用到傳遞引用,但是我們無(wú)法確保引用傳遞出去后能否及時(shí)的回收。比如比較有代表性的Context泄漏,很多情況下當Activity 結束掉后,由于仍被其他的對象指向導致一直遲遲不能回收,這就造成了內存泄漏。這時(shí)可以考慮第三條建議。
3、善用SoftReference/WeakReference/LruCache
Java、Android中有沒(méi)有這樣一種機制呢,當內存吃緊或者GC掃過(guò)的情況下,就能及時(shí)把一些內存占用給釋放掉,從而分配給需要分配的地方。答案是肯定的,java為我們提供了兩個(gè)解決方案。如果對內存的開(kāi)銷(xiāo)比較關(guān)注的APP,可以考慮使用WeakReference,當GC回收掃過(guò)這塊內存區域時(shí)就會(huì )回收;如果不是那么關(guān)注的話(huà),可以使用SoftReference,它會(huì )在內存申請不足的情況下自動(dòng)釋放,同樣也能解決OOM問(wèn)題。同時(shí)Android自3.0以后也推出了LruCache類(lèi),使用LRU算法就釋放內存,一樣的能解決OOM,如果兼容3.0一下的版本,請導入v4包。關(guān)于第二條的無(wú)關(guān)引用的問(wèn)題,我們傳參可以考慮使用WeakReference包裝一下。
4、謹慎handler
在處理異步操作的時(shí)候,handler + thread是個(gè)不錯的選擇。但是相信在使用handler的時(shí)候,大家都會(huì )遇到警告的情形,這個(gè)就是lint為開(kāi)發(fā)者的提醒。handler運行于UI線(xiàn)程,不斷處理來(lái)自MessageQueue的消息,如果handler還有消息需要處理但是Activity頁(yè)面已經(jīng)結束的情況下,Activity的引用其實(shí)并不會(huì )被回收,這就造成了內存泄漏。解決方案,一是在A(yíng)ctivity的onDestroy方法中調用
handler.removeCallbacksAndMessages(null);取消所有的消息的處理,包括待處理的消息;二是聲明handler的內部類(lèi)為static。
5、Bitmap終極殺手
Bitmap的不當處理極可能造成OOM,絕大多數情況都是因這個(gè)原因出現的。Bitamp位圖是Android中當之無(wú)愧的胖小子,所以在操作的時(shí)候當然是十分的小心了。由于Dalivk并不會(huì )主動(dòng)的去回收,需要開(kāi)發(fā)者在Bitmap不被使用的時(shí)候recycle掉。使用的過(guò)程中,及時(shí)釋放是非常重要的。同時(shí)如果需求允許,也可以去BItmap進(jìn)行一定的縮放,通過(guò)BitmapFactory.Options的inSampleSize屬性進(jìn)行控制。如果僅僅只想獲得Bitmap的屬性,其實(shí)并不需要根據BItmap的像素去分配內存,只需在解析讀取Bmp的時(shí)候使用BitmapFactory.Options的inJustDecodeBounds屬性。最后建議大家在加載網(wǎng)絡(luò )圖片的時(shí)候,使用軟引用或者弱引用并進(jìn)行本地緩存,推薦使用android-universal-imageloader或者xUtils,牛人出品,必屬精品。前幾天在講《自定義控件(三) 繼承控件》的時(shí)候,也整理一個(gè),大家可以去Github下載看看。
6、Cursor及時(shí)關(guān)閉
在查詢(xún)SQLite數據庫時(shí),會(huì )返回一個(gè)Cursor,當查詢(xún)完畢后,及時(shí)關(guān)閉,這樣就可以把查詢(xún)的結果集及時(shí)給回收掉。
7、頁(yè)面背景和圖片加載
在布局和代碼中設置背景和圖片的時(shí)候,如果是純色,盡量使用color;如果是規則圖形,盡量使用shape畫(huà)圖;如果稍微復雜點(diǎn),可以使用9patch圖;如果不能使用9patch的情況下,針對幾種主流分辨率的機型進(jìn)行切圖。
8、ListView和GridView的item緩存
對于移動(dòng)設備,尤其硬件參差不齊的android生態(tài),頁(yè)面的繪制其實(shí)是很耗時(shí)的,findViewById也是蠻慢的。所以不重用View,在有列表的時(shí)候就尤為顯著(zhù)了,經(jīng)常會(huì )出現滑動(dòng)很卡的現象。具體參照歷史文章《說(shuō)說(shuō)ViewHolder的另一種寫(xiě)法》
評論