使用工具分析内存泄漏

作者: Dddddw | 来源:发表于2016-10-06 13:45 被阅读0次

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

    相关文章

      网友评论

        本文标题:使用工具分析内存泄漏

        本文链接:https://www.haomeiwen.com/subject/qdmeyttx.html