Android中内存泄露与如何有效避免OOM总结

节选自:Android中内存泄露与如何有效避免OOM总结


三、预防OOM的几点建议

Android开发过程中,在 Activity的生命周期里协调耗时任务可能会很困难,你一不小心就会导致内存泄漏问题。下面是一些小提示,能帮助你预防内存泄漏问题的发生:

1、合理使用static:

每一个非静态内部类实例都会持有一个外部类的引用,若该引用是Activity 的引用,那么该Activity在被销毁时将无法被回收。如果你的静态内部类需要一个相关Activity的引用以确保功能能够正常运行,那么你得确保你在对象中使用的是一个Activity的弱引用,否则你的Activity将会发生意外的内存泄漏。但是要注意,当此类在全局多处用到时在这样干,因为static声明变量的生命周期其实是和APP的生命周期一样的,有点类似与Application。如果大量的使用的话,就会占据内存空间不释放,积少成多也会造成内存的不断开销,直至挂掉。static的合理使用一般用来修饰基本数据类型或者轻量级对象,尽量避免修复集合或者大对象,常用作修饰全局配置项、工具类方法、内部类。

2、善用SoftReference/WeakReference/LruCache

Java、Android中有没有这样一种机制呢,当内存吃紧或者GC扫过的情况下,就能及时把一些内存占用给释放掉,从而分配给需要分配的地方。答案是肯定的,java为我们提供了两个解决方案。如果对内存的开销比较关注的APP,可以考虑使用WeakReference,当GC回收扫过这块内存区域时就会回收;如果不是那么关注的话,可以使用SoftReference,它会在内存申请不足的情况下自动释放,同样也能解决OOM问题。同时Android自3.0以后也推出了LruCache类,使用LRU算法就释放内存,一样的能解决OOM,如果兼容3.0一下的版本,请导入v4包。关于第二条的无关引用的问题,我们传参可以考虑使用WeakReference包装一下。

3、谨慎handler

在处理异步操作的时候,handler + thread是个不错的选择。但是相信在使用handler的时候,大家都会遇到警告的情形,这个就是lint为开发者的提醒。handler运行于UI线程,不断处理来自MessageQueue的消息,如果handler还有消息需要处理但是Activity页面已经结束的情况下,Activity的引用其实并不会被回收,这就造成了内存泄漏。解决方案,一是在Activity的onDestroy方法中调handler.removeCallbacksAndMessages(null);取消所有的消息的处理,包括待处理的消息;二是声明handler的内部类为static。

4、不要总想着Java 的垃圾回收机制会帮你解决所有内存回收问题

就像上面的示例,我们以为垃圾回收机制会帮我们将不需要使用的内存回收,例如:我们需要结束一个Activity,那么它的实例和相关的线程都该被回收。但现实并不会像我们剧本那样走。Java线程会一直存活,直到他们都被显式关闭,抑或是其进程被Android系统杀死。所以,为你的后台线程实现销毁逻辑是你在使用线程时必须时刻铭记的细节,此外,你在设计销毁逻辑时要根据Activity的生命周期去设计,避免出现Bug。

考虑你是否真的需要使用线程。Android应用的框架层为我们提供了很多便于开发者执行后台操作的类。例如:我们可以使用Loader 代替在Activity 的生命周期中用线程通过注入执行短暂的异步后台查询操作,考虑用Service将结构通知给UI的BroadcastReceiver。最后,记住,这篇博文中对线程进行的讨论同样适用于AsyncTask(因为AsyncTask使用ExecutorService执行它的任务)。然而,虽说ExecutorService只能在短暂操作(文档说最多几秒)中被使用,那么这些方法导致的Activity内存泄漏应该永远不会发生。

5、ListView和GridView的item缓存

对于移动设备,尤其硬件参差不齐的android生态,页面的绘制其实是很耗时的,findViewById也是蛮慢的。所以不重用View,在有列表的时候就尤为显著了,经常会出现滑动很卡的现象,所以我们要善于重复利用创建好的控件。这里主要注意两点:

1)convertView重用

ListView中的每一个Item显示都需要Adapter调用一次getView()的方法,这个方法会传入一个convertView的参数,这个方法返回的View就是这个Item显示的View。Android提供了一个叫做Recycler(反复循环)的构件,就是当ListView的Item从滚出屏幕视角之外,对应Item的View会被缓存到Recycler中,相应的会从生成一个Item,而此时调用的getView中的convertView参数就是滚出屏幕的缓存Item的View,所以说如果能重用这个convertView,就会大大改善性能。

2)使用ViewHolder重用

我们都知道在getView()方法中的操作是这样的:先从xml中创建view对象(inflate操作,我们采用了重用convertView方法优化),然后在这个view去findViewById,找到每一个item的子View的控件对象,如:ImageView、TextView等。这里的findViewById操作是一个树查找过程,也是一个耗时的操作,所以这里也需要优化,就是使用ViewHolder,把每一个item的子View控件对象都放在Holder中,当第一次创建convertView对象时,便把这些item的子View控件对象findViewById实例化出来并保存到ViewHolder对象中。然后用convertView的setTag将viewHolder对象设置到Tag中, 当以后加载ListView的item时便可以直接从Tag中取出复用ViewHolder对象中的,不需要再findViewById找item的子控件对象了。这样便大大提高了性能。

不过Android5.L为我们提供了RecyclerView,RecyclerView是经典的ListView的进化与升华,它比ListView更加灵活,但也因此引入了一定的复杂性。最新的v7支持包新添加了RecyclerView。RecyclerView提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator实现令人瞠目的效果。而且RecyclerView内部为我们处理了item缓存,所以用着效率更高,更安全,感兴趣的读者可以了解一下。

发表评论