Android性能优化(二)——内存泄漏

Android性能优化(二)——内存泄漏

前言

想要彻底搞懂内存泄漏(Memory Leak),就要从三个方面下手,什么是内存泄漏,内存泄漏有什么影响,如何解决内存泄漏三个方面入手。

  • 什么是内存泄漏及影响 ?

    每一个对象都是有生命周期的,当生命周期结束的时候会回收的,但是因为其他的持有这个对象的引用导致不能被回收,所以无法释放内存,长此以往的堆积在堆内存中会造成内存泄漏,因为程序分配的内存不足以支持程序运行所需要的内存,最终会导致程序内存溢出(OOM),程序Crash崩溃掉,这对程序开发者和用户来说都是致命的问题。

  • 怎么检测内存泄漏 ?

    内存检测工具有很多,这里我推荐两款工具,一个是MAT,一个是Leak Canary三方检测工具,这两个都很好使用,工具没有最好的,只有最适合自己的。这篇博客我使用的是Leak Canary,具体的使用方法这有链接Github-LeakCanary的使用方法

  • 如何解决内存泄漏 ?

    这里我写了一张图,供大家欣赏,后面会详细讲解其中的一些常见内存泄漏情况。

    这里写图片描述

  • 第一种常见的内存泄漏:非静态内部类创建静态实例导致的内存泄漏

    因为非静态内部类会持有外部类的引用,而使用该非静态内部类又创建了一个静态实例,因为静态实例的生命周期和应用的生命周期一样长,导致该实例一直持有Activity的引用,导致Activity的资源不能被正常回收利用,导致内存泄漏。从下面图片可以看出内存泄漏和当前类中的demo有关,便于进一步查看原因。

    解决方法:可以将类写为静态内部类或者将类设置成单例模式即可。

    public class StaticOOM1Activity extends AppCompatActivity {

        static Demo demo;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_static_oom1);
            RefWatcher refWatcher = App.getRefWatcher(this);
            refWatcher.watch(this);
            if (demo == null) {
                demo = new Demo();
            }
            finish();
        }

        class Demo {
        }
    }

非静态内部类创建静态实例导致的内存泄漏


还有一种情况如下:因为mContext是静态变量生命周期比较长,一般来说和进程一致,那么此时将mContext=this的代码就将静态变量持有了Activity的引用,导致Activity资源无法回收,内存泄漏,一般不会出现这种情况,因为太明显了。

public class StaticOOM2Activity extends AppCompatActivity {

    private static Context mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_static_oom1);
        RefWatcher refWatcher = App.getRefWatcher(this);
        refWatcher.watch(this);
        mContext = this;
        finish();
    }
}

这里写图片描述


  • 第二种常见的内存泄漏:单例模式引发的内存泄漏

    因为此时单例模式的形参是一个context,如果在Activity中传入this参数的话,那么就会出现内存泄漏,因为单例模式的生命周期和Application一致,当Activity销毁的时候因为单例模式的实例还持有Activity的引用,所以导致Activity无法回收,导致内存泄漏。

public class Person {

    private static Person person = null;
    Context mContext;
    private Person(Context context) {
        this.mContext = context;
    }

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

解决方法:使用使用context.getApplicationContext()/App.get()这里获取的是整个程序的上下文

public class Person {

    private static Person person = null;
    Context mContext;
    private Person(Context context) {
        this.mContext = context;
    }

    public static Person getInstance(Context context) {
        if (person == null) {
            synchronized (Person.class) {
                if (person == null) {
                    person = new Person(context.getApplicationContext());
//                    person = new Person(App.get());
                }
            }
        }
        return person;
    }
}

  • 第三种常见的内存泄漏:属性动画导致的内存泄漏

    因为在Activity中,动画会持有View的引用,而View又会持有Activity的引用,即使Activity关闭了,但是动画还是会继续运行的,不过我们看不见罢了,这样会造成内存泄漏。

public class AnimationOOMActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_static_oom1);
        RefWatcher refWatcher = App.getRefWatcher(this);
        refWatcher.watch(this);

        Button button= (Button) findViewById(R.id.tv);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
        AnimatorSet set = new AnimatorSet();
        ObjectAnimator alpha = ObjectAnimator.ofFloat(button, "alpha", 1.0f, 0.5f);
        alpha.setRepeatCount(1000);
        alpha.setDuration(500);
        set.playTogether(alpha);
        set.start();
    }
}

解决办法:在onDestory()方法中取消动画即可animation.cancel()。


  • 第四种常见的内存泄漏:Handle引起的内存泄漏。

    假如我们发送一个延迟一分钟的message的话,Handler的消息处理机制相信大家都已经很熟悉了,通过loop()方法不停的循环遍历消息队列中的消息,因为loop()方法是一个阻塞方法,而且是以队列的形式处理消息的,那么在这一分钟内MessageQueue就会持有message和handler的引用,而handler又是一个非静态内部类,它会持有外部类的引用,这样的话当我们的Activity销毁的时候,就会造成内存泄漏了。

public class HandleOOMActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_static_oom1);
        RefWatcher refWatcher = App.getRefWatcher(this);
        refWatcher.watch(this);

        mHandler.sendEmptyMessageDelayed(1, 60 * 1000);
        finish();
    }

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                Toast.makeText(HandleOOMActivity.this, "收到了信息", Toast.LENGTH_SHORT).show();
            }
        }
    };

解决方法:将Handler声明为静态内部类,这样的话就不会持有外部类的引用,就和Activity无关了,就不会造成内存泄漏了,虽然这时候不会造成内存泄漏了,但是我们知道还是有消息在MessageQueue中,Looper也在等待处理消息,所以我们要在Activity结束的时候处理掉队列中的消息。如果用到Context等外部类非static对象时候,需要使用和应用同生命周期的Context为好。

public class HandleOOMActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_static_oom1);
        RefWatcher refWatcher = App.getRefWatcher(this);
        refWatcher.watch(this);

        MyHandler myHandler = new MyHandler(this);
        myHandler.sendEmptyMessageDelayed(1,60*1000);
        finish();
    }

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                Toast.makeText(HandleOOMActivity.this, "收到了信息", Toast.LENGTH_SHORT).show();
            }
        }
    };

    private static final class MyHandler extends Handler {
        private WeakReference<HandleOOMActivity> mActivity;

        public MyHandler(HandleOOMActivity mainActivity) {
            //mActivity=mainActivity.getApplicationContext;
            mActivity = new WeakReference<>(mainActivity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            HandleOOMActivity mainActivity = mActivity.get();
            if (null != mActivity) {
                //相关处理
            }
        }
    }

    /**
     * 虽然我们结束了Activity的内存泄漏问题,但是经过Handler发送的延时消息还在MessageQueue中,
     * Looper也在等待处理消息,所以我们要在Activity销毁的时候处理掉队列中的消息。
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

结尾:如上面思维导图所写的那样,其实还有很多会导致内存泄漏的原因,比如I/O流的问题,Bitmap的资源释放,注册和反注册以及不要多次重复的创建对象等等,只要在开发中多多总结,多留心就会有所长进的,本博客也是性能优化的一个重要方面,程序猿们都加油吧…

猜你喜欢

转载自blog.csdn.net/oman001/article/details/78933642
今日推荐