使用工具分析內存泄漏

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/w_jingjing0428/article/details/52655655

在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()方法中會有提示,意思是說,這個方法會頻繁被調用所以不要在這個方法中創建對象,我們就把兩個創建對象的語句移到構造函數前,沒有了黃色的提示強迫症得到極大的滿足,自此我找的錯誤就這些了,還望指教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章