第1部分:Android是如何管理内存的
Android并没有提供内存的交换区(Swap space),但是它有使用 paging 与 memory-mapping(mmapping) 的机制来管理内存。这意味着任何你修改的内存(无论是通过分配新的对象还是访问到mmaped pages的内容)都会贮存在RAM中,而且不能被paged out。因此唯一完整释放内存的方法是释放那些你可能hold住的对象的引用,这样使得它能够被GC回收。只有一种例外是:如果系统想要在其他地方reuse这个对象。
第2部分:你的应用该如何管理内存
1)珍惜Services资源
如果你的app需要在后台使用service,除非它被触发执行一个任务,否则其他时候都应该是非运行状态。同样需要注意当这个service已经完成任务后停止service失败引起的泄漏。
当你启动一个service,系统会倾向为了这个Service而一直保留它的Process。这使得process的运行代价很高,因为系统没有办法把Service所占用的RAM让给其他组件或者被paged out。这减少了系统能够存放到LRU缓存当中的process数量,它会影响app之间的切换效率。它甚至会导致系统内存使用不稳定,从而无法继续hold住所有目前正在运行的Service。
限制你的Service的最好办法是使用IntentService, 它会在处理完扔给它的intent任务之后尽快结束自己。更多信息,请阅读Running in a Background Service.
最糟糕的错误之一。因此千万不要贪婪的使得一个Service持续保留。不仅仅是因为它会使得你的app因RAM的限制而性能糟糕,而且用户会发现那些有着常驻后台行为的app并且卸载它。
2)当你的UI隐藏时释放内存
当用户切换到其它app并且你的app UI不再可见时,你应该释放你的UI上占用的任何资源。在这个时候释放UI资源可以显著的增加系统cached process的能力,它会对用户体验有着直接的影响,因为虽然你的应用在后台,但是此时你的应用并不会被系统给回收掉,因为你在后台时间,把该回收的全部回收了,并没有在后台占用太多东西。
在OnStop()被调用的时间可以取消Http请求、unregister一些广播、cancel一些子线程任务等等。
在 onTrimMemory 的回调参数为 TRIM_MEMORY_UI_HIDDEN:内存不足,并且该进程的UI已经不可见了,在这个条件判断下,你才要释放你界面上的UI资源,把bitmap回收等等。
如果你在onStop时间就处理UI资源,会造成用户再次进入这个界面需要较长时间恢复。
3)当内存紧张时释放部分内存
在你的app生命周期的任何阶段,onTrimMemory回调方法同样可以告诉你整个设备的内存资源已经开始紧张。你应该根据onTrimMemory方法中的内存级别来进一步决定释放哪些资源。
- TRIM_MEMORY_RUNNING_MODERATE:你的app正在运行并且不会被列为可杀死的。但是设备此时正运行于低内存状态下,系统开始触发杀死LRU Cache中的Process的机制。
- TRIM_MEMORY_RUNNING_LOW:你的app正在运行且没有被列为可杀死的。但是设备正运行于更低内存的状态下,你应该释放不用的资源用来提升系统性能(但是这也会直接影响到你的app的性能)。
- TRIM_MEMORY_RUNNING_CRITICAL:你的app仍在运行,但是系统已经把LRU Cache中的大多数进程都已经杀死,因此你应该立即释放所有非必须的资源。如果系统不能回收到足够的RAM数量,系统将会清除所有的LRU缓存中的进程,并且开始杀死那些之前被认为不应该杀死的进程,例如那个包含了一个运行态Service的进程。
同样,当你的app进程正在被cached时,你可能会接受到从onTrimMemory()中返回的下面的值之一:
- TRIM_MEMORY_BACKGROUND: 系统正运行于低内存状态并且你的进程正处于LRU缓存名单中最不容易杀掉的位置。尽管你的app进程并不是处于被杀掉的高危险状态,系统可能已经开始杀掉LRU缓存中的其他进程了。你应该释放那些容易恢复的资源,以便于你的进程可以保留下来,这样当用户回退到你的app的时候才能够迅速恢复。
- TRIM_MEMORY_MODERATE: 系统正运行于低内存状态并且你的进程已经已经接近LRU名单的中部位置。如果系统开始变得更加内存紧张,你的进程是有可能被杀死的。
- TRIM_MEMORY_COMPLETE: 系统正运行与低内存的状态并且你的进程正处于LRU名单中最容易被杀掉的位置。你应该释放任何不影响你的app恢复状态的资源。
因为onTrimMemory()的回调是在API 14才被加进来的,对于老的版本,你可以使用onLowMemory)回调来进行兼容。onLowMemory相当与TRIM_MEMORY_COMPLETE。
Note:
4)检查你应该使用多少的内存
正如前面提到的,每一个Android设备都会有不同的RAM总大小与可用空间,因此不同设备为app提供了不同大小的heap限制。你可以通过调用getMemoryClass())来获取你的app的可用heap大小。如果你的app尝试申请更多的内存,会出现OutOfMemory的错误。
在一些特殊的情景下,你可以通过在manifest的application标签下添加largeHeap=true的属性来声明一个更大的heap空间。如果你这样做,你可以通过getLargeMemoryClass())来获取到一个更大的heap size。
然而,能够获取更大heap的设计本意是为了一小部分会消耗大量RAM的应用(例如一个大图片的编辑应用)。不要轻易的因为你需要使用大量的内存而去请求一个大的heap size。只有当你清楚的知道哪里会使用大量的内存并且为什么这些内存必须被保留时才去使用large heap. 因此请尽量少使用large heap。使用额外的内存会影响系统整体的用户体验,并且会使得GC的每次运行时间更长。在任务切换时,系统的性能会变得大打折扣。
另外, large heap并不一定能够获取到更大的heap。在某些有严格限制的机器上,large heap的大小和通常的heap size是一样的。因此即使你申请了large heap,你还是应该通过执行getMemoryClass()来检查实际获取到的heap大小。
5)避免bitmaps的浪费
当你加载一个bitmap时,仅仅需要保留适配当前屏幕设备分辨率的数据即可,如果原图高于你的设备分辨率,需要做缩小的动作。请记住,增加bitmap的尺寸会对内存呈现出2次方的增加,因为X与Y都在增加。在现有的框架中,我们设定了ImageView的长、宽为dip,其实已经根据不同的分辨率做了不同程度的压缩。
Note:在Android 2.3.x (API level 10)及其以下, bitmap对象的pixel data是存放在native内存中的,它不便于调试。然而,从Android 3.0(API level 11)开始,bitmap pixel data是分配在你的app的Dalvik heap中, 这提升了GC的工作效率并且更加容易Debug。因此如果你的app使用bitmap并在旧的机器上引发了一些内存问题,切换到3.0以上的机器上进行Debug。
6)使用优化的数据容器
利用Android Framework里面优化过的容器类,例如 SparseArray, SparseBooleanArray, 与 LongSparseArray。 通常的HashMap的实现方式更加消耗内存,因为它需要一个额外的实例对象来记录Mapping操作。另外,SparseArray更加高效在于他们避免了对key与value的autobox自动装箱,并且避免了装箱后的解箱。
7)请注意内存开销
对你所使用的语言与库的成本与开销有所了解,从开始到结束,在设计你的app时谨记这些信息。通常,表面上看起来无关痛痒(innocuous)的事情也许实际上会导致大量的开销。例如:
- Enums的内存消耗通常是static constants的2倍。你应该尽量避免在Android上使用enums。
- 在Java中的每一个类(包括匿名内部类)都会使用大概500 bytes。
- 每一个类的实例花销是12-16 bytes。
- 往HashMap添加一个entry需要额一个额外占用的32 bytes的entry对象。
8)请注意代码“抽象”
通常,开发者使用抽象作为"好的编程实践",因为抽象能够提升代码的灵活性与可维护性。然而,抽象会导致一个显著的开销:通常他们需要同等量的代码用于可执行。那些代码会被map到内存中。因此如果你的抽象没有显著的提升效率,应该尽量避免他们。
9)避免使用依赖注入框架
使用Guice和RoboGuice可以有效减少代码的数量,但是那些框架会扫描你的代码里面的注解进行大量初始化的操作,这会导致你的代码需要大量的RAM来map代码。这些被map的代码会有内存中占用很长一段时间。
10)使用ProGuard来剔除不需要的代码
11)对最终的APK使用zipalign
12)使用多进程
如果合适的话,有一个更高级的技术可以帮助你的app管理内存使用:通过把你的app组件切分成多个组件,运行在不同的进程中。这个技术必须谨慎使用,大多数app都不应该运行在多个进程中。因为如果使用不当,它会显著增加内存的使用,而不是减少(因为多进程之间的封闭性,所以相同的资源会存在两份供调用)。当你的app需要在后台运行与前台一样的大量的任务的时候,可以考虑使用这个技术。