内存泄漏总结

内存泄漏主要为activity泄漏有几大情景:

1,内部类泄漏          内部类持有外部类,但外部类关闭时内部类依然被持有造成泄漏

2,静态常量泄漏       静态变量长期维持到大数据对象的引用,阻止垃圾回收

3,资源未关闭泄漏    资源性对象如Cursor、Stream、Socket,Bitmap

4,注册反注册泄漏    我们常常写很多的Listener,未反注册会导致观察者列表里维持着对象的引用,阻止垃圾回收。

5,图片太大

6,listview使用

最主要的最普遍的是activity泄漏  主要情景: (在 activity关闭时,检查静态变量是否需要赋值为null,检查内部类是否还存在)

 

内部类泄漏常见情景:

1,匿名内部类

匿名内部类和非静态内部类相同,会持有外部类对象,也就是activity,因此如果你在Activity里创建一个匿名内部类,则可能会发生内存泄漏,如果这个内部类在Activity销毁后还一直存在,就会造成activity泄漏。

解决方案:将匿名内部类,改为静态内部类。或者用静态变量名引用匿名内部类,并在activity结束时将静态内部类变量赋值null

2,非静态内部类的静态实例  

由于内部类默认持有外部类的引用,而静态实例属于类。所以,当外部类被销毁时,内部类仍然持有外部类的引用,致使外部类无法被GC回收。因此造成内存泄露

 

解决方案:将内部类声明为静态内部类,或者在创建静态内部类的时候使用静态变量名引用,并在activity关闭时将静态变量赋值为null

3,Handler临时性内存泄露

Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。

Runnable类属于非静态匿名类,同样会引用外部类。
为了解决遇到的问题,我们要明确一点:静态内部类不会持有对外部类的引用。所以,我们可以把handler类放在单独的类文件中,或者使用静态内部类便可以避免泄漏。
另外,如果想要在handler内部去调用所在的外部类Activity,那么可以在handler内部使用弱引用的方式指向所在Activity,这样统一不会导致内存泄漏。
对于匿名类Runnable,同样可以将其设置为静态类。因为静态的匿名类不会持有对外部类的引用。

解决方案:可以由上面的结论看出,产生泄漏的根源在于匿名类持有Activity的引用,因此可以自定义Handler和Runnable类并声明成静态的内部类,来解除和Activity的引用。或者在activity 结束时,将发送的Message移除

 

4.内部线程泄露

new Thread(){}.start();Java中的Thread有一个特点就是她们都是直接被GC Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用,导致GC永远都无法回收掉这些线程对象,除非线程被手动停止并置为null或者用户直接kill进程操作。看到这相信你应该也是心中有答案了吧 : 我在每一个MainActivity中都创建了一个线程,此线程会持有MainActivity的引用,即使退出Activity当前线程因为是直接被GC Root引用所以不会被回收掉,导致MainActivity也无法被GC回收。

解决方案:当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉

5.单例(主要原因还是因为一般情况下单例都是全局的,有时候会引用一些实际生命周期比较短的变量,导致其无法释放)

有的单例模式,在创建的时候需要传递上下文,这是把当前activity对象传入,创建了静态单例。从此静态单例持有activity对象,造成activity泄漏

解决方案: 传递activity的弱饮用或者软引用,或者传递上下文时传递生命周期为全程的上下文。比如application上下文,全程都存在的activity

6. Timer Tasks

  这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而导致Timer和TimerTask一直引用外部类Activity。根本原因就是内部类的使用造成持有外部类activity的引用

解决方案:在适当的时机进行Cancel。

 

静态常量常见情景:

1.静态Activities(static Activities)

activity有个静态activity成员变量,然后在生命周期中将自己赋值给这个静态常量,这样导致被持有的activity泄漏

解决方案:不使用静态activity,或给静态activity赋值时,考虑赋值的activity生命周期是不是全局的,或者在静态activity使用完后及时释放

2.静态View

在Activity里声明一个静态变量view,然后初始化。问题出在这里,View一旦被加载到界面中将会持有一个Context对象的引用,这个context对象是我们的Activity,声明一个静态变量引用这个View,也就引用了activity,所以当activity生命周期结束了,静态View没有清除掉,还持有activity的引用,因此内存泄漏了。

解决方案:不使用静态view,或在activity关闭时将静态view赋值为null

3,静态Drawable

当一个Drawable附加到一个 View上时,
View会将其作为一个callback设定到Drawable上。上述的代码片段,意味着Drawable拥有一个TextView的引用,

 

4,集合中对象没清理造成的内存泄漏

我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。

 

资源未关闭泄漏:

1.数据库的cursor没有关闭。

操作Sqlite数据库时,Cursor是数据库表中每一行的集合,Cursor提供了很多方法,可以很方便的读取数据库中的值,
可以根据索引,列名等获取数据库中的值,通过游标的方式可以调用moveToNext()移到下一行
当我们操作完数据库后,一定要记得调用Cursor对象的close()来关闭游标,释放资源。

2,未关闭InputStream/OutputStream。

3,Bitmap对象不在使用时调用recycle()释放内存

4,BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap

 

 

注册反注册泄漏情景:

1,注册没有反注册

通过Context调用getSystemService获取系统服务,这些服务运行在他们自己的进程执行一系列后台工作或者提供和硬件交互的接口,如果Context对象需要在一个Service内部事件发生时随时收到通知,则需要把自己作为一个监听器注册进去,这样服务就会持有一个Activity,如果开发者忘记了在Activity被销毁前注销这个监听器,这样就导致内存泄漏。

解决方案:在onDestroy方法里注销监听器。

 

 

图片处理情况:

1,使用三级缓存    使用LruCache

2,进行图片压缩

3,进行图片裁剪

4,Bitmap对象不在使用时调用recycle()释放内存

 

直接使用ImageView显示bitmap会占用较多资源,特别是图片较大的时候,可能导致崩溃。
使用BitmapFactory.Options设置inSampleSize, 这样做可以减少对系统资源的要求。
属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
BitmapFactory.Options bitmapFactoryOptions = new BitmapFactory.Options();
bitmapFactoryOptions.inJustDecodeBounds = true;
bitmapFactoryOptions.inSampleSize = 2;
// 这里一定要将其设置回false,因为之前我们将其设置成了true
// 设置inJustDecodeBounds为true后,decodeFile并不分配空间,即,BitmapFactory解码出来的Bitmap为Null,但可计算出原始图片的长度和宽度
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);

 

ListView 情况:

1,构造Adapter时,没有使用缓存的convertView

解决方案:使用convertView

 

2,  Adapter中引用了Activity如何避免内存泄漏

有时需要点击ListView条目里的某个按钮实现界面跳转,getView()方法inflate布局的时候需要上下文,而且点击按钮后的跳转逻辑也需要上下文。所以我们经常会把Activity传入到Adapter中,如果Adapter中有很多耗时操作,可能就会防止Activity finish的时候被回收。

 

解决方案:

inflate布局是可以使用getView()方法里的parent参数的,通过parent.getActivity()获得上下文。

 

 

 

在涉及到Context时先考虑ApplicationContext,当然它并不是万能的,对于有些地方则必须使用Activity的Context,对于Application,Service,Activity三者的Context的应用场景如下:
这里写图片描述
**其中:**NO1表示Application和Service可以启动一个Activity,不过需要创建一个新的task任务队列。而对于Dialog而言,只有在Activity中才能创建

 

内存监测工具

1.  Android Studio自带工具Memory Monitor    ,Device Monitor 

2.DDMS dump + MAT分析

3.LeakCanary

 

posted @ 2017-04-10 23:06  一点点征服  阅读(2818)  评论(0编辑  收藏  举报