之前做過公司產(chǎn)品的內(nèi)存優(yōu)化乳蓄,不過時間有一段時間了策彤,可能記憶不全,歡迎大家添加補充店诗,有錯誤之處也方便指出音榜。
1、追查內(nèi)存的方法
第一步:使用lint
? ? ? ? ? ? ? ? ? lint會提醒你很多使用不得當?shù)牡胤讲聊遥饕獣性龠@么幾個地方
? ? ? ? ? ? ? ? (1)handler等長周期匿名內(nèi)部類的使用,具體原因下文表
? ? ? ? ? ? ? ? (2)數(shù)據(jù)結構的優(yōu)化瞬场,hashmap向稀疏數(shù)組的優(yōu)化
? ? ? ? ? ? ? ? ?(3)未使用的圖片資源
當然lint還會有很多很好的提醒,比如硬編碼贯被,layout層級問題等,這里就不說明了彤灶。
第二步:使用腳本每隔1s輸出對應包的PSS值
? ? ? ? ? ? ? ? PSS的定義是:Proportional Set Size 實際使用的物理內(nèi)存(比例分配共享庫占用的內(nèi)存),共享內(nèi)存則是:framework的代碼與資源在ram中占有的內(nèi)存幌陕。所以PSS值除了自身應用占有的內(nèi)存外還包括共享內(nèi)存中比例分配到單個應用身上的內(nèi)存,所以我覺得用這個值來定義是否進行了優(yōu)化是比較合適的搏熄。
第三步:使用核心然后再退出的功能,查看PSS值是否飆升或者在使用后長時間不降低下來
? ? ? ? ? ? ? ? ?如果遇到飆升雖然后續(xù)能降低下來宵凌,但是依然有可能OOM契邀,這樣我們也需要去追查是什么原因了,看如何能夠減少內(nèi)存的使用坯门。
? ? ? ? ? ? ? ? ?而內(nèi)存使用長時間不降低下來肯定是因為對象使用后還被引用著導致未被銷毀,當遇到這些情況后欠橘,我們要引入下一個工具了MAT
第四步:使用MAT分析內(nèi)存
? ? ? ? ? ? ? ? http://my.oschina.net/biezhi/blog/286223现恼,這塊內(nèi)容比較多,我也是參考博文進行分析的叉袍,具體的大家可以通過這篇文章來了解和發(fā)現(xiàn)內(nèi)存使用情況喳逛。
2、內(nèi)存消耗過大的原因
? ? ? ? ? ? ? 2.1 handler,resultreceiver等生命周期較長的匿名內(nèi)部類,匿名內(nèi)部類會持有外部類的引用殿怜,從而導致短期就算activity退出但其實其中的activity也會被引用從而導致相關的資源未被回收曙砂。
? ? ? ? ? ? ? ?2.2 數(shù)據(jù)結構的優(yōu)化,hashmap替換成sparsearray
? ? ? ? ? ? ? 2.3 圖片的優(yōu)化鸠澈,采用緩存,圖片縮略加載基于不同手機的分辨率獲取不同尺寸的圖片末荐,必要時可以進行縮放以及色彩優(yōu)化
android 色彩模式說明:
ALPHA_8:每個像素占用1byte內(nèi)存新锈。
ARGB_4444:每個像素占用2byte內(nèi)存
ARGB_8888:每個像素占用4byte內(nèi)存
RGB_565:每個像素占用2byte內(nèi)存
Android默認的色彩模式為ARGB_8888,這個色彩模式色彩最細膩块请,顯示質量最高拳缠。但同樣的,占用的內(nèi)存也最大窟坐。
? ? ? ? ? ? ? ? ?另外bitmap要記得recycle。
? ? ? ? ? ? ? ? ?2.4 對象生命周期臣疑,這里有如下幾點要注意
? ? ? ? ? ? ? ? (一)開發(fā)上盡量避免內(nèi)存中太多對象的存在徙菠,比如可以用數(shù)據(jù)庫來實現(xiàn)
? ? ? ? ? ? ? ? (二)切勿在循環(huán)調(diào)用的地方去產(chǎn)生對象,比如很多人不會注意的在getview里new onclicklistener(),這樣的方式拖動的次數(shù)越多那么就會產(chǎn)生越多的對象婿奔。當然還有在onDraw的地方newPaint等操作,這些都是需要避免的挤茄。
? ? ? ? ? ? ? ? ?(三)使用完對象要及時銷毀冰木,比如能局部變量的不要使用全局變量,功能用完成后要去掉對他的引用囚衔。
? ? ? ? ? ? ? ? ? (四)大內(nèi)存對象長生命周期雕沿,現(xiàn)有的內(nèi)存回收機制都是分為新生代,中生代审轮,老年代進行回收,而老年代的回收會對性能影響頗大篡诽,所以要盡量避免這樣的對象存在榴捡。
? ? ? ? ? ? ? ? ? (五)bitmap記得recycle,cursor要記得close
? ? ? ? ? ? ? ? ? (六)各種監(jiān)聽,廣播等吊圾,注冊后忘記取消等项乒。
? ? ? ? ? ? ? ? ? ?2.5 ?慎用service
? ? ? ? ? ? ? ? ? 理論上linux系統(tǒng)在內(nèi)存充足時不會在程序退出就Kill這個進程,而是會保存進程到lrucache中從而方便下次快速啟動檀何。而android的幾大組件的生命周期中,前臺可見的activity優(yōu)先級最高最難回收栓辜,然后前臺不可見的activity次之砚殿,service是第三,而空進程則是最容易被回收的似炎。
? ? ? ? ? ? ? ? ?如果啟動太多的service同時使用完畢后也不關系,那么就會造成內(nèi)存的占用贩毕,即使是切換其他的程序這個service占用的內(nèi)存也很難被回收仆嗦,當內(nèi)存過緊時就會造成系統(tǒng)的不穩(wěn)定。
? ? ? ? ? ? ? 推薦IntentService谆甜。
? ? ? ? ? ? ? ?2.6 代碼細節(jié)注意
? ? ? ? ? ? ? ? Enums的內(nèi)存消耗通常是static constants的2倍。你應該盡量避免在Android上使用enums规辱。
? ? ? ? ? ? ? ?在Java中的每一個類(包括匿名內(nèi)部類)都會使用大概500 bytes。
? ? ? ? ? ? ? ?每一個類的實例花銷是12-16 bytes改淑。
? ? ? ? ? ? ? ?往HashMap添加一個entry需要額一個額外占用的32 bytes的entry對象浴讯。
? ? ? ? ? ? ? ?在已知數(shù)量大小的情況下,初始化容器時訂好容器大小榆纽,比如new arraylist(3)那么數(shù)組就只會分配三個空間掠河。
? ? ? ? ? ? ? ?2.7 單例的使用
? ? ? ? ? ? ? ?一般來說我們是不推薦用單例的,因為大家為了偷懶寫代碼更方便一個勁的整成了單例唠摹,越多的單例使用導致內(nèi)存中長時間存活的對象過多,也會讓內(nèi)存占用過多煮甥。
? ? ? ? ? ? ? ? 2.8 無效的資源
? ? ? ? ? ? ? ?包括沒有用到的圖片等資源藕赞,以及可以縮小內(nèi)存的外部library,比如視頻sdk中我們使用了更小的一個斧蜕。
3、后續(xù)處理
? ? ? ? ? ? ? ?其實我覺得我的方法并不是一個十分良好的方法洒闸,我也只做了核心的功能的測試和優(yōu)化均芽,而其實代碼中還有很多的地方也可以優(yōu)化,但是并沒有時間和太好的方法進行優(yōu)化了深纲,所以將這些內(nèi)存使用問題分享給團隊,讓每個人自查自己代碼湃鹊,同時在以后開發(fā)中注意,那么就會讓工程代碼質量更高澄惊。
? ? ? ? ? ? ?如果大家有更好的追查方法富雅,也歡迎分享給我肛搬!
? ? ? ? ? ? ?轉載請注明原文件鏈接,以及作者:昱全yuquan
4蛤奢、Update
? ? ? ?4.1 非UI線程調(diào)用View的post(Runnable r)函數(shù)
? ? ? ?匿名內(nèi)部回調(diào)接口類持有view的引用陶贼,再回調(diào)時通過post(Runnable r)的方式,希望操作能夠在UI THREAD執(zhí)行操作拜秧,做一些更新UI的操作,這樣就可以不要寫handler相關的邏輯志衍,寫法簡單聊替。
? ? ? ? 但是這樣就會造成內(nèi)存泄露,當view被detach的時候惹悄,view的attachinfo為空泣港,這個從ViewRootImpl到父View,子View一層層傳下來的attachinfo爷速,當view被detach的時候,這個attachinfo會為空莉给。于是Post函數(shù)執(zhí)行如下操作:
? ? ? ? ViewRootImpl.getRunQueue().post(action);
? ? ? ? 在getRunQueue中會通過靜態(tài)的sRunQueues去綁定到當前的線程中,也就是這個異步線程中徐矩,只要這個線程存在這個引用關系就會一直存在而不被釋放叁幢。
? ? ? ? static final ThreadLocal sRunQueues = new ThreadLocal()。
? ? ? ? ? ? ? ? ? ?