TECH_ANDROIDAndroid汇总总结Android 知识

Android避免OOM(内存优化)

2016-06-02  本文已影响7340人  lwwlsky

Android内存优化是性能优化很重要的一部分,而如何避免OOM又是内存优化的核心。

Android内存管理机制

android官网有一篇文章

Android是如何管理应用的进程与内存分配
Android系统的Dalvik虚拟机扮演了内存垃圾自动回收的角色。

OOM介绍(out of memory 内存溢出)

Android和java中都会出现由于不良代码引起的内存泄露,为了使Android应用程序能够快速高效的运行,Android每个应用程序都会有专门Dalvik虚拟机实例来运行,也就是每个程序都在属于自己的进程中运行。
这样,某个应用程序内存泄露仅仅只会使自己进程被kill掉不会影响其他进程(如果是system_process等系统进程出现问题,就会造成系统重启),另一方面,系统为每一个应用程序分配了不同的内存上限,如果超过这个上限被视为内存泄露,从而被kill掉。
Dalvik Heap size因不同设备的RAM不同而有所差异,应用占用内存接近这个阀值,在尝试分配内存就会引起outofmemoryError的错误。

出现OOM有几种情况:

  1. 加载对象过大
  2. 相应资源过多,来不及加载。

解决这些问题,有:

  1. 内存引用上做一些处理,常用的有软引用。
  2. 内存中加载图片直接在内存中做处理(如边界压缩)
    这个Glide\Fresco 图片框架可能封装好了
    3.动态回收内存
    4.优化Delivk虚拟机的堆内存分配
    5.自定义堆内存大小

共享内存

Android应用程序的进程都是从Zygote的进程fork出来的。Zygote进程在系统启动并载入通用的framework代码和资源后启动。一个新的应用程序启动,系统就会从Zygote中fork出来一个新的进程,在新的进程中加载并允许应用程序的代码。这使得大多数RAM pages被分配给framework的代码,并且RAM资源能够在应用的所有进程之间共享。

大多数static 数据被mmapped到一个进程中,这样使得同样的数据在进程之间能够共享,而且在需要的时候能paged out.常见static 数据包括Dalvik code ,app resourecs,so 文件等。

大多数情况下,Android通过显示的方式分配共享内存区域(例如ashmem或gralloc)来实现动态RAM区域能够在不同进程之间进行共享的机制。比如,Window Surface在APP和Screen Composition之间使用共享的内存,
Cursor Buffers在Content Provider与Clients之间共享内存。

分配与回收内存

应用切换操作

Android系统不会再用户切换应用的时候进行交换内存的操作,而是把不包含Foreground组件的应用进程放到LRUCache中,比如用户启动一个应用,系统会为它创建一个进程,但是当用户离开这个应用,此进程不会背立即销毁而是会放到一个Cache中,当用户切换回来够快速的恢复。

发生OOM的条件

通过不同的内存分配方式对不同的对象(bitmap,etc)进行操作因Android版本差异发生变化。
4.0以上,废除了external的计数器,类似bitmap的分配改到dalvik的Java heap(堆)中申请,只要allocated+新分配的内存>=getMemoryClass()就会发生OOM。(在AS memory monitor查看内存中Dalvik Heap的实时变化)

如何避免OOM

减少OOM的第一步就是要尽量减少新分配出来的对象占用内存的大小,尽量使用更加轻量的对象。

  1. 使用更加轻量的数据结构
    考虑使用ArrayMap/SpareseArray而不是传统的HashMap等数据结构,Android系统为移动系统设计的容器ArrayMap更加高效,占用内存更少,因为HashMap需要一个额外的实例对象来记录Mapping的操作。而SparesArray高效的避免了key和value的自动装箱,而且避免了装箱后的解箱。


    关于更多ArrayMap/SparseArray的讨论,请参考http://hukai.me/android-performance-patterns-season-3/的前三个段落
  2. 避免在Android中使用Enum

  3. 减少Bitmap对象的内存占用
    Bitmap是一个消耗内存的大胖子,减少创建出来的Bitmap的内存占用很重要。一般有两种措施

  1. 使用更小的图片
    在设计图片资源的时候,我们要考虑图片是否存在可以压缩的空间,是否能使用更小的图片,使用小图在xml加载资源时就不会在初始化视图因为内存不足而发生InflationException,其根本原因就是发生了OOM。

内存对象的重复利用

Android最常用的缓存算法LRU(Least Recently Use)


  1. 复用系统自带的资源,比如字符串、图片、动画、样式、颜色、简单布局,在应用中直接引用,减少自身负重、apk大小、减少内存的开销、复用性更好。但需要考虑版本差异。
  2. Listview和GirdView出现大量重复子组件的视图里面对ConvertView的复用。
  3. Bitmap对象的复用
inBitmap的限制
  1. 避免在onDraw方法里面执行对象的创建
    在onDraw这种频繁调用的方法要避免对象的创建操作,因为他会迅速增加内存的使用,引起频繁的gc,甚至内存抖动
    5.StringBuilder
    如果代码中有大量字符串拼接操作,使用StringBuilder代替"+"

避免对象的内存泄露

内存对象的泄露会导致不再使用的对象无法及时释放,不仅浪费了宝贵的内存空间,后续要分配内存的时候,空间不足造成OOM。这样,每级的generation会变小,gc更加容易触发,引起内存抖动,带来性能问题。

  1. 注意Activity的泄露
    Activity泄露是内存泄露最为严重的问题,涉及内存多,影响面广
    两种情形:
  1. 考虑使用Application Context而不是Activity Context
    除必须使用Activity Context的情况(Dialog的context必须是Activity),我们可以使用Application Context来避免Activity泄露
  2. 注意临时Bitmap的及时回收
    大多数情况下,我们对Bitmap对象增加缓存机制,但是有时候部分bitmap需要及时回收。比如我们临时创建的摸个相对大的bitmap对象,变换得到新的bitmap对象后,尽快回收原始的bitmap,及时释放原来的空间。
  3. 注意监听器的注销
    android程序里面register后要及时释放unregister那些监听器,自己手动add的listener,要记得remove这个listener.
    5.注意缓存容器的对象泄露
    有时候我们为了提高对象的复用性,把某些对象放到缓存容器中,如果这些对象没有及时从容器中清楚,也可能导致内存泄露,
  4. 注意webview的泄露
    Android不同版本对webview产生有很大差异,较为严重的问题是webview的泄露,解决办法:为webview新开一个线程,通过AIDL与主进程通信,根据业务的需要在合适的时机进行销毁,从而达到内存的释放。
  5. 注意cursor对象是否关闭
    我们在对数据库进行操作时,使用完cursor没有及时关闭,cursor的泄露,会对内存管理带来负面影响

内存使用策略优化

1.谨慎使用large heap
android设备由于软硬件的差异,heap阀值不同,特殊情况下可以在manifest中使用largeheap=true声明一个更大的heap空间,使用getLargeMemoryClass()来获取到这个更大的空间。但是要谨慎使用,因为额外的空间会影响到系统整体的用户体验,并且会使每次gc的运行时间更长。切换任务时性能大打折扣,large heap并不一定能获取到更大的heap.

  1. 综合考虑设备内存阈值与其他因素设计合适的缓存大小
    例如,在设计ListView或者GridView的Bitmap LRU缓存的时候,需要考虑的点有:

应用程序剩下了多少可用的内存空间?

  1. onLowMemory() 与onTrimMemory()
    Android可以在不同的应用当中随意切换。为了让background转到foreground, 每一个background都会占用一定的内存。系统会根据内存的使用情况决定回收部分background的应用内存。background的应用从暂停状态恢复到foreground,比较快,如果从kill状态恢复比较慢。
  2. 资源文件需要选择合适的文件夹进行存放
    我们知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi
    的手机去引用那张图片就会被拉伸到200
    200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下。
  3. Try catch某些大内存分配的操作
    在某些情况下,我们需要事先评估那些可能发生OOM的代码,对于这些可能发生OOM的代码,加入catch机制,可以考虑在catch里面尝试一次降级的内存分配操作。例如decode bitmap的时候,catch到OOM,可以尝试把采样比例再增加一倍之后,再次尝试decode。
  4. 谨慎使用static对象
    因为static的生命周期过长,和应用的进程保持一致,使用不当很可能导致对象泄漏,在Android中应该谨慎使用static对象。
  5. 特别留意单例对象中不合理的持有
    虽然单例模式简单实用,提供了很多便利性,但是因为单例的生命周期和应用保持一致,使用不合理很容易出现持有对象的泄漏。
  6. 珍惜Services资源
    如果你的应用需要在后台使用service,除非它被触发并执行一个任务,否则其他时候Service都应该是停止状态。另外需要注意当这个service完成任务之后因为停止service失败而引起的内存泄漏。 当你启动一个Service,系统会倾向为了保留这个Service而一直保留Service所在的进程。这使得进程的运行代价很高,因为系统没有办法把Service所占用的RAM空间腾出来让给其他组件,另外Service还不能被Paged out。这减少了系统能够存放到LRU缓存当中的进程数量,它会影响应用之间的切换效率,甚至会导致系统内存使用不稳定,从而无法继续保持住所有目前正在运行的service。 建议使用IntentService,它会在处理完交代给它的任务之后尽快结束自己。更多信息,请阅读Running in a Background Service
  7. 优化布局层次,减少内存消耗
    越扁平化的视图布局,占用的内存就越少,效率越高。我们需要尽量保证布局足够扁平化,当使用系统提供的View无法实现足够扁平的时候考虑使用自定义View来达到目的。
  8. 谨慎使用“抽象”编程
    很多时候,开发者会使用抽象类作为”好的编程实践”,因为抽象能够提升代码的灵活性与可维护性。然而,抽象会导致一个显著的额外内存开销:他们需要同等量的代码用于可执行,那些代码会被mapping到内存中,因此如果你的抽象没有显著的提升效率,应该尽量避免他们。
  9. 使用nano protobufs序列化数据
    Protocol buffers是由Google为序列化结构数据而设计的,一种语言无关,平台无关,具有良好的扩展性。类似XML,却比XML更加轻量,快速,简单。如果你需要为你的数据实现序列化与协议化,建议使用nano protobufs。关于更多细节,请参考protobuf readme的”Nano version”章节。
  10. 谨慎使用依赖注入框架
    使用类似Guice或者RoboGuice等框架注入代码,在某种程度上可以简化你的代码。下面是使用RoboGuice前后的对比图:

13.谨慎使用多进程
使用多进程可以把应用中的部分组件运行在单独的进程当中,这样可以扩大应用的内存占用范围,但是这个技术必须谨慎使用,绝大多数应用都不应该贸然使用多进程,一方面是因为使用多进程会使得代码逻辑更加复杂,另外如果使用不当,它可能反而会导致显著增加内存。当你的应用需要运行一个常驻后台的任务,而且这个任务并不轻量,可以考虑使用这个技术。

一个典型的例子是创建一个可以长时间后台播放的Music Player。如果整个应用都运行在一个进程中,当后台播放的时候,前台的那些UI资源也没有办法得到释放。类似这样的应用可以切分成2个进程:一个用来操作UI,另外一个给后台的Service。

  1. 使用ProGuard来剔除不需要的代码
    ProGuard能够通过移除不需要的代码,重命名类,域与方法等等对代码进行压缩,优化与混淆。使用ProGuard可以使得你的代码更加紧凑,这样能够减少mapping代码所需要的内存空间。
  2. 谨慎使用第三方libraries
    很多开源的library代码都不是为移动网络环境而编写的,如果运用在移动设备上,并不一定适合。即使是针对Android而设计的library,也需要特别谨慎,特别是在你不知道引入的library具体做了什么事情的时候。例如,其中一个library使用的是nano protobufs, 而另外一个使用的是micro protobufs。这样一来,在你的应用里面就有2种protobuf的实现方式。这样类似的冲突还可能发生在输出日志,加载图片,缓存等等模块里面。另外不要为了1个或者2个功能而导入整个library,如果没有一个合适的库与你的需求相吻合,你应该考虑自己去实现,而不是导入一个大而全的解决方案。
  3. 考虑不同的实现方式来优化内存占用

写在最后:

详细看郭霖的分析内存的使用总结
胡凯大大内存优化之OOM

上一篇下一篇

猜你喜欢

热点阅读