一、内存常见问题
常见的Android内存相关问题,通常可以分为以下三种,内存抖动、内存泄露、内存溢出。
1、内存抖动:在短时间内有大量的对象被创建或者被回收的现象,主要是循环中大量创建、回收对象。当系统内存不足,不断GC内存的时候,也有可能出现内存抖动情况。
2、内存泄露:当一个对象不在使用了,本应该被垃圾回收器回收。但是这个对象由于被其他正在使用的对象所持有,造成无法被回收的现象。
3、内存溢出:Android系统给每个应用分配的内存也有一个阀值,也就是Heap Size。当应用占用的内存加上我们申请的内存资源超过了系统分配的最大内存时就会抛出的Out Of Memory异常。
上述三者之间的是一个递进关系,内存抖动->内存泄露->内存溢出。对于一般应用主要是处理内存抖动和内存泄露两点,处理好这两点就会大大降低内存溢出的可能性。
二、内存优化的意义
内存优化能让应用降低Crash、减少ANR和卡顿、存活久KeepAlive
1、降低Crash
导致Android应用Crash的原因有很多种,而做内存优化可以让我们的应用避免由内存问题引起的Crash。
2、减少ANR和卡顿
Android中造成界面卡顿的原因有很多种,其中一种就是由内存问题引起的。之所以会影响到界面流畅度,是因为在发生GC时,所有线程都要停止,包括主线程。当GC和绘制界面的操作同时触发时,绘制的执行就会被搁置导致掉帧,也就是界面卡顿。
3、存活久KeepAlive
存活久指的是应用在后台运行时不会被系统kill。Android系统会按照特定的机制(低杀)清理进程,清理进程时优先会考虑清理后台进程。清理进程的机制就是低杀。
三、Android系统清理进程
在说进程之前,需要了解Android系统清理进程的机制–低杀。
在Android 中有一个心狠手辣的杀手,要想让我们的应用活下来,就要在开发应用时格外小心。不过我们也不用太担心,因为它只杀“坏蛋”,只要我们不使坏,那它就不会对我们下手。这个杀手叫低杀,它的全名是Low-Memory-Killer。
低杀跟垃圾回收器GC很像,GC的作用是保证应用有足够的内存可以使用,而低杀的作用是保证系统有足够的内存可以使用。GC会按照引用的强度来回收对象,而低杀会按照进程的优先级来回收资源,在这里进程优先级就相当于是应用被用户“引用”的强度。
四、Android中有哪几种进程,是如何管理的?优先级
在Android中不同的进程有着不同的优先级,当两个进程的优先级相同时,低杀会优先考虑干掉消耗内存更多的进程。也就是如果我们应用占用的内存比其他应用少,并且处于后台时,我们的应用能在后台活下来,这也是内存优化为我们应用带来竞争力的一个直接体现。
ActivityManagerService负责根据各种策略算法计算进程的adj值,然后交由系统内核进行进程的管理。
1、前台进程
用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程:
- 托管用户正在交互的Activity(已调用Activity的onResume() 方法)
- 托管某个Service,后者绑定到用户正在交互的 Activity
- 托管正在“前台”运行的 Service(服务已调用 startForeground())
- 托管正执行一个生命周期回调的Service(onCreate()、onStart() 或 onDestroy())
- 托管正执行其onReceive()方法的BroadcastReceiver
通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。
2、可见进程
没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:
- 托管不在前台、但仍对用户可见的 Activity(已调用其 onPause()方法)。例如,如果前台Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
- 托管绑定到可见(或前台)Activity 的 Service。
可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
3、服务进程
正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。
4、后台进程
包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。
5、空进程
不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
五、常见场景及解决方案
(一) 内存抖动
主要原因:
当我们在短时间内频繁创建大量临时对象时,就会引起内存抖动,比如在一个 for 循环中创建临时对象实例。由于短时间内有大量对象进出Young Generiation区导致的,它伴随着频繁的GC。
预防方法:
- 尽量避免在循环体内创建对象,应该把对象创建移到循环体外。
- 注意自定义View的onDraw()方法会被频繁调用,所以在这里面不应该频繁的创建对象。
- 对于能够复用的对象,可以考虑使用对象池把它们缓存起来
(二) 内存泄漏
主要原因:
内存泄漏指的是,当一块内存没有被使用,但无法被GC时的情况。内存泄漏的表现就是可用内存逐渐减少,比如下图中是一种比较严重的内存泄漏现象,无法被回收的内存逐渐累积,直到无更多可用内存可申请时,最终会导致内存溢出OOM。
具体分析和解决方案:
1、static关键字修饰成员变量
2、非静态内部类/匿名类
3、资源未关闭
4、Bitmap造成内存泄漏
详细的请阅读我上一篇文章探索App性能优化之Android内存泄漏,没看过也不影响本篇文章的浏览体验。
六、总结
以上讲述了Android开发中常见的内存问题、内存优化的意义、常见场景和解决方案。场景和解决方案是一种方法论,通过方法论可以避免很多内存的问题。那么当内存问题发生时,我们就要去进行分析和定位问题,接下来研究内存调优和分析工具,敬请期待…