前言:本文章只是简单阐述实际开发中遇到的OOM 问题 本文不会涉及太多很深的技术点,我也不懂,哈哈啊哈哈哈哈~
(一) 什么是OOM?
1: 这是一个面试官经常会问到的问题
OOM就是内存溢出,即Out Of Memory。也就是说申请的内存超过了VM所分配的最大内存;
这里我们简单阐述一个面试点: 内存溢出(OOM)和内存泄漏(memory leak)的区别 有好多刚入学的童鞋一看"溢出","泄漏" 哎呀!我擦!这不一个东西,其实不然,两者还是存在很大的区别.
内存溢出: 简单举个例子:,就好比哥几个去按摩店 按摩 (请注意我这里指的是正规的按摩店,遥哥哥最近脖子疼,大家懂得.) 总共五个人,到店了,申请开房,然后服务员领着哥几个去房间,打开房间一看, 我擦! 搞毛呢?五个人,你给我整四张床,然后服务员解释道:"对不起,由于周末人多,目前只能给你们提供四人间" ........那另一个人躺门外啊,这就是溢出, 就是你所需要的(内存)超出了系统给你提供的(内存).
内存泄漏: 最后等了半天,换了五人间,躺好后,这脖子难受死了,这要赶紧叫中医按摩师傅,五个人五个师傅,师傅来了后,按理说就该开始了,突然一个哥们侧过身对我说"遥遥,我要换个技师",我一听反问道:"换技师干嘛",这哥们吞吞吐吐说"这个不好看",我一听大骂道"xxxx,按摩和技术长相有毛的关系?" 但话又说回来,其实我也没相中,哈哈,没办法,自己请客,总得让兄弟玩的开心吧,换就换吧,真是的...换了一个又一个人,我这一睁眼,屋里面站满了人,这什么情况!!!!!! 我就和哥们说"你换就换,你换,你总得让之前的回去吧,屋子本来就不大,师傅都站到外边去了",哥们赶紧解释道"我控几不住我几及啊,总得让我对比一下吧",遥遥表示翻了个白眼,这孩子真是宛如智障! 师傅都被挤到门外去了,这就是泄漏,就是你换了技师,你又不让之前的回去,你申请内存后,又无法释放掉,久而久之就会造成内存溢出(OOM)
咳咳....我绝对是正经人,大家不要误会,好了简单说个知识点,接下来我们言归正传......
上面我们已经讲了OOM产生的原因,要是想了解具体怎么产生的,为什么就超过了呢?这个大家可以去详细了解下关于Android 内存管理机制 Google在Android的官网上有一篇文章,初步介绍了Android是如何管理应用的进程与内存分配;
(二)如何避免OOM总结
减小对象的内存占用
1)使用更加轻量的数据结构
例如,我们可以考虑使用比较高效的ArrayMap/SparseArray而不是HashMap等传统数据结构。需要的同学可以去深入了解几个的数据结构和原理.这里不探讨太深.
2)减小Bitmap对象的内存占用
Bitmap的不当处理极可能造成OOM,绝大多数情况都是因这个原因出现的。Bitamp位图是Android中当之无愧的胖小子,所以在操作的时候当然是十分的小心了。由于Dalivk并不会主动的去回收,需要开发者在Bitmap不被使用的时候recycle掉。使用的过程中,及时释放是非常重要的。
项目中我们经常会加载一些大图片,有时候会一下加载很多,一张在pc机上用的1024*768图片,如果直接用在手机屏幕这种小屏幕上,不仅没有提高显示质量,还容易使内存吃紧。假设照片是用ARGB_8888格式,那么一张1024×768的图片需要占用3M的内存, 4-5张就OOM了。bitmap分辨率越高,所占用的内存就越大,这个是以2为指数级增长的。
解决方案:
inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。
有时候我们用第三方框架的时候存在bug,在加载的图片的时候回造成OMM问题,目前项目用的Glide遇到过这个问题,具体原因还待研究.
3)使用更小的图片
在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。
作为android适配 一般我们都会有四套资源图片
就目前而言,我们完全没必要配置xxxhdpi 资源文件
内存对象的重复利用
RecycleView/GridView等item的缓存
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的子控件对象了。这样便大大提高了性能。
4)避免对象的内存泄露
内存对象的泄漏,会导致一些不再使用的对象无法及时释放,这样一方面占用了宝贵的内存空间,很容易导致后续需要分配内存的时候,空闲空间不足而出现OOM。
1)注意Activity的泄漏
通常来说,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广,我们需要特别注意以下两种情况导致的Activity泄漏:
内部类引用导致Activity的泄漏
最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。
Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏。
内部类引起的泄漏不仅仅会发生在Activity上,其他任何内部类出现的地方,都需要特别留意!我们可以考虑尽量使用static类型的内部类,同时使用WeakReference的机制来避免因为互相引用而出现的泄露。
2)考虑使用Application Context而不是Activity Context
对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。
3)注意临时Bitmap对象的及时回收
虽然在大多数情况下,我们会对Bitmap增加缓存机制,但是在某些时候,部分Bitmap是需要及时回收的。例如临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。
4)注意WebView的泄漏
Android中的WebView存在很大的兼容性问题,不仅仅是Android系统版本的不同对WebView产生很大的差异,另外不同的厂商出货的ROM里面WebView也存在着很大的差异。更严重的是标准的WebView存在内存泄露的问题,请看这里。所以通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
5)内存使用策略优化
1)资源文件需要选择合适的文件夹进行存放
我们知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100x100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200x200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下。
2)Try catch某些大内存分配的操作
在某些情况下,我们需要事先评估那些可能发生OOM的代码,对于这些可能发生OOM的代码,加入catch机制,可以考虑在catch里面尝试一次降级的内存分配操作。例如decode bitmap的时候,catch到OOM,可以尝试把采样比例再增加一倍之后,再次尝试decode。
3)谨慎使用static对象
因为static的生命周期过长,和应用的进程保持一致,使用不当很可能导致对象泄漏,在Android中应该谨慎使用static对象
4)及时释放无用的对象,尤其是循环内的对象要及时释放
5):优化代码,更高效的逻辑处理