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

时间:2021-12-08 06:04:12

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

前言

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

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

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

  • 怎么检测内存泄漏 ?

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

  • 如何解决内存泄漏 ?

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

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

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

    因为非静态内部类会持有外部类的引用,而使用该非静态内部类又创建了一个静态实例,因为静态实例的生命周期和应用的生命周期一样长,导致该实例一直持有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 {
}
}

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


还有一种情况如下:因为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();
}
}

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


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

    因为此时单例模式的形参是一个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的资源释放,注册和反注册以及不要多次重复的创建对象等等,只要在开发中多多总结,多留心就会有所长进的,本博客也是性能优化的一个重要方面,程序猿们都加油吧…