相信很多人都这样用过Handler:
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
//更新UI
//...
super.handleMessage(msg);
}
};
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作
//...
handler.sendEmptyMessage(0);
}
}).start();
很常见的一段段码,对没错,但是确实发生了内存泄漏。假如当在处理耗时操作的时候,可能是某段网络请求,也有可能是很复杂的计算,这时用户等的不耐烦了退出当前界面,由于异步操作,此时虽然界面销毁了,但是任务还在继续,由于线程中,引用这当前Activity中的对象handler,而handler对象又隐式的应用了当前的Activity,因此虽然界面销毁了,但是Activity并不会被回收。当任务执行完后,调用了handler方法,方法中又调用了更新界面,就会出现空指针异常崩溃。这算是最好的情况了,更严重的是由于Activity无法被销毁,那么就是占着那个地方不做事,这就是所谓的站着茅坑不拉屎。那么这块资源便浪费了,当用户来回反复切换的时候,资源占用越来越大,最终就算不空指针异常 也会出现OOM,这是由于内存泄漏导致的。
内存泄露的危害
只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制。
解决方案
- 在关闭Activity的时候停掉你的后台线程任务。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
- 如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
- 我们换一种写法,将handler对象声明成静态内部类,静态类不持有外部类的对象,所以你的Activity可以随意被回收。
private TestHandler testHandler=new TestHandler();
static class TestHandler extends Handler{
@Override
public void handleMessage(Message msg) {
//更新界面操作
//...
super.handleMessage(msg);
}
}
很快你会发现编译无法通过。因为上面说过了。静态类是不持有外部类的对象的,因此如果你在里面写了更新界面的操作当然会编译无法通过。解决办法当然有,就在静态类中在增加一个外部类的弱引用。
static class MyHandler extends Handler{
private WeakReference<SearchContantActivity> mContext;
public MyHandler(SearchContantActivity activity){
mContext=new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
if(msg.what==0){
SearchContantActivity activity=mContext.get();
if(activity!=null){
activity.xxx
}
}
super.handleMessage(msg);
}
}
但是这种仍然会出现异常,当你的界面退出后,界面的控件都会被销毁了,此时异步任务执行完,触发了handler发送消息,这时handler仍然会受到,从弱引用中拿到的Activity还是存在的,这时候你在去更新界面,那么理所当然就发生了NullPointException.所以最好的办法就是界面在正常发送handler,界面销毁就不发送handler,彻底切断它与Activity的联系,那么就一劳永逸了。