android內存優化總結
之前做過公司產品的內存優化,不過時間有一段時間了,可能記憶不全,歡迎大家添加補充,有錯誤之處也方便指出。
1、追查內存的方法
第一步:使用lint
lint會提醒你很多使用不得當的地方,主要會集中再這么幾個地方
(1)handler等長周期匿名內部類的使用,具體原因下文表
(2)數據結構的優化,hashmap向稀疏數組的優化
(3)未使用的圖片資源
當然lint還會有很多很好的提醒,比如硬編碼,layout層級問題等,這里就不說明了。
第二步:使用腳本每隔1s輸出對應包的PSS值
PSS的定義是:Proportional Set Size 實際使用的物理內存(比例分配共享庫占用的內存),共享內存則是:framework的代碼與資源在ram中占有的內存。所以PSS值除了自身應用占有的內存外還包括共享內存中比例分配到單個應用身上的內存,所以我覺得用這個值來定義是否進行了優化是比較合適的。
第三步:使用核心然后再退出的功能,查看PSS值是否飆升或者在使用后長時間不降低下來
如果遇到飆升雖然后續能降低下來,但是依然有可能OOM,這樣我們也需要去追查是什么原因了,看如何能夠減少內存的使用。
而內存使用長時間不降低下來肯定是因為對象使用后還被引用著導致未被銷毀,當遇到這些情況后,我們要引入下一個工具了MAT
第四步:使用MAT分析內存
http://my.oschina.net/biezhi/blog/286223,這塊內容比較多,我也是參考博文進行分析的,具體的大家可以通過這篇文章來了解和發現內存使用情況。
2、內存消耗過大的原因
2.1 handler,resultreceiver等生命周期較長的匿名內部類,匿名內部類會持有外部類的引用,從而導致短期就算activity退出但其實其中的activity也會被引用從而導致相關的資源未被回收。
2.2 數據結構的優化,hashmap替換成sparsearray
2.3 圖片的優化,采用緩存,圖片縮略加載基于不同手機的分辨率獲取不同尺寸的圖片,必要時可以進行縮放以及色彩優化
android 色彩模式說明:
ALPHA_8:每個像素占用1byte內存。
ARGB_4444:每個像素占用2byte內存
ARGB_8888:每個像素占用4byte內存
RGB_565:每個像素占用2byte內存
Android默認的色彩模式為ARGB_8888,這個色彩模式色彩最細膩,顯示質量最高。但同樣的,占用的內存也最大。
另外bitmap要記得recycle。
2.4 對象生命周期,這里有如下幾點要注意
(一)開發上盡量避免內存中太多對象的存在,比如可以用數據庫來實現
(二)切勿在循環調用的地方去產生對象,比如很多人不會注意的在getview里new onclicklistener(),這樣的方式拖動的次數越多那么就會產生越多的對象。當然還有在onDraw的地方newPaint等操作,這些都是需要避免的。
(三)使用完對象要及時銷毀,比如能局部變量的不要使用全局變量,功能用完成后要去掉對他的引用。
(四)大內存對象長生命周期,現有的內存回收機制都是分為新生代,中生代,老年代進行回收,而老年代的回收會對性能影響頗大,所以要盡量避免這樣的對象存在。
(五)bitmap記得recycle,cursor要記得close
(六)各種監聽,廣播等,注冊后忘記取消等。
2.5 慎用service
理論上linux系統在內存充足時不會在程序退出就Kill這個進程,而是會保存進程到lrucache中從而方便下次快速啟動。而android的幾大組件的生命周期中,前臺可見的activity優先級最高最難回收,然后前臺不可見的activity次之,service是第三,而空進程則是最容易被回收的。
如果啟動太多的service同時使用完畢后也不關系,那么就會造成內存的占用,即使是切換其他的程序這個service占用的內存也很難被回收,當內存過緊時就會造成系統的不穩定。
推薦IntentService。
2.6 代碼細節注意
Enums的內存消耗通常是static constants的2倍。你應該盡量避免在Android上使用enums。
在Java中的每一個類(包括匿名內部類)都會使用大概500 bytes。
每一個類的實例花銷是12-16 bytes。
往HashMap添加一個entry需要額一個額外占用的32 bytes的entry對象。
在已知數量大小的情況下,初始化容器時訂好容器大小,比如new arraylist(3)那么數組就只會分配三個空間。
2.7 單例的使用
一般來說我們是不推薦用單例的,因為大家為了偷懶寫代碼更方便一個勁的整成了單例,越多的單例使用導致內存中長時間存活的對象過多,也會讓內存占用過多。
2.8 無效的資源
包括沒有用到的圖片等資源,以及可以縮小內存的外部library,比如視頻sdk中我們使用了更小的一個。
3、后續處理
其實我覺得我的方法并不是一個十分良好的方法,我也只做了核心的功能的測試和優化,而其實代碼中還有很多的地方也可以優化,但是并沒有時間和太好的方法進行優化了,所以將這些內存使用問題分享給團隊,讓每個人自查自己代碼,同時在以后開發中注意,那么就會讓工程代碼質量更高。
如果大家有更好的追查方法,也歡迎分享給我!
轉載請注明原文件鏈接,以及作者:昱全yuquan
4、Update
4.1 非UI線程調用View的post(Runnable r)函數
匿名內部回調接口類持有view的引用,再回調時通過post(Runnable r)的方式,希望操作能夠在UI THREAD執行操作,做一些更新UI的操作,這樣就可以不要寫handler相關的邏輯,寫法簡單。
但是這樣就會造成內存泄露,當view被detach的時候,view的attachinfo為空,這個從ViewRootImpl到父View,子View一層層傳下來的attachinfo,當view被detach的時候,這個attachinfo會為空。于是Post函數執行如下操作:
ViewRootImpl.getRunQueue().post(action);
在getRunQueue中會通過靜態的sRunQueues去綁定到當前的線程中,也就是這個異步線程中,只要這個線程存在這個引用關系就會一直存在而不被釋放。
static final ThreadLocal sRunQueues = new ThreadLocal()。
來自:http://www.jianshu.com/p/d9ba8573a940