对于一个初级程序员来说,内存泄露这种东西压根想都没想过,以前也总听一些大牛再说,可能是现在手机的内存都比较大,所以还没什么深刻的体会,但是作为一个负责的程序员,现在还是有必要了解和预防一下了。
本节说的是Handler引发的内存泄漏,这种场景也是很多的,比如需要去网上获取一张图片,然后显示在ImageView上面,然后还没拿到的时候就退出了Activity了,这个时候系统就不会去回收Activity了,直接导致内存泄漏。
public class TestActivity extends Activity{
ImageView mImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mImageView=(ImageView) findViewById(R.id.end);
//模拟网络请求60s
handler.sendEmptyMessageDelayed(0, 60000);
//立即关闭Activity(模拟退出,此刻网络请求还在跑)
finish();
}
Handler handler=new Handler(){
public void handleMessage(android.os.Message msg) {
mImageView.setImageResource(R.drawable.ic_launcher);
Toast.makeText(TestActivity.this, "dddd", 0).show();
};
};
}
IDE会报一个黄色的错误。
This Handler class should be static or leaks might occur
因为:
在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
如果外部类是Activity,则会引起Activity泄露 。
当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。
Handler 的生命周期与Activity 不一致
当Android应用启动的时候,会先创建一个UI主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。
当在主线程中初始化Handler时,该Handler和Looper的消息队列关联(没有关联会报错的)。发送到消息队列的Message会引用发送该消息的Handler对象,这样系统可以调用 Handler#handleMessage(Message) 来分发处理该消息。
上面代码可以发现,ImageView持有一个Activity的引用,Toast持有一个,而Handler跟Activity关联又持有一个引用,因此这样很容易就内存泄漏了,
内存泄露的危害
只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,如果这个Activity干的活比较多的话(也就是操作的东西比较多),那么不经意间就jj了。
既然让我们声明为静态的,那我们就改改代码:
static Handler handler=new Handler(){
public void handleMessage(android.os.Message msg) {
mImageView.setImageResource(R.drawable.ic_launcher);
Toast.makeText(TestActivity.this, "dddd", 0).show();
};
};
顿时有尴尬了:
在静态类中不能引用非静态的变量,
但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):
什么是WeakReference?
WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。
好了,我们再改改代码:
public class TestActivity extends Activity{
ImageView mImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mImageView=(ImageView) findViewById(R.id.end);
handler.sendEmptyMessageDelayed(0, 60000);
finish();
}
final MyHandler handler=new MyHandler(this);
static class MyHandler extends Handler{
private final WeakReference<TestActivity> mActivity;
public MyHandler(TestActivity activity) {
mActivity=new WeakReference<TestActivity>(activity);
}
public void handleMessage(android.os.Message msg) {
TestActivity activity=mActivity.get();
if(activity!=null){
activity.mImageView.setImageResource(R.drawable.ic_launcher);
Toast.makeText(activity, "dddd", 0).show();
}
};
};
}
这样总算完美了?????
当Activity finish后 handler对象还是在Message中排队。 还是会处理消息,还是会触发回调。
那到底怎样才算完美呢?
看看Handler的api中我们发现有这么几个方法:
所以我们在onDestory方法的时候,移除所有的Handler回调跟Message。
@Override
protected void onDestroy() {
if(handler!=null){
handler.removeCallbacksAndMessages(null);
}
super.onDestroy();
}
到此才勉强算是完美了,所以如果想要当一个好的程序员要考虑的东西还不是那么简单的,就一个看似简单的Handler