在android常用编程中,handler在进行异步操作并处理返回结果时经常被使用。通常我们的代码会这样实现。
private final handler mleakyhandler = new handler() {
@override
public void handlemessage(message msg) {
// ...
}
}
}
但是,其实上面的代码可能导致内存泄露,当你使用android lint工具的话,会得到这样的警告
看到这里,可能还是有一些搞不清楚,代码中哪里可能导致内存泄露,又是如何导致内存泄露的呢?那我们就慢慢分析一下。
1.当一个android应用启动的时候,会自动创建一个供应用主线程使用的looper实例。looper的主要工作就是一个一个处理消息队列中的消息对象。在android中,所有android框架的事件(比如activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到looper要处理的消息队列中,由looper负责一条一条地进行处理。主线程中的looper生命周期和当前应用一样长。
2.当一个handler在主线程进行了初始化之后,我们发送一个target为这个handler的消息到looper处理的消息队列时,实际上已经发送的消息已经包含了一个handler实例的引用,只有这样looper在处理到这条消息时才可以调用handler#handlemessage(message)完成消息的正确处理。
3.在java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用。静态的内部类不会持有外部类的引用。关于这一内容可以查看细话java:”失效”的private修饰符
确实上面的代码示例有点难以察觉内存泄露,那么下面的例子就非常明显了
private final handler mleakyhandler = new handler() {
@override
public void handlemessage(message msg) {
// ...
}
}
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
// post a message and delay its execution for 10 minutes.
mleakyhandler.postdelayed(new runnable() {
@override
public void run() { /* ... */ }
}, 1000 * 60 * 10);
// go back to the previous activity.
finish();
}
}
分析一下上面的代码,当我们执行了activity的finish方法,被延迟的消息会在被处理之前存在于主线程消息队列中10分钟,而这个消息中又包含了handler的引用,而handler是一个匿名内部类的实例,其持有外面的sampleactivity的引用,所以这导致了sampleactivity无法回收,进行导致sampleactivity持有的很多资源都无法回收,这就是我们常说的内存泄露。
注意上面的new runnable这里也是匿名内部类实现的,同样也会持有sampleactivity的引用,也会阻止sampleactivity被回收。
要解决这种问题,思路就是不适用非静态内部类,继承handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的activity时,我们可以使用弱引用来处理。另外关于同样也需要将runnable设置为静态的成员属性。注意:一个静态的匿名内部类实例不会持有外部类的引用。 修改后不会导致内存泄露的代码如下:
/**
* instances of static inner classes do not hold an implicit
* reference to their outer class.
*/
private static class myhandler extends handler {
private final weakreference<sampleactivity> mactivity;
public myhandler(sampleactivity activity) {
mactivity = new weakreference<sampleactivity>(activity);
}
@override
public void handlemessage(message msg) {
sampleactivity activity = mactivity.get();
if (activity != null) {
// ...
}
}
}
private final myhandler mhandler = new myhandler(this);
/**
* instances of anonymous classes do not hold an implicit
* reference to their outer class when they are "static".
*/
private static final runnable srunnable = new runnable() {
@override
public void run() { /* ... */ }
};
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
// post a message and delay its execution for 10 minutes.
mhandler.postdelayed(srunnable, 1000 * 60 * 10);
// go back to the previous activity.
finish();
}
}
其实在android中很多的内存泄露都是由于在activity中使用了非静态内部类导致的,就像本文提到的一样,所以当我们使用时要非静态内部类时要格外注意,如果其实例的持有对象的生命周期大于其外部类对象,那么就有可能导致内存泄露。个人倾向于使用文章的静态类和弱引用的方法解决这种问题。
如对本文有疑问, 点击进行留言回复!!
before社区电量是什么意思 Before社区电量获得方法
RecycleView入门详解(教你全面掌握RecycleView用法)
动态权限请求框架RxPermissions(几行代码搞定权限)
URL路径@PathVariable出现点号“.“时值遭截断问题
网友评论