re1和re2的区别是,re2使用了匿名内部类 。运行时这两个引用的内存可以看到,re1没什么特别的 。
但ref2这个匿名类的实现对象里面多了一个引用:this$0这个引用指向MainActivity.this 。
也就是说当前的MainActivity实例会被re2持有,如果将这个引用再传入一个异步线程,此线程和此Acitivity生命周期不一致的时候,就造成了Activity的泄露 。
5、Handler 造成的内存泄漏
handler为了避免ANR而不在主线程进行耗时操作,去处理网络任务或者封装一些请求回调等api等 。我们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的,万一 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有造成内存泄漏 。
由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的 。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放 。
知识点:在java里 ,非静态内部类 和匿名类都会潜在的引用它们所属的外部类 。但是静态内部类却不会 。
接下里看个案例:
public class SampleActivity extends Activity {private final Handler mHandler = 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. mHandler.postDelayed(new Runnable() { @Override public void run() { ... }}, 1000 * 60 * 10);// Go back to the previous Activity. finish(); }}分析:
当activity结束(finish)时,里面的延时消息在得到处理前,会一直保存在主线程的消息队列里持续10分钟 。而且,由上文可知这个message持有handler引用,而handler又持有对其外部类(activity)的潜引用 。这条引用关系会一直保持直到消息得到处理,从而阻止了acitivty被垃圾回收器回收,造成应用程序的泄漏 。另外非静态匿名类Runnable同样持有外部类,导致泄漏 。总结2条原因:
【最常见的Android内存优化方式及防止泄漏造成OOM总结篇】小结:
- 只要有未处理的消息,那么消息会引用handler,非静态的handler又会引用外部类,即Activity,导致Activity无法被回收,造成泄漏;
- Runnable类属于非静态匿名类,同样会引用外部类 。
- 我们可以把handler类放在单独的类文件中,或者使用静态内部类便可以避免泄漏 。另外,如果想要在handler内部去调用所在的外部类Activity,那么可以在handler内部使用弱引用的方式指向所在Activity,这样统一不会导致内存泄漏 。
- 对于匿名类Runnable,同样可以将其设置为静态类 。因为静态的匿名类不会持有对外部类的引用 。
public class SampleActivity extends Activity {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);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(); }}如果一个内部类实例的生命周期比Activity更长,那么我们千万不要使用非静态的内部类 。最好的做法是,使用静态内部类,然后在该类里使用弱引用来指向所在的Activity 。综述,推荐使用静态内部类 + WeakReference 这种方式 。每次使用前注意判空 。
知识点:
前面提到了 WeakReference,所以这里就简单的说一下 Java 对象的几种引用类型 。
Java对引用的分类有强(Strong reference),软(SoftReference),弱 (WeakReference),虚 PhatomReference 四种 。
在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术 。
软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中 。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用 。
假设我们的应用会用到大量的默认图片,比如应用中有默认的头像,默认游戏图标等等,这些图片很多地方会用到 。如果每次都去读取图片,由于读取文件需要硬件操作,速度较慢,会导致性能较低 。所以我们考虑将图片缓存起来,需要的时候直接从内存中读取 。但是,由于图片占用内存空间比较大,缓存很多图片需要很多的内存,就可能比较容易发生OutOfMemory异常 。这时,我们可以考虑使用软/弱引用技术来避免这个问题发生 。以下就是高速缓冲器的雏形:首先定义一个HashMap,保存软引用对象——private Map 。
推荐阅读
- javascript实现web通讯的几种方式
- 绿茶可以改善唐氏综合症患者的记忆和词汇认知能力
- 在过去的四个月里全国范围内的商店数量已经达到100家
- 虚拟主机FTP连接失败的原因
- 专家称云南红茶将成为云南茶业新的增长极
- 常见的Web安全漏洞及测试方法介绍
- 尚流藏茶创始人杨坚持你的第颗心耐得住寂寞
- 什么是核心交换机的链路聚合、冗余、堆叠、热备份
- 常用的八个cmd命令,你会熟练应用吗?
- 告诉你PC不能通过二层交换机实现跨网段通信的小秘密:你很难想到
