Android性能优化之OOM

什么是OOM?

java.lang.OutOfMemoryError
当程序需要申请一段“大”内存时,但是虚拟机没有办法及时的给到,这就会抛出 OutOfMemoryError 也就是OOM

为什么会有OOM?

因为android系统的app的每个进程或者每个虚拟机有个最大内存限制,如果申请的内存资源超过这个限制,系统就会抛出OOM错误。跟整个设备的剩余内存没太大关系。比如比较早的android系统的一个虚拟机最多16M内存,当一个app启动后,虚拟机不停的申请内存资源来装载图片,当超过内存上限时就出现OOM。

APP的内存限制

APP内存由 dalvik内存 和 native内存 两部分组成,dalvik也就是java堆,创建的对象就是在这里分配的,而native是通过c/c++方式申请的内存,Bitmap就是以这种方式分配的。

App的内存限制是多少?

// 以下方法会返回以M为单位的数字,不同的系统平台或设备上的值都不太一样,这里取到是虚拟机的最大内存资源。
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)
int size = activityManager.getMemoryClass();

// head堆的大小限制,可以查看/system/build.prop文件
dalvik.vm.heapstartsize  =  5m
dalvik.vm.heapgrowthlimit = 48m
dalvik.vm.heapsize = 256m
// heapsize参数表示单个进程heap可用的最大内存,但如果存在以下参数"dalvik.vm.headgrowthlimit=48m"表示单个进程heap内存被限定在48m,即程序运行过程实际只能使用48m内存

为什么Android系统要设定App的内存限制?

  • 要使开发者内存使用更为合理。 限制每个应用的可用内存上限,可以放置某些应用程序恶意或者无意的使用过多的内存。而导致其它应用无法正常运行。Android是有多进程的,如果一个进程(一个应用)耗费过多的内存,其他应用就无法运行了。因为有了限制,使得开发者必须好好利用有限资源,优化资源的使用。
  • 屏幕显示内容有限,内存足够即可。 即使有万千图片千万数据需要使用到,但在特定时刻需要展示给用户看的总是有限的,因为屏幕显示就那么大,上面可以放的信息就是很有限的。大部分信息都是处于准备显示状态,所以没必要给予太多heap内存。也就是说出现 OOM现象,绝大部分原因是我们的程序设计上有问题,需要优化。优化方法很多,比如通过时间换空间,不停的加载要用的的图片,不停的回收不用的图片,把大图片解析成适合手机屏幕大小的图片等。
  • 保证了android的稳定性。 android上的app使用独立虚拟机,每开一个应用就会打开至少一个独立的虚拟机。这样可以避免虚拟机崩溃导致整个系统崩溃,同时代价就是需要浪费更多的内存。

Android有GC自动回收资源,为什么还会OOM?

Android的GC垃圾回收机制会按照特定的算法回收程序不用的内存资源,避免app的内存申请约积越多,但是GC一般回收的资源是那些无主的对象内存或者软引用的资源。
ps:编程要养成习惯,不用的对象设置为null。其实更好的是,不用的图片直接recycle。因为通过设置null让GC来回收,有时候还是会来不及。

容易发生OOM的场景及处理方案

  • 网络下载大量图片;
    处理方案:多线程异步网络,小图直接用LRUCache+SoftRef+Sd,大图按需下载
  • 对于需要加载非常多条目信息的ListView,GridView;
    处理方案:Google官方推荐使用:“convertview+静态类viewholder”
    在adapter的getView函数里有个convertView参数,告知你是否有可重用的view对象。 如果不使用convertView的话,每次调用getView时每次都会重新创建view,这样之前的view可能还没销毁,加之不断的新建view势必会造成内存剧增,从而导致OOM
    原因1. 重用缓存convertView传递给getView()方法来避免填充不必要的视图;
    2. 使用ViewHolder模式来避免没有必要的调用findViewById;因为太多的findViewById也会影响性能。
    ps:ViewHolder类的作用:ViewHolder模式通过在getView方法返回的视图的标签(tag)中存储一个数据结构。这个数据结构包含了指向我们要绑定数据的视图的引用,从而避免每次调用getView()的时候调用findViewById();

如何避免OOM?

避免OOM需要从设计、编码、测试等多个环节综合考虑,持续监控和优化应用的内存使用情况,可以从四个方面着手,首先是减小对象的内存占用,其次是内存对象的重复利用,然后是避免对象的内存泄露,最后是内存使用策略优化

  1. 图像资源管理
    • 图片压缩适当尺寸:使用适当尺寸和质量的图像,避免加载不必要的大图。可以使用第三方库(如Glide、Picasso、Fresco)自动处理图像的压缩、大小调整和缓存;
    • 减小Bitmap对象的内存占用:
      • inSampleSize:缩放比例,在把图片载入内存之前,我们需要先计算出一个合适的缩放比例,避免不必要的大图载入;
      • decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差异。
  2. 内存泄漏预防
    • 考虑使用Application Context而不是Activity Context(避免不经意的Activity泄露);
    • 避免静态变量引用Activity或Context:静态变量的生命周期与应用相同,可能导致Activity无法被正常回收;
    • 避免在Android里面使用Enum:因为Enum的每个值都是一个对象,‌会占用额外的内存,‌并且在运行时会有较大的开销,‌可能导致应用的内存占用增加和DEX文件大小的增大;
    • 避免在onDraw方法里面执行对象的创建:类似onDraw这种频繁调用的方法,一定需要注意避免在这里做创建对象的操作,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动;
    • 监听器和回调的管理:注册的监听器和回调应在不再需要时及时取消注册;
    • Fragment和View的生命周期管理:确保在onDestroy或相应的生命周期方法中清理资源;
    • 合理释放资源、销毁对象:在不再需要的时候要及时释放一些资源或销毁对象,尤其是IO流、Bitmap对象、WebView、数据库连接等;
    • 谨慎使用static对象:因为static的生命周期过长,和应用的进程保持一致,使用不当很可能导致对象泄漏,在Android中应该谨慎使用static对象;
  3. 优化布局与视图
    • 减少布局层级:扁平化布局,减少嵌套,可以有效降低内存占用,提高渲染效率;
    • 避免过度绘制:使用工具检测并消除不必要的重叠绘制区域;
  4. 内存监控与分析
    • 使用Profiler工具:定期使用Android Studio的Profiler工具检查内存使用情况,定位内存泄漏;
    • LeakCanary:在开发过程中要注意避免内存泄漏,尤其是长生命周期的对象持有了对Activity或Context的引用,导致Activity无法正常被回收。可以使用工具(如LeakCanary)来帮助检测内存泄漏问题;
  5. 数据与缓存策略
    • 使用更加轻量的数据结构:比如,我们可以考虑使用ArrayMap或SparseArray而不是使用HashMap等传统数据结构。因为ArrayMap是基于数组实现的,‌而HashMap则是基于哈希表实现的,在存储少量数据时,‌ArrayMap占用的内存相对较少,‌因为它通过两个数组来存储键和值,‌而HashMap则需要维护一个哈希表和链表,‌这增加了其内存开销。SparseArray更加高效在于他们避免了对key与value的autobox自动装箱,并且避免了装箱后的解箱;
    • StringBuilder:在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”;
    • 合理使用缓存:对于一些频繁访问的数据,可以使用LruCache来进行内存缓存管理,避免重复的创建和销毁,提高内存利用率(Android自带的Least Recently Used缓存策略);
    • 分页加载:对于大量数据,采用分页加载,避免一次性加载所有数据到内存中;
  6. 内存对象的复用
    • 复用系统自带的资源:Android系统本身内置了很多的资源,例如字符串/颜色/图片/动画/样式以及简单布局等等,这些资源都可以在应用程序中直接引用。这样做不仅仅可以减少应用程序的自身负重,减小APK的大小,另外还可以一定程度上减少内存的开销,复用性更好;
    • 复用View组件:如使用RecyclerView替代ListView,利用ViewHolder模式减少重复创建View对象;
  7. 多进程与服务优化
    • 合理使用多进程:对于内存消耗大的服务或模块,考虑运行在独立进程中,但需注意进程间通信的开销;
    • 服务生命周期管理:后台服务应及时停止,避免无谓的内存占用;
  8. VM(虚拟机)堆大小调整
    • 谨慎调整heap size:虽然可以通过android:largeHeap="true"申请更大堆空间,但这不是根本解决方案,且可能导致其他应用或系统性能问题。

借鉴于:https://www.runoob.com/w3cnote/android-oom.html

  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是阿超

现在二师兄的肉比师父的都贵了.

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值