内存泄漏的检测、几种常见场景及解决方法

文章参考:http://blog.nimbledroid.com/2016/05/23/memory-leaks.htmlhtml

使用AndroidStudio检测内存泄漏:

http://wetest.qq.com/lab/view/99.htmljava

一.内存泄漏的缘由

  • 通常内存泄漏(traditional memory leak)的缘由是:由忘记释放分配的内存致使的。
  • 逻辑内存泄漏(logical memory leak)的缘由是:当应用再也不须要这个对象,当仍未释放该对象的全部引用。

二.几种常见的内存泄漏场景

实例代码一:
MainActivity:android

public class MainActivity extends AppCompatActivity {

     TextView tv_test;

    private Handler handler = new MyHandler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_test = (TextView) findViewById(R.id.tv_test);
        Message message = Message.obtain();
        message.what = 0;
        message.obj = "000";
        handler.sendMessageDelayed(message,60000);
        new Util(this,handler);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //handler.removeCallbacksAndMessages(null);
    }

    class MyHandler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            tv_test.setText((CharSequence) msg.obj);
        }
    }

}

Util代码:web

public class Util {
    private static Context context;
    private static Handler handler;
    Util(Context context,Handler handler){
        this.context = context;
        this.handler = handler;
    }
}

场景一:静态变量持有了Activity

因为Util中的context是static的,即其生命周期是和应用同样长的,在MainActivity方法中建立Util对象的时候,持有了MainActivity的引用(即this),致使该MainActivity没法被垃圾回收器回收,这样就形成了内存泄漏。 ide

场景二:匿名内部类或非静态内部类引发的内存泄漏

内部类会隐式地持有了外部类。在该例子中,MyHandler会持有MainActivity的引用,而Util中的静态变量又持有了MyHandler的引用,这样仍是静态变量间接地持有了Activity,致使Activity没法被垃圾回收器回收。
另外还有其它几种常见的实例
实例一:TimerTask svg

private void scheduleTimer() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                while(true);
            }
        }, Long.MAX_VALUE >> 1);
    }
实例二:
void spawnThread() {
        new Thread() {
            @Override public void run() {
                while(true);
            }
        }.start();
    }

该方法若在Activity中,因为TimerTask使用的是匿名内部类,会持有Activity的引用,所以会形成内存泄漏。其实这个是匿名内部类致使内存泄漏的一个实例。post

场景三:Handler

由于Handler是基于消息的。每次new出Handler,都会建立一个消息队列用于处理你使用handler发送的消息,形如:handler.send***Message。因为消息的发送老是会有先来后到的区别(若是只是这样都还好,毕竟再慢也不会过久,总归能够跑完,可能会延迟个几秒),可是若是你使用的是sendMessageDelayed(Message msg, long delayMillis)或postDelayed(Runnable r, long delayMillis)等发送延迟消息的时候,那基本内存泄漏发生的几率已经在90%以上了。由于handler会持有MainActivity的引用,会致使MainActivity没法销毁。
  动画

场景四:Static Views

private static View view;

void setStaticView() {
    view = findViewById(R.id.sv_button);
}

因为View持有其宿主Activity的引用,故和缘由一实际上是同样的。this

场景五:无限循环的属性动画

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tv_test,"rotation",0,360).setDuration(2000);
        objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
        objectAnimator.start();

若在OnDestory()中没有中止动画,则动画即时不可见,仍会一直执行下去,tv_test持有Activity的引用,故会致使Activity没法被回收spa

三.解决方案

  • 针对场景一:
    静态变量不要持有Activity,若无特殊需求,将其改成通常的变量
  • 针对缘由二:
    将非静态内部类/匿名内部类替换为静态内部类,这样就不会持有外部类的引用了
  • 针对场景三:
    在onDestory()中调用handler.removeCallbacksAndMessages(null);,就是移除全部的消息和回调,简单一句话就是清空了消息队列。注意,不要觉得你post的是个Runnable或者只是sendEmptyMessage。你能够看一下源码,在handler里面都是会把这些转成正统的Message,放入消息队列里面,因此清空队列就意味着这个Handler直接被打成原型了,固然也就能够回收了。
  • 针对场景四:
    尽可能不要用静态的view,若必须使用,需在onDestory的时候将view置为null
  • 针对场景五:
    在onDestory()中,中止动画 objectAnimator.cancel();

四.总结:

  1. 定义变量的时候,慎用static,例如若使用静态的集合,集合中的数据都不会被回收
  2. 及时回收须要回收的资源,如bitmap,cursor等
  3. 使用非静态内部类或匿名内部类的时候要注意

若有问题,欢迎加群交流:579853893