android的內(nèi)存優(yōu)化一般從以下幾個方面考慮:
- 內(nèi)存泄漏
- 內(nèi)存抖動
- Bitmap
- 代碼質(zhì)量優(yōu)化
內(nèi)存泄漏
內(nèi)存泄漏的本質(zhì):不合理的引用導致引用者對象的生命周期>被引用者對象的生命周期剑逃。當回收被引用者對象時發(fā)現(xiàn)該對象還在被引用狀態(tài),無法被回收怨咪,就出現(xiàn)了內(nèi)存泄漏啤呼。
常見的內(nèi)存泄漏場景:
- 非靜態(tài)匿名內(nèi)部類
比如經(jīng)典的在一個Activity里new一個非static 的Handler對象省核、在Activity里new了一個非靜態(tài)的Thread或者Runnable。還有很多類似的若河,尤其是在Activity或者Fragment內(nèi)引用一個非靜態(tài)的匿名內(nèi)部類時平道,這個類都會持有外部類闯估,相當于持有了context灼舍,極易導致context無法被回收,然后就內(nèi)存泄漏睬愤。
解決方法:我們可以把這個內(nèi)部類設置為靜態(tài)的片仿;或者不要使用內(nèi)部類,在外面寫個類尤辱;如果涉及到Context的必須持有問題,用Application的Context厢岂,因為其生命周期較長且唯一光督,可以不用回收,這樣就不會泄漏塔粒。
- Static 關(guān)鍵字修飾的成員變量
我們知道结借,jvm層面static修飾的變量是放在方法區(qū)的,方法區(qū)是永久代卒茬,基本上不會發(fā)送GC船老,或者說其發(fā)送GC的條件非常苛刻圃酵,而且在java1.8后出現(xiàn)了元空間柳畔,回收更是遙遙無期,也就是說static修飾的變量的生命周期跟你的APP應用生命周期一樣郭赐,如果你的static修飾的成員變量是Context或者持有Context時薪韩,那就會導致這個變量回收不掉,會莫名其妙用了很多內(nèi)存,我們每次啟動一個Activity都會new一個Context對象俘陷,如此反復你的內(nèi)存很快就爆掉了罗捎。
解決方法:我們在寫程序時不要用static修飾類似占用大資源的對象(例如Context,View)拉盾。
- 單例模式
這個跟上面的static原理是一樣的桨菜,因為單例模式是static的,其生命周期很長捉偏,所以要特別小心其持有對象導致內(nèi)存泄漏雷激。例如:常見的觀察者模式+單例模式時,我們會在Activity里實現(xiàn)某個接口(觀察者接口)告私,然后把這個接口add到單例模式的ArrayList(被觀察者)屎暇,這個過程就相當于單例對象持有了一個Activity對象,如果在Activity destroy時沒有把這個Activity從ArrayList里remove掉驻粟,那就造成內(nèi)存泄漏了根悼。
解決辦法:單例模式里持有變量時要注意其生命周期的管理,有+也有-蜀撑,這樣才安全挤巡。另外在這種情況里,我們可以適當合理地使用WeakReference酷麦。
- 集合類
當一個集合使用完后沒有清空其持有的對象矿卑。例如:我們往ArrayList里add一個對象時,有的時候我們把是把一個對象add進去沃饶,但是ArrayList真正持有的是這個對象的引用母廷,所以即使我們把add的這個對象置null,但是還會依然持有對象的引用糊肤,也就等于還持有另外對象琴昆。
解決辦法:當集合不用時,要清空然后置null馆揉。例如:arrayList.clear();arrayList=null;
- 資源對象未關(guān)閉
例如:registerBroadcast最后沒有unregisterBroadcast业舍;數(shù)據(jù)庫操作的cursor沒有close;stream流未關(guān)閉升酣;Bitmap沒有recycle等舷暮;
解決辦法:關(guān)閉或者釋放相關(guān)的信息
- 其他情況
例如:在ListView里沒有復用好View而是創(chuàng)建了大量的View;Webview使用沒有關(guān)閉噩茄;
內(nèi)存泄漏分析下面、跟蹤、監(jiān)測的工具:
- Android Studio的Memory;
- MAT(Memory Analysis Tools);
- Heap Viewer;
- Allocation Tracker;
- LeakCanary;
至于如何利用這些工具進行內(nèi)存的分析巢墅,后續(xù)我會慢慢補上诸狭。
以上都是理論知識券膀,也是普遍的問題存在,而且相對來說比較容易理解驯遇,大部分開發(fā)人員也熟知芹彬,真正的提升需要在實踐中,所以有時間大家可以參考外面文章的同時自己多實踐叉庐。
內(nèi)存抖動
所謂的內(nèi)存抖動就是短時間里舒帮,內(nèi)存出現(xiàn)了反復的波動,其出現(xiàn)的原因主要是因為短時間內(nèi)陡叠,我們寫代碼時創(chuàng)建了大量的對象玩郊,頻繁地觸發(fā)了GC機制回收對象,如果回收的速度趕不上你創(chuàng)建的速度枉阵,極有可能就OOM了译红,而且創(chuàng)建了大量的對象再回收會導致磁盤空間占用比較分散,不利于整體分配內(nèi)存兴溜,會影響內(nèi)存利用率和使用效率等侦厚。避免內(nèi)存抖動注意以下幾種:
- 避免for循環(huán)、while循環(huán)里new了很多對象拙徽;
- 避免View的onDraw()方法里創(chuàng)建對象刨沦;
- 避免在引用api時創(chuàng)建了大量對象,比如java的字符串拼接api膘怕;
- 避免創(chuàng)建大量的Bitmap想诅,盡量用內(nèi)存池進行管理;
- 避免創(chuàng)建大量的View岛心, 盡可能復用来破;
- 其他的能夠復用的盡量復用對象,用對象池進行管理鹉梨;
其實內(nèi)存抖動還是比較容易定位的讳癌,這一塊的分析和修改也沒有太大的難度,就不做過多的分析了存皂。
Bitmap的使用
在很多的項目中,Bitmap占用的內(nèi)存達到了整個App占用內(nèi)存的50%逢艘,甚至更高旦袋。Bitmap的優(yōu)化一般從如下幾個方面考慮:
- 避免加載過大的圖片;
- 及時釋放不用的Bitmap它改;
android在早期版本會把Bitmap占用的內(nèi)存放在Native里疤孕,中間(大概2.2后)轉(zhuǎn)到java heap層了,后面(8.0)又轉(zhuǎn)到Native層了央拖。所以什么時候釋放祭阀,如何釋放Bitmap占用的內(nèi)存是我們要考慮的重點鹉戚,例如RecyclerView、ListView专控、ViewPager加載View抹凳,當滑動后View不可見時Bitmap的回收。 - Bimap緩沖池的使用伦腐,盡可能復用以前分配的內(nèi)存赢底;
可以參考GlideBitmapPool的設計。 - 適當?shù)腤eakRefrence使用柏蘑;
主要是利于GC回收內(nèi)存幸冻。 - 根據(jù)不同分辨率合理配置圖片;
理解不同分辨率和density下圖片所占用的內(nèi)存咳焚,進行圖片配置洽损。 - 采取合理的圖片壓縮技術(shù);
主要考慮的參數(shù)有inTargetDensity革半,inSampleSize碑定,inJustDecodeBounds,inPreferredConfig督惰,inBitmap等不傅,要理解這些參數(shù)的含義(比較簡單)。 - 利用緩存機制在業(yè)務層面根據(jù)不同的圖片使用頻繁度進行多緩存池+不同的緩存機制進行優(yōu)化
關(guān)于Bitmap推薦一篇文章https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=403263974&idx=1&sn=b0315addbc47f3c38e65d9c633a12cd6&scene=21#wechat_redirect
代碼質(zhì)量
就是寫代碼過程里要注意一些小細節(jié)赏胚。
- 盡量不用或者少用枚舉访娶,可以用IntDef/@StringDef + @interface來進行限定參數(shù),因為枚舉占內(nèi)存大且容易來帶類型不安全問題觉阅。
- 當界面不可見時崖疤,適當?shù)蒯尫艃?nèi)存,避免內(nèi)存占用過高同時也可避免進程被回收,因為不可見的進程內(nèi)存占用越高被回收的概率越高《Э鳎可以在Activity的onTrimMemory(int level)方法里做贷揽。
- 使用Android系統(tǒng)自帶的數(shù)據(jù)存儲結(jié)構(gòu)代碼Java自帶的,例如SparseArray取代HashMap赖钞。
- 使用HashMap、ArrayList這些存儲結(jié)構(gòu)時,要適當考慮容量般码,盡可能避免無端擴容帶來的性能浪費。
- 利用好Java的引用類型(強軟弱虛)乱顾,合理選擇引用類型板祝。
- 謹慎使用多進程。
- 謹慎使用largeHeap走净。
- 謹慎使用shareprefercnce券时。
- 用好ProGuard剔除不必要的代碼孤里。
- 選好基礎(chǔ)數(shù)據(jù)類型,能用byte的不用int橘洞,能用int的不用long捌袜,盡量不用包裝類型。
- 注意dex文件和.so文件數(shù)量問題震檩,加載過多的這類文件內(nèi)存也會增加很多琢蛤。
- 可以考慮利用好匿名共享內(nèi)存,但是要注意管理好抛虏,匿名共享內(nèi)存自身是管殺不管埋的博其。
- Parcelable序列化對象時不能太大,否則會爆掉導致crash迂猴。
- 盡量選用系統(tǒng)資源慕淡,如String,Color沸毁,Style等峰髓。
- Log信息輸出的選擇上,最后選用aop式息尺,在正式代碼里直接不把日志輸出代碼編譯進去携兵。
- 優(yōu)化線程的分配,過多的線程分配和線程切換浪費資源(內(nèi)存+CPU)搂誉。在App開發(fā)過程中徐紧,我們經(jīng)常會引入很多第三方SDK,在這些SDK里各自都可能存在多線程或者多線程池的情況炭懊,我們盡可能選擇可配置線程的SDK使用并级,最后統(tǒng)一用一個線程池進行管理線程。