Android性能优化(二)——内存泄漏
前言
想要彻底搞懂内存泄漏(Memory Leak),就要从三个方面下手,什么是内存泄漏,内存泄漏有什么影响,如何解决内存泄漏三个方面入手。
-
什么是内存泄漏及影响 ?
Android中每一个对象都是有生命周期的,Java虚拟机有回收机制,当生命周期结束的时候,正常情况下这个对象是应该被回收的,但是因为有的时候生命周期长的对象持有了需要回收的生命周期短的对象的引用,导致不能被回收,所以无法释放内存,长此以往的堆积在堆内存中会造成内存泄漏,因为程序分配的内存不足以支持程序运行所需要的内存,最终会导致程序内存溢出(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()方法是一个阻塞方法,而且是以队列的形式处理消息的,那么在这一分钟内Message会持有Handler的引用,而Handler又是一个非静态内部类,它会持有外部类Activity的引用,这样的话当我们的Activity销毁的时候,因为被Handler持有,不能被回收,所以就会造成内存泄漏了。
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无关了,就不会造成内存泄漏了。
- 虽然这时候不会造成Activity内存泄漏了,但是我们知道还是有消息在MessageQueue中,Looper也在等待处理消息,所以我们要在Activity结束的时候使用
removeCallbacksAndMessages
处理队列中的消息。 - 如果用到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的资源释放,注册和反注册以及不要多次重复的创建对象等等,只要在开发中多多总结,多留心就会有所长进的,本博客也是性能优化的一个重要方面,程序猿们都加油吧…