Moosphan / Android-Daily-Interview

:pushpin:每工作日更新一道 Android 面试题,小聚成河,大聚成江,共勉之~

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2019-04-02:谈谈Android中内存优化的方式?

Moosphan opened this issue · comments

2019-04-02:谈谈Android中内存优化的方式?
commented

关于内存泄漏,一般像单例模式的使用不当啊、集合的操作不当啊、资源的缺乏有效的回收机制啊、Handler、线程的使用不当等等都有可能引发内存泄漏。

  1. 单例模式引发的内存泄漏:
    原因:单例模式里的静态实例持有对象的引用,导致对象无法被回收,常见为持有Activity的引用
    优化:改为持有Application的引用,或者不持有使用的时候传递。
  2. 集合操作不当引发的内存泄漏:
    原因:集合只增不减
    优化:有对应的删除或卸载操作
  3. 线程的操作不当引发的内存泄漏:
    原因:线程持有对象的引用在后台执行,与对象的生命周期不一致
    优化:静态实例+弱引用(WeakReference)方式,使其生命周期一致
  4. 匿名内部类/非静态内部类操作不当引发的内存泄漏:
    原因:内部类持有对象引用,导致无法释放,比如各种回调
    优化:保持生命周期一致,改为静态实例+对象的弱引用方式(WeakReference)
  5. 常用的资源未关闭回收引发的内存泄漏:
    原因:BroadcastReceiver,File,Cursor,IO流,Bitmap等资源使用未关闭
    优化:使用后有对应的关闭和卸载机制
  6. Handler使用不当造成的内存泄漏:
    原因:Handler持有Activity的引用,其发送的Message中持有Handler的引用,当队列处理Message的时间过长会导致Handler无法被回收
    优化:静态实例+弱引用(WeakReference)方式
    内存溢出:
    原因:
    1.内存泄漏长时间的积累
    2.业务操作使用超大内存
    优化:
    1.调整图像大小后再放入内存、及时回收
    2.不要过多的创建静态变量

总结为一张图:

企业微信截图_3522bb02-32a8-4682-acfb-5758cae79088

真正遇到过的内存溢出,是有一次查表,总共有几百M的数据被我一次性查出来,拿到这些数据后进行了一个StringBuffer的拼接,才0.5M就OOM了。后来处理方式就是每次拿10条数据库里面的东西,用完就=null。

内存优化,上面的都将的差不多了,那我说一下不一样的
其实性能优化的大多数方案都会侧面的有 内存优化的功能
比如:apk包体积优化,就会减少应用所需系统内存,还有 界面优化(层级减少、界面尽量不刷新或者局部刷新)也会优化内存,

看了大家的回答,感觉都讲得很不错。我这里说几个大家在开发中关于内存优化的几个细节。

  • 循环中尽可能不要创建较大的对象,列入bitmap。这会引起内存抖动。
  • 自定义View避免在onDraw中创建对象,因为自定义Viewon的onDraw方法会被频繁调用。创建对象,甚至较大的对象,会导致内存增加,甚至内存抖动。
  • cursor和流的及时关闭,特别是异常处理。一定要写finally里关闭流。
  • 避免静态内部类的引用。比如A类中有静态变量B。这是只要B被应用,就会导致A也不能回收。
  • 图片尽量使用软引用。较大的图片可以通过bitmapFactory缩放后再使用。并及时recycler.另外加载较大的图片尽可能不要使用setImageResourse,BitmapFactory.decordeResource和setImageBitmap方法。这些方法返回的是Bitmap对象。占用内存较大。可以使用BitmapFactory.decodeStream配合BitampFactory.Options对图片进行缩放,然后显示。

...
其实关于内存的优化还有好多,欢迎大家补充。

2.集合操作不当引发的内存泄漏
(1)对于HashMap,不使用动态改变hashCode的作为key对象
(2)对于HashSet,可以重写对象的equal和hashCode方法,增减特定的key的hashCode,使得每一个对象唯一
(3)如果发现remove之后,集合的数目没有改变,对于HashMap可以进行SetKey排查

commented

由于JVM有垃圾回收机制,因此,java内存要注意的问题相对c语音就大幅减少。主要可分为内存泄漏和内存溢出。

1,内存泄漏,本质是:生命周期长的对象持有了生命周期短的对象的引用。
常见场景:
1)资源对象未关闭导致内存泄漏,例如File、Cursor
2)静态类强引用,例如:Handler、webview、广播注册
2,内存溢出,本质是:APP内存超过了系统的上限
常见场景:
1)创建了超大文件,例如:大的图片一次性加载、text文件一次性加载
2)申请内存的速度超出了gc的速度,例如:递归没有终止、循环创建大量对象(线程)、大量的内存泄漏累积

内存优化,针对这两种情况进行注意就可以了,定期对APP进行内存检查即可。
当然,还有一种内存的兜底方案:
实现低内存状态回调,当系统内存不足,调用api时,执行自动重启APP、杀掉推送、关闭后台service等等操作

  • 避免静态内部类的引用。比如A类中有静态变量B。这是只要B被应用,就会导致A也不能回收。
    没明白你的意思,难道要用非静态内部类避免内存泄漏
commented
  • 避免静态内部类的引用。比如A类中有静态变量B。这是只要B被应用,就会导致A也不能回收。
    没明白你的意思,难道要用非静态内部类避免内存泄漏

A类中有静态变量B,B被引用。并不会导致A类对应的对象不能回收。。
例如:
class A{
public static B mB = new B();
}

使用:
A a = new A();
这个a和mB之间没有引用的关系。

你使用的时候也是:A.mB而不是a.mB

  • 少用枚举类型
  • 优先使用Parcelable而不是Serializable
  • 在onDraw方法中不要创建对象
  • 给RecyclerView的item设置点击事件时使用同一个listener对象
commented

为什么这么多说软引用的,谷歌都不推荐 https://blog.csdn.net/zhangphil/article/details/80634204, 安卓开发中也不存在必须要用的软引用的地方,要么就硬引用,要么就被释放,不需要模棱两可

导致内存泄漏的原因只要有以下几种
1单例静态实例使用对象的引用
例:单例静态实例中使用了Activity context
解决方法:单例静态实例中不使用Activity context 而是使用application context
2资源末回收
例:数据库 文件读取流末关闭
解决方法:及时关闭数据库 文件读取流末关闭
3注册事件末注销
例:广播事件 EventBus注册后 无使用不注销掉
解决方法:无使用注销掉注册事件
4线程池的对象引用在后台执行
5匿名内部类/非静态内部类操作不当
例:在Activity中创建一个内部类handle 因为在发送消息后 handle的message持有Activity的对象引用 而message又放在消息队列中等待轮询处理 Activity可能退出后handle可能还末处理或者正在处理 这样就会导致Activity无法被回收 导致内存泄漏
解决方法:使用静态内部类+弱引用
6集合只增不减
例:当一个对象放入到ArrayList集合中 那这个集合就持有该对象的引用 当我们不需要这个对象时如果没有将它从集合中移除 这样只要这个集合还在这个对象就已经造成内存泄漏
解决方法:无使用的集合对象 从集合remove,或者clear集合,以避免内存泄漏。

为什么这么多说软引用的,谷歌都不推荐 https://blog.csdn.net/zhangphil/article/details/80634204, 安卓开发中也不存在必须要用的软引用的地方,要么就硬引用,要么就被释放,不需要模棱两可

文章已经不在了