APP的优化是任重而道远的过程,必须在意每一个环节,否者当你想要优化的时候,发现到处都是坑,已经不知道填补哪里了,所以我们必须一点一滴的做起。
OOM
(OutOfMemoryError)内存溢出错误,在常见的Crash疑难排行榜上,OOM绝对可以名列前茅并且经久不衰。因为它发生时的Crash堆栈信息往往不是导致问题的根本原因,而只是压死骆驼的最后一根稻草。
OOM原因分类
1.堆栈溢出,堆内、虚拟机栈内对象内存分配不够空间,导致的内存溢出。
2.当对象申请需要连续内存空间时,内存从无法分配连续的内存空间。
3.超过进程已经打开的fd允许的最大数量(一般遇到较少)
4.创建的线程超过最大可申请的线程。
5.虚拟内存不足(一般遇到较少)
总结:
怎样避免OOM和内存优化呢,避免出现以上5条的原因特别注意1,4条。这是我们可控的。合理使用强软弱3种引用。一定要熟悉生命周期,才能协助你更好的关内生命周期内的对象内存分配和回收。
内存抖动
频繁的GC会导致内存抖动,根本原因:短时间内有大量对象创建销毁
如何避免内存抖动?举3个栗子
避免在循环中创建对象
避免在频繁调用的方法中创建对象,如View的onDraw方法;
允许复用的情况下,使用对象池进行缓存,如:Handler的Message单链表(obtain);
常用的内存调优分析命令:(了解,主要通过工具了解较多)
1. dumpsys meminfo 适用场景: 查看进程的oom adj,或者dalvik/native等区域内存情况,或者某个进程或apk的内存情况,功能非常强大;
2. procrank 适用场景: 查看进程的VSS/RSS/PSS/USS各个内存指标;
3. cat /proc/meminfo 适用场景: 查看系统的详尽内存信息,包含内核情况;
4. free 适用场景: 只查看系统的可用内存;
5. showmap 适用场景: 查看进程的虚拟地址空间的内存分配情况;
6. vmstat 适用场景: 查看内存情况,还可以查看进程运行队列、系统切换、CPU时间占比等情况,另外该指令还是周期性地动态输出。
Android内存泄漏分析工具
MAT
基于Eclipse的内存分析工具
Android Studio Memory-profiler
https://developer.android.com/studio/profile/memory-profiler#performance
LeakCanary
https://github.com/square/leakcanary
我们也可以使用Androidstudio提供的profiler 的memory和cpu进行内存和CPU检测
Android内存泄漏常见场景以及解决方案
1、资源性对象未关闭
对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。例如Bitmap等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。
2、注册对象未注销
例如BraodcastReceiver、EventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。
3、类的静态变量持有大数据对象
尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。
4、单例造成的内存泄漏
优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封装,然后,在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。)(长生命周期对象引用 持有 短生命周期对象引用)
5、非静态内部类的静态实例
该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的内存资源不能正常回收。此时,我们可以将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,尽量使用Application Context,如果需要使用Activity Context,就记得用完后置空让GC可以回收,否则还是会内存泄漏。
6、Handler临时性内存泄漏
Message发出之后存储在MessageQueue中,在Message中存在一个target,它是Handler的一个引用,Message在Queue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。并且消息队列是在一个Looper线程中不断地轮询处理消息,当这个Activity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message持有Handler实例的引用,Handler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。解决方案如下所示:
(1)、使用一个静态Handler内部类,然后对Handler持有的对象(一般是Activity)使用弱引用,这样在回收时,也可以回收Handler持有的对象。
(2)、在Activity的Destroy或者Stop时,应该移除消息队列中的消息,避免Looper线程的消息队列中有待处理的消息需要处理。需要注意的是,AsyncTask内部也是Handler机制,同样存在内存泄漏风险,但其一般是临时性的。对于类似AsyncTask或是线程造成的内存泄漏,我们也可以将AsyncTask和Runnable类独立出来或者使用静态内部类。
7、容器中的对象没清理造成的内存泄漏
在退出程序之前,将集合里的东西clear,然后置为null,再退出程序
8、WebView
WebView都存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。我们可以为WebView开启一个独立的进程,使用AIDL与应用的主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,达到正常释放内存的目的。
9、使用ListView时造成的内存泄漏
在构造Adapter时,使用缓存的convertView。