在android studio中分析內存泄漏的工具有以下:MemoryMonitor 以及Allocation Tracker,還有Dump Java Heap,手動GC工具;as位置如下:
鼠標懸浮在上面就可以知道各個圖標的名字。
首先是MemoryMonitor:
1是連接設備,2是運行的modle,三就是實時顯示內存變化區。然後是Dump Java Heap:
在1處選擇Package Tree View。然後在2區按照包名可以找到Activity和其他類,右邊是當前類的實例,下邊是和這個類有關的引用。當創建了幾個Activity實例,Activity被銷燬之後,如果手動GC,該實例的個數沒有減少,那麼說明可能已經發生了內存泄漏。最後是Allocation Tracker:
可以檢測在一段時間內內存的分配情況,下面的餅圖就很直觀。除了上面的工具還有一個神器:LeakCanary可以檢測出代碼中的內存泄漏;具體使用文檔裏有介紹。
下面就用一個小例子說明一下這些工具的使用。
首先放上代碼
主Activity:
package com.github.lzyzsd.memorybugs;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static TextView sTextView;
private Button mStartBButton;
private Button mStartAllocationButton;
Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sTextView = (TextView) findViewById(R.id.tv_text);
sTextView.setText("Hello World!");
mStartBButton = (Button) findViewById(R.id.btn_start_b);
mStartBButton.setOnClickListener(this);
mStartAllocationButton = (Button) findViewById(R.id.btn_allocation);
mStartAllocationButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_start_b:
startB();
break;
case R.id.btn_allocation:
startAllocationLargeNumbersOfObjects();
break;
}
}
private void startB() {
finish();
startActivity(new Intent(this, ActivityB.class));
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
System.out.println("post delayed may leak");
}
}, 5000);
Toast.makeText(this, "請注意查看通知欄LeakMemory", Toast.LENGTH_SHORT).show();
}
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());
}
}
}
運行程序後,界面如下:
現在看起來沒有問題,但當點擊STARTACTIVITYB按鈕之後,就會轉入一個空的activity,大概過3-5秒通知欄就會彈出一個消息,點擊消息就可以進入LeakCanary,然後我們就可以看到:
每項展開後就可以從上到下看到:靜態變量sTextView引用了mContext導致MainActivity不能及時回收而造成內存泄漏。那解決方法就是把代碼中修飾sTextView的static去掉。其實現在的android studio到達2.2後已經十分的智能。在sTextView它就提示了這裏會導致內存泄漏:
所以有時候也要看看它的建議。
然後我們再多次點擊第二個按鈕,在Memory Monitor裏就可以看到由於突然創建大量對象不斷的垃圾回收,所以會看打內存抖動的現象:
此時如果打開start allocation tracking:
按照圖中的步驟(第二步是點擊最外層),可以看到在短時間就創建了很多StringBuilder和Rect對象。所以我們可以把循環裏Rect對象的創建和輸出語句裏的字符拼接移到onCreate()方法裏。如下:
- 成員變量
private String mWidth;
- onCreate()
Rect rect = new Rect(0, 0, 100, 100);
mWidth = "-------: " + rect.width();
- 點擊方法中
private void startAllocationLargeNumbersOfObjects() {
Toast.makeText(this, "請注意查看MemoryMonitor 以及AllocationTracker", Toast.LENGTH_SHORT).show();
for (int i = 0; i < 10000; i++) {
System.out.println(mWidth);
}
}
這樣之後,再多次點擊按鈕沒有出現內存抖動了。//昨天之後,經同學提醒,System.out.println()輸出語句在線程中還是會影響性能,所以還是把它改成log日誌打印了。接下來我們把焦點聚集到Handler上面,老師在這裏提示說可能會發生內存泄漏,然而剛剛的操作並沒有產生這個問題,那我們試試把延遲的5000ms改成15000ms,如下:
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
System.out.println("post delayed may leak");
}
}, 15000);
Toast.makeText(this, "請注意查看通知欄LeakMemory", Toast.LENGTH_SHORT).show();
運行程序之後,點擊按鈕跳轉ActivityB,過幾秒之後,通知欄就出現了消息,點進去如下圖:
一層一層分析下來原來是Runnable持有了外部類Activity實例的引用,那我們就來把內部類實現對Activity的弱引用,修改代碼如下:
public static class MyRunnable implements Runnable{
WeakReference<MainActivity> mMainActivityWeakReference;
MyRunnable(MainActivity mainActivity){
mMainActivityWeakReference = new WeakReference<MainActivity>(mainActivity);
}
@Override
public void run() {
System.out.println("post delayed may leak");
}
}
其他地方做小修改之後,運行程序,哎呀,好像通知欄還是有提示,不過這次的錯誤好像不一樣:
是handler的問題,突然間我就想起來之前的老師就給我們說過handler會造成內存泄漏,果然知識還是要經常複習才能不會忘記。然而這裏是Handler裏面的一個匿名內部類Callback造成的,所以只需要讓對Activity實現弱引用。
static class MyHandlerCallback implements Handler.Callback{
WeakReference<MainActivity> mMainActivityWeakReference;
MyHandlerCallback(MainActivity mainActivity){
mMainActivityWeakReference = new WeakReference<>(mainActivity);
}
@Override
public boolean handleMessage(Message msg) {
return false;
}
}
再一次運行就沒有錯誤了,最後再看看自定義控件類,在onDraw()方法中會有提示,意思是說,這個方法會頻繁被調用所以不要在這個方法中創建對象,我們就把兩個創建對象的語句移到構造函數前,沒有了黃色的提示強迫症得到極大的滿足,自此我找的錯誤就這些了,還望指教。