app开发中,图片是少不了的。各种图标图片资源,如果不能很好的处理图片的利用。会导致app性能严重下降,影响用户体验,最直观的感受就是卡顿,手机发热,有时候还OOM,
那么今天我们就来分析oom和内存优化总结;
按照JVM规范,JAVA虚拟机在运行时会管理以下的内存区域:
Android系统的app的每个进程或者每个虚拟机有个最大内存限制,如果申请的内存资源超过这个限制,系统就会抛出OOM错误;
跟整个设备的剩余内存没太大关系。比如比较早的android系统的一个虚拟机最多16M内存,当一个app启动后,虚拟机不停的申请内存资源来装载图片,当超过内存上限时就出现OOM
为什么会没有内存了呢?原因不外乎有两点:
1、分配的少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少;
2、应用用的太多,并且用完没释放,浪费了,此时就会造成内存泄露或者内存溢出;
在之前没有垃圾自动回收的日子里,比如C语言和C++语言,我们必须亲自负责内存的申请与释放操作,如果申请了内存,用完后又忘记了释放,比如C++中的new了但是没有delete,那么就可能造成内存泄露。偶尔的内存泄露可能不会造成问题,而大量的内存泄露可能会导致内存溢出;
而在Java语言中,由于存在了垃圾自动回收机制,所以,我们一般不用去主动释放不用的对象所占的内存,也就是理论上来说,是不会存在“内存泄露”的。但是,如果编码不当,比如,将某个对象的引用放到了全局的Map中,虽然方法结束了,但是由于垃圾回收器会根据对象的引用情况来回收内存,导致该对象不能被及时的回收。如果该种情况出现次数多了,就会导致内存溢出,比如系统中经常使用的缓存机制。Java中的内存泄露,不同于C++中的忘了delete,往往是逻辑上的原因泄露。
避免OOM的第一步就是要尽量减少新分配出来的对象占用内存的大小,尽量使用更加轻量的对象。
1)使用更加轻量的数据结构
我们可以考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。
HashMap的简要工作原理,相比起Android专门为移动操作系统编写的ArrayMap容器,在大多数情况下,都显示效率低下,更占内存。
通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。
另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),并且避免了装箱后的解箱。
2)避免在Android里面使用Enum
枚举通常需要两倍于静态常量的内存。您应该严格避免在Android上使用枚举。,所以请避免在Android里面使用到枚举。
3)减小Bitmap对象的内存占用
Bitmap是一个极容易消耗内存的大胖子,减小创建出来的Bitmap的内存占用可谓是重中之重,通常来说有以下2个措施
inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入。
decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。
4)使用更小的图片
在涉及给到资源图片时,我们需要特别留意这张图片是否存在可以压缩的空间,是否可以使用更小的图片。尽量使用更小的图片不仅可以减少内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,很有可能在初始化视图时会因为内存不足而发生InflationException,这个问题的根本原因其实是发生了OOM。
内存对象的泄漏,会导致一些不再使用的对象无法及时释放,这样一方面占用了宝贵的内存空间,很容易导致后续需要分配内存的时候,空闲空间不足而出现OOM。显然,这还使得每级Generation的内存区域可用空间变小,GC就会更容易被触发,容易出现内存抖动,从而引起性能问题
1)注意Activity的泄漏
2)考虑使用Application Context而不是Activity Context
对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露;
3)注意临时Bitmap对象的及时回收
4)注意监听器的注销
在Android程序里面存在很多需要register与unregister的监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的listener,需要记得及时remove这个listener。
5)注意缓存容器中的对象泄漏
有时候,我们为了提高对象的复用性把某些对象放到缓存容器中,可是如果这些对象没有及时从容器中清除,也是有可能导致内存泄漏的。例如,针对2.3的系统,如果把drawable添加到缓存容器,因为drawable与View的强应用,很容易导致activity发生泄漏。而从4.0开始,就不存在这个问题。解决这个问题,需要对2.3系统上的缓存drawable做特殊封装,处理引用解绑的问题,避免泄漏的情况。
6)注意WebView的泄漏
Android中的WebView存在很大的兼容性问题,不仅仅是Android系统版本的不同对WebView产生很大的差异,另外不同的厂商出货的ROM里面WebView也存在着很大的差异。更严重的是标准的WebView存在内存泄露的问题,请看 这里。所以通常根治这个问题的办法是为WebView开启另外一个进程,通过AIDL与主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
7)注意Cursor对象是否及时关闭
在程序中我们经常会进行查询数据库的操作,但时常会存在不小心使用Cursor之后没有及时关闭的情况。这些Cursor的泄露,反复多次出现的话会对内存管理产生很大的负面影响,我们需要谨记对Cursor对象的及时关闭。
1)谨慎使用large heap
2)综合考虑设备内存阈值与其他因素设计合适的缓存大小
在设计ListView或者GridView的Bitmap LRU缓存的时候,需要考虑的点有:
3)onLowMemory()与onTrimMemory()
Android用户可以随意在不同的应用之间进行快速切换。为了让background的应用能够迅速的切换到forground,每一个background的应用都会占用一定的内存。Android系统会根据当前的系统的内存使用情况,决定回收部分background的应用内存。如果background的应用从暂停状态直接被恢复到forground,能够获得较快的恢复体验,如果background应用是从Kill的状态进行恢复,相比之下就显得稍微有点慢。
当程序正在前台运行的时候,可能会接收到从onTrimMemory()中返回的下面的值之一:
当应用进程退到后台正在被Cached的时候,可能会接收到从onTrimMemory()中返回的下面的值之一:
因为onTrimMemory()的回调是在API 14才被加进来的,对于老的版本,你可以使用onLowMemory)回调来进行兼容。onLowMemory相当与TRIM_MEMORY_COMPLETE。
请注意:当系统开始清除LRU缓存中的进程时,虽然它首先按照LRU的顺序来执行操作,但是它同样会考虑进程的内存使用量以及其他因素。占用越少的进程越容易被留下来。
4)资源文件需要选择合适的文件夹进行存放
我们知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下。
5)Try catch某些大内存分配的操作
在某些情况下,我们需要事先评估那些可能发生OOM的代码,对于这些可能发生OOM的代码,加入catch机制,可以考虑在catch里面尝试一次降级的内存分配操作。例如decode bitmap的时候,catch到OOM,可以尝试把采样比例再增加一倍之后,再次尝试decode。
6)谨慎使用static对象
因为static的生命周期过长,和应用的进程保持一致,使用不当很可能导致对象泄漏,在Android中应该谨慎使用static对象。
7)特别留意单例对象中不合理的持有
虽然单例模式简单实用,提供了很多便利性,但是因为单例的生命周期和应用保持一致,使用不合理很容易出现持有对象的泄漏。
8)珍惜Services资源
如果你的应用需要在后台使用service,除非它被触发并执行一个任务,否则其他时候Service都应该是停止状态。另外需要注意当这个service完成任务之后因为停止service失败而引起的内存泄漏。当你启动一个Service,系统会倾向为了保留这个Service而一直保留Service所在的进程。这使得进程的运行代价很高,因为系统没有办法把Service所占用的RAM空间腾出来让给其他组件,另外Service还不能被Paged out。这减少了系统能够存放到LRU缓存当中的进程数量,它会影响应用之间的切换效率,甚至会导致系统内存使用不稳定,从而无法继续保持住所有目前正在运行的service。建议使用IntentService,它会在处理完交代给它的任务之后尽快结束自己。更多信息,请阅读 Running in a Background Service。
9)优化布局层次,减少内存消耗
越扁平化的视图布局,占用的内存就越少,效率越高。我们需要尽量保证布局足够扁平化,当使用系统提供的View无法实现足够扁平的时候考虑使用自定义View来达到目的。
10)谨慎使用“抽象”编程
很多时候,开发者会使用抽象类作为”好的编程实践”,因为抽象能够提升代码的灵活性与可维护性。然而,抽象会导致一个显著的额外内存开销:他们需要同等量的代码用于可执行,那些代码会被mapping到内存中,因此如果你的抽象没有显著的提升效率,应该尽量避免他们。
11)使用nano protobufs序列化数据
Protocol buffers是由Google为序列化结构数据而设计的,一种语言无关,平台无关,具有良好的扩展性。类似XML,却比XML更加轻量,快速,简单。如果你需要为你的数据实现序列化与协议化,建议使用nano protobufs。关于更多细节,请参考 protobuf readme的”Nano version”章节。
12)谨慎使用依赖注入框架
那些注入框架会通过扫描你的代码执行许多初始化的操作,这会导致你的代码需要大量的内存空间来mapping代码,而且mapped pages会长时间的被保留在内存中。除非真的很有必要,建议谨慎使用这种技术;
13)谨慎使用多进程
14)使用ProGuard来剔除不需要的代码
ProGuard能够通过移除不需要的代码,重命名类,域与方法等等对代码进行压缩,优化与混淆。使用ProGuard可以使得你的代码更加紧凑,这样能够减少mapping代码所需要的内存空间。
15)谨慎使用第三方libraries
很多开源的library代码都不是为移动网络环境而编写的,如果运用在移动设备上,并不一定适合。即使是针对Android而设计的library,也需要特别谨慎,特别是在你不知道引入的library具体做了什么事情的时候。例如,其中一个library使用的是nano protobufs, 而另外一个使用的是micro protobufs。这样一来,在你的应用里面就有2种protobuf的实现方式。这样类似的冲突还可能发生在输出日志,加载图片,缓存等等模块里面。另外不要为了1个或者2个功能而导入整个library,如果没有一个合适的库与你的需求相吻合,你应该考虑自己去实现,而不是导入一个大而全的解决方案。
内存优化并不就是说程序占用的内存越少就越好,如果因为想要保持更低的内存占用,而频繁触发执行gc操作,在某种程度上反而会导致应用性能整体有所下降,这里需要综合考虑做一定的权衡。
Android的内存优化涉及的知识面还有很多:内存管理的细节,垃圾回收的工作原理,如何查找内存泄漏等等都可以展开讲很多。OOM是内存优化当中比较突出的一点,尽量减少OOM的概率对内存优化有着很大的意义
网站名称:Android性能优化-OOM崩溃引起的内存管理大爆炸详解
分享地址:http://www.shufengxianlan.com/qtweb/news33/445183.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联