Android Profiler (二)应用之分析内存泄露

1,721 阅读2分钟

我们先创建一个内存泄露的例子

首先,创建一个简单Android Java工程,里面有MainActivity和MainActivity2,其中MainActivity2用来模拟Handler的内存和单例内存泄露例子

MainActivity 跳转到MainActivity2

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.bt_go).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getApplication(),MainActivity2.class);
                intent.putExtra("hhh","hhhh");
                startActivity(intent);
            }
        });
    }
}

MainActivity2模拟了handler和单例持有上下文的内存泄露

public class MainActivity2 extends AppCompatActivity {

    //定义Handler对象
    private Handler handler = new Handler() {
        @Override
        //当有消息发送出来的时候就执行Handler的这个方法
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Toast.makeText(MainActivity2.this, "数据返回了!", Toast.LENGTH_LONG).show();
            Log.i("MainActivity2", "handleMessage -->" + Thread.currentThread().getName());
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        //创建单例,引用活动上下文会发现内存泄露
        TestFactory factory = TestFactory.getInstance(MainActivity2.this);
        //handler内存泄露测试
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                processThread();
            }
        });
    }

    private void processThread() {
        //构建一个下载进度条
        Log.i("MainActivity2", "processThread()-->" + Thread.currentThread().getName());
        new Thread() {
            @Override
            public void run() {
                Log.i("MainActivity2", "run()-->" + Thread.currentThread().getName());
                //在新线程里执行长耗时方法
                longTimeMethod();
                //执行完毕后给handler发送一个空消息
                handler.sendEmptyMessage(0);
            }
        }.start();
    }

    //模拟下载文件的长耗时方法
    private void longTimeMethod() {
        try {
            Log.i("MainActivity2", "longTimeMethod-->" + Thread.currentThread().getName());
            Thread.sleep(10000); //10秒钟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }


}

单例类

public class TestFactory {
    private Context context;
    private static volatile TestFactory testFactory;

    public TestFactory(Context context) {
        this.context = context;
    }


    public static TestFactory getInstance(Context context) {
        if (testFactory == null) {
            synchronized (TestFactory.class) {
                if (testFactory == null) {
                    testFactory = new TestFactory(context);
                }
            }
        }
        return testFactory;
    }
}

启动项目,打开Profiler,绑定我们的进程,进入内存分析视图,具体使用方法可以参考上一章

内存泄露的自动分析需要使用第一种Captrue heap dump Captrue heap dump

每点击一次Record,都会记录当前App内存情况,生成一个Heap Dump记录,所以app操作之后点击一次Record,内存情况记录会跟上一次有所不同的。 GC按钮图标是一个垃圾桶:

打开内存监控就可以进行操作了 点击Mainctivity的跳转按钮,跳转到Mainctivity2 , 然后点击执行执行processThread函数的方法,这边可以自己实现,退出HMainctivity2,回到MainActivity。然后回到Android Profile,执行一次GC按钮(模拟GC回收不可达的引用),再点击Record按钮,就会得到一个Heap Dump "记录1"

图片.png

我们看到,Leaks 数量是2,说明两种情况都内存泄露了

图片.png 按照箭头的顺序点击便可以查看到泄露的对象 可以看出因为单例持有了活动上下文导致无法被销毁 第二个泄露是hander的这里就不展开分析了

图片.png

细心的网友可能会问,除了第一个选项,后面两个做什么的呢? 根据注释说明我们可以知道主要分析native和kottlin的内存使用情况,如果用这个查内存泄露需要自己观察数据,不是最优选择

是不是很简单,你学废了吗?