关于Android中Handler引发的内存泄露
2015-11-12 20:39
295 查看
在Android编程中,Handler在进行异步操作并处理返回结果时经常被使用。通常代码为:
public class SampleActivity extends Activity{
private final Handler mLeakyHandler = new Handler(){
@Override
public void handleMessage(Message msg){
//TODO.....
}
}
}
但其实上面的代码可能导致内存泄露,当使用Android lint工具时,会得到这样的警告:
This Handler class should be static or leaks might occur (com.example.multifragment.SampleActivity.l)
Issue: Ensures that Handler classes do not hold on to a reference to an outer class
Id: HandlerLeak
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected.
If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue.'
If the Handler is using the Looper or MessageQueue of hte main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to
your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.
到这,可能还是搞不清楚,代码中哪些地方可能导致内存泄露,又是如何导致内存泄露的呢? 下面来分析一下
1、当一个Android应用启动的时候,会自动创建一个供应用主线程使用的Looper实例。Looper的主要工作就是一个一个处理消息队列中的消息对象。在Android中,所有Android框架的事件(比如activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,有Looper负责一条一条的进行处理。主线程中的Looper生命周期和当前用用一样长。
2、当一个Handler在主线程进行了初始化后,我们发送一个target为这个Handler的消息到Looper处理的消息队列时,实际上已经发送的消息已经包含了一个Handler实例的引用,只有这样Looper在处理到这条消息时才可以调用Handler#handleMessage(Message)完成消息的正确处理。
3、在Java中,非静态的内部类和匿名内部类都会隐式的持有其外部类的引用。静态的内部类不会持有外部类的引用。
确定上面代码内存泄露不明显,则下面的例子就明显多了
public class SampleActivity extends Activity{
private final Handler mLeakyHandler = new Handler(){
@Override
public void handleMessage(Message msg){
//TODO...
}
}
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mLeakyHandler.postDelayed(new Runnable(){
@Override
public void run(){
//TODO
}
}, 1000*60*10);
finish();
}
}
分析代码,档我们执行Activity的finish()方法,被延迟的消息会在被处理之前存在于主线程消息队列中10分钟,而这个消息中又包含了Handler的引用,而Handler是一个匿名内部类的实例,其持有外面的SampleActivity的引用,所以这导致了SampleActivity无法回收,进而导致SampleActivity持有的很多资源都无法回收,这就是内存泄露。
注意new Runnable这里也是匿名内部类实现的,同样也会持有SampleActivity的引用,也会阻止SampleActivity被回收。
要解决这问题,思路就是不适用非静态内部类,继承Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实力的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理。另外关于同样也需要将Runnable设置为静态的成员属性。
注意:一个静态的匿名内部类实例不会持有外部类的引用。
修改后不会导致内存泄露的代码为:
public class SampleActivity extends Activity{
private static class MyHandler extends Handler{
private final WeakReference mActivity;
public MyHandler(SampleActivity activity){
mActivity = new WeakReference(activity);
@Override
public void handleMessage(Message msg){
SampleActivity activity = mActivity.get();
if(activity != null){
//TODO...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable sRunnable = new Runnable(){
@Override
public void run(){
//TODO
}
};
}
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mLeakyHandler.postDelayed(new Runnable(){
@Override
public void run(){
//TODO
}
}, 1000*60*10);
finish();
}
}
public class SampleActivity extends Activity{
private final Handler mLeakyHandler = new Handler(){
@Override
public void handleMessage(Message msg){
//TODO.....
}
}
}
但其实上面的代码可能导致内存泄露,当使用Android lint工具时,会得到这样的警告:
This Handler class should be static or leaks might occur (com.example.multifragment.SampleActivity.l)
Issue: Ensures that Handler classes do not hold on to a reference to an outer class
Id: HandlerLeak
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected.
If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue.'
If the Handler is using the Looper or MessageQueue of hte main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to
your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.
到这,可能还是搞不清楚,代码中哪些地方可能导致内存泄露,又是如何导致内存泄露的呢? 下面来分析一下
1、当一个Android应用启动的时候,会自动创建一个供应用主线程使用的Looper实例。Looper的主要工作就是一个一个处理消息队列中的消息对象。在Android中,所有Android框架的事件(比如activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,有Looper负责一条一条的进行处理。主线程中的Looper生命周期和当前用用一样长。
2、当一个Handler在主线程进行了初始化后,我们发送一个target为这个Handler的消息到Looper处理的消息队列时,实际上已经发送的消息已经包含了一个Handler实例的引用,只有这样Looper在处理到这条消息时才可以调用Handler#handleMessage(Message)完成消息的正确处理。
3、在Java中,非静态的内部类和匿名内部类都会隐式的持有其外部类的引用。静态的内部类不会持有外部类的引用。
确定上面代码内存泄露不明显,则下面的例子就明显多了
public class SampleActivity extends Activity{
private final Handler mLeakyHandler = new Handler(){
@Override
public void handleMessage(Message msg){
//TODO...
}
}
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mLeakyHandler.postDelayed(new Runnable(){
@Override
public void run(){
//TODO
}
}, 1000*60*10);
finish();
}
}
分析代码,档我们执行Activity的finish()方法,被延迟的消息会在被处理之前存在于主线程消息队列中10分钟,而这个消息中又包含了Handler的引用,而Handler是一个匿名内部类的实例,其持有外面的SampleActivity的引用,所以这导致了SampleActivity无法回收,进而导致SampleActivity持有的很多资源都无法回收,这就是内存泄露。
注意new Runnable这里也是匿名内部类实现的,同样也会持有SampleActivity的引用,也会阻止SampleActivity被回收。
要解决这问题,思路就是不适用非静态内部类,继承Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实力的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理。另外关于同样也需要将Runnable设置为静态的成员属性。
注意:一个静态的匿名内部类实例不会持有外部类的引用。
修改后不会导致内存泄露的代码为:
public class SampleActivity extends Activity{
private static class MyHandler extends Handler{
private final WeakReference mActivity;
public MyHandler(SampleActivity activity){
mActivity = new WeakReference(activity);
@Override
public void handleMessage(Message msg){
SampleActivity activity = mActivity.get();
if(activity != null){
//TODO...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable sRunnable = new Runnable(){
@Override
public void run(){
//TODO
}
};
}
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mLeakyHandler.postDelayed(new Runnable(){
@Override
public void run(){
//TODO
}
}, 1000*60*10);
finish();
}
}
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件