Android應用性能優(yōu)化——內存優(yōu)化(內附一個內存泄露優(yōu)化實例)

當我們剛開始接觸Android時筹麸,可能關注的比較多的是如何實現(xiàn)某個功能枫夺,但學到一定程度的時候将宪,我們會發(fā)現(xiàn)無論一個應用多么炫酷,如果運行特別慢橡庞,或者說很耗內存较坛,這將會帶來很差的用戶體驗,所以說扒最,性能優(yōu)化變得尤為重要丑勤。

一. 垃圾回收機制


自動管理內存和回收機制,垃圾回收器負責回收程序中已經不使用吧趣,但是仍然被各種對象占用的內存确封,將程序員從繁重、危險的內存管理工中解放出來再菊。

缺點:可能會占用大量資源爪喘。

Android有垃圾回收機制,無需手動管理內存纠拔,Android系統(tǒng)會自動跟蹤所有對象秉剑,并釋放那些不再使用的對象。

二. Android中的垃圾回收機制


新生代

  • 大多數(shù)新建的對象都位于Eden區(qū)稠诲。
  • 當Eden區(qū)域被對象填滿時侦鹏,就會執(zhí)行Minor GC诡曙,并把所有存活下來的對象轉移到其中一個survivor區(qū)。
  • Survivor Space:S0略水、S1有兩個价卤,存放每次垃圾回收后存活的對象。
  • Minor GC同樣會檢查survivor區(qū)存活下來的對象渊涝,并把它們轉移到另一個survivor區(qū)慎璧,這樣在一段時間內總是有一個空的survivor區(qū)。

老年代

  • 存放長期存活的對象和經過多次Minor GC后跨释,依然存活下來的對象胸私。
  • 滿了進行Major GC。

永久代

  • 存放方法區(qū)鳖谈,方法區(qū)中有要加載的類信息岁疼、靜態(tài)變量、final類型的常量缆娃、屬性和方法信息捷绒。

三. 內存泄露


  • 應用程序分配了大量不能被回收的對象。
  • 系統(tǒng)可分配內存越來越少贯要。
  • 新對象的創(chuàng)建需要內存不夠暖侨。
  • GC之后再分配。
  • 60fps郭毕。

四. 內存抖動


因為在短時間內大量的對象被創(chuàng)建又馬上被釋放,瞬間產生大量的對象會嚴重占用新生代的內存區(qū)域函荣,當達到閾值显押,剩余空間不夠的時候,會觸發(fā)GC從而導致剛產生的對象又很快被回收傻挂,即使每次分配的對象占用了很少的內存乘碑,但是他們疊加在一起會增加Heap的壓力,從而觸發(fā)更多其他類型的GC金拒,這個操作又可能會影響到幀率兽肤,并使得用戶感知到性能問題。

五. 工具


Memory Monitor

藍色部分表示使用內存绪抛,灰色部分表示空閑內存资铡,峰值表示發(fā)生了一次垃圾回收。

特點:

  • 方便顯示內存使用和GC情況幢码。
  • 快速定位卡頓是否和GC有關笤休。
  • 快速定位Crash是否和內存占用過高有關。
  • 快速定位潛在的內存泄露問題症副。
  • 簡單易用店雅。
  • 不能準確定位問題政基。

Allocation Tracker

跟蹤對象內存分配的工具∧掷玻可以追蹤應用程序在運行時所有已分配的內存沮明,所有已創(chuàng)建的對象,對象的數(shù)量和他們所占用的內存大小以及這些對象是在哪些方法中創(chuàng)建的窍奋,用于檢測內存抖動現(xiàn)象荐健。

特點:

  • 定位代碼中分配的對象的類型、大小费变、時間摧扇、線程、堆棧等信息挚歧。
  • 定位內存抖動問題扛稽。
  • 配合Heap Viewer一起定位內存抖動問題。
  • 使用復雜滑负。

Heap Viewer

實時展示應用程序運行時所有已分配的對象的數(shù)量在张、大小以及類型信息。用于檢測內存泄露矮慕。

特點:

  • 內存快照信息帮匾。
  • 每次GC之后收集一次信息。
  • 查找內存泄露利器痴鳄。
  • 使用復雜瘟斜。

六. 實例


這里有一個存在內存泄露的例子,下載地址:https://github.com/lzyzsd/MemoryBugs

主要使用MemoryMonitor痪寻, AllocationTracker螺句,HeapDump以及LeakCanary等工具來查找潛在的內存問題,并嘗試解決橡类。

解決過程記錄如下:

運行該程序蛇尚,可以看到主界面如下圖所示:

主界面

有一個TextView,一個半圓顾画,兩個按鈕取劫。

這里先點擊第一個按鈕StartActivityB,這時會彈出一個Toast:請注意查看通知欄LeakMemory研侣,點開通知欄的通知谱邪,看到有提示MainActivity has leaked,意思就是MainActivity出現(xiàn)內存泄露庶诡,如下圖:

MainActivity has leaked 1

通過分析虾标,是由于static類型的sTextView引用了mContext導致了MainActivity發(fā)生了內存泄漏,看到這里很多人估計會一臉懵逼,難道手機會自帶檢測內存泄露的工具嗎璧函?其實不是傀蚌,看程序源代碼,不難發(fā)現(xiàn)在build.gradle中引入了一個叫LeakCanary的工具蘸吓,具體代碼如下:

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' // or 1.4-beta1
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1

并且在MyApplication中LeakCanary.install(this);

由于static類型的變量是不會被垃圾回收的善炫,所以導致了MainActivity的內存泄露,解決方案就是去掉static库继,修改代碼:

//    private static TextView sTextView;    
      private TextView mTextView;

接著看一下半圓的繪制是否存在問題箩艺,先看代碼:

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    RectF rect = new RectF(0, 0, 100, 100);
    Paint paint = new Paint();
    paint.setColor(Color.RED);
    paint.setStrokeWidth(4);
    canvas.drawArc(rect, 0, 180, true, paint);
}

果然有問題,由于onDraw()方法調用比較頻繁宪萄,所以一般盡量避免在onDraw()方法中創(chuàng)建對象艺谆,這里恰恰就在onDraw()方法中創(chuàng)建對象,所以這里的修改方案是把創(chuàng)建對象放到定義成員變量的位置拜英。代碼如下:

private RectF mRectF = new RectF(0, 0, 100, 100);
private Paint mPaint = new Paint();

這時的onDraw()方法如下:

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    mPaint.setColor(Color.RED);
    mPaint.setStrokeWidth(4);
    canvas.drawArc(mRect, 0, 180, true, mPaint);
}

OK静汤,再次運行程序,點擊按鈕StartActivityB居凶,沒有出現(xiàn)LeakCanary的提示虫给。

在Android Studio中打開Android Monitor -> Memory,不斷點擊按鈕StartAllocation侠碧,不斷的發(fā)生內存回收和分配抹估,會出現(xiàn)以下狀況,這就是我們上邊所說的內存抖動弄兜。

內存抖動

配合Allocation Tracking药蜻,在內存抖動開始時點擊Start Allocation Tracking按鈕,在抖動結束后再點擊一下替饿。會得到如下圖所示的.alloc文件:

Group by Method

選擇Group by Allocator语泽,然后點擊最外圈的綠色,然后雙擊右面的Activity盛垦,把Activity展開后會發(fā)現(xiàn)進行很多Rect和StringBuilder對象的創(chuàng)建湿弦。

Group by Allocator

問題就在這里瓤漏,看代碼:

private void startAllocationLargeNumbersOfObjects() {    
Toast.makeText(this, "請注意查看MemoryMonitor 以及AllocationTracker", Toast.LENGTH_SHORT).show();
    for (int i = 0; i < 10000; i++) {
        Rect rect = new Rect(0, 0, 100, 100);
        System.out.println("-------: " + rect.width());
    }
}

可以看到在for循環(huán)中一直創(chuàng)建對象及字符串的拼接腾夯。

修改方案是把Rect對象的創(chuàng)建放到成員變量中,在onCreate中進行初始化蔬充,為了避免在logcat輸出時產生大量的String對象蝶俱,修改方案是在onCreate中把String對象創(chuàng)建好,這樣就不會重復創(chuàng)建了饥漫,還要把里面的字符串提取出來榨呆,放到strings.xml中,有的要設置為static final類型的字符串資源庸队,修改代碼如下:

成員變量:

public static final String LINE_TAG = "-------: ";
private Rect mRect;
private String mLogString;

onCreate():

mRect = new Rect(0, 0, 100, 100);
mLogString = LINE_TAG + mRect.width();

startAllocationLargeNumbersOfObjects()

private void startAllocationLargeNumbersOfObjects() {
    Toast.makeText(this, R.string.memory_monitor, Toast.LENGTH_SHORT).show();
    for (int i = 0; i < 10000; i++) {
        System.out.println(mLogString);
    }
}

strings.xml:

<resources>
    <string name="app_name">MemoryBugs</string>
    <string name="memory_monitor">請注意查看MemoryMonitor 以及AllocationTracker</string>
    <string name="leakmemory">請注意查看通知欄LeakMemory</string>
    <string name="hello_world">Hello World!</string>
</resources>

以上解決了三個問題积蜻,那么怎么檢測是否還存在內存泄露呢闯割?還有一個工具叫Heap Viewer,這個工具可以實時展示應用程序運行時所有已分配的對象的數(shù)量竿拆、大小以及類型信息宙拉,可以檢測內存泄露。

在手機屏幕上點擊StartActivityB丙笋,在Android Studio中點擊Dump Java Heap谢澈,選擇Package Tree View,找到我們的程序御板,可以看到MainActivity還沒有被垃圾回收锥忿。

Heap Viewer.png

手動進行一下垃圾回收,再次點擊Dump Java Heap怠肋,可以看到如下效果:

GC之后_Heap Viewer.png

這時看到MainActivity已經被垃圾回收了敬鬓,不存在內存泄漏問題了。

參考:http://www.reibang.com/p/c53101db112e

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末灶似,一起剝皮案震驚了整個濱河市列林,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酪惭,老刑警劉巖希痴,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異春感,居然都是意外死亡砌创,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門鲫懒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嫩实,“玉大人,你說我怎么就攤上這事窥岩〖紫祝” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵颂翼,是天一觀的道長晃洒。 經常有香客問我,道長朦乏,這世上最難降的妖魔是什么球及? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮呻疹,結果婚禮上吃引,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好镊尺,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布朦佩。 她就那樣靜靜地躺著,像睡著了一般庐氮。 火紅的嫁衣襯著肌膚如雪吕粗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天旭愧,我揣著相機與錄音颅筋,去河邊找鬼。 笑死输枯,一個胖子當著我的面吹牛议泵,可吹牛的內容都是我干的。 我是一名探鬼主播桃熄,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼先口,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瞳收?” 一聲冷哼從身側響起碉京,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎螟深,沒想到半個月后谐宙,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡界弧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年凡蜻,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垢箕。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡划栓,死狀恐怖,靈堂內的尸體忽然破棺而出条获,到底是詐尸還是另有隱情忠荞,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布帅掘,位于F島的核電站委煤,受9級特大地震影響,放射性物質發(fā)生泄漏锄开。R本人自食惡果不足惜素标,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一称诗、第九天 我趴在偏房一處隱蔽的房頂上張望萍悴。 院中可真熱鬧,春花似錦、人聲如沸癣诱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撕予。三九已至鲫惶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間实抡,已是汗流浹背欠母。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吆寨,地道東北人赏淌。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像啄清,于是被迫代替她去往敵國和親六水。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容