使用工具分析内存泄漏

版权声明:本文为博主原创文章,未经博主允许不得转载。 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()方法中会有提示,意思是说,这个方法会频繁被调用所以不要在这个方法中创建对象,我们就把两个创建对象的语句移到构造函数前,没有了黄色的提示强迫症得到极大的满足,自此我找的错误就这些了,还望指教。

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