Android内存优化

Android内存优化之OOM

如下为正文:html

Android的内存优化是性能优化中很重要的一部分,而避免OOM又是内存优化中比较核心的一点。这是一篇关于内存优化中如何避免OOM的总结性概要文章,内容大多都是和OOM有关的实践总结概要。理解错误或是误差的地方,还请多包涵指正,谢谢!java

(一)Android的内存管理机制

Google在Android的官网上有这样一篇文章,初步介绍了Android是如何管理应用的进程与内存分配:http://developer.android.com/training/articles/memory.html。 Android系统的Dalvik虚拟机扮演了常规的内存垃圾自动回收的角色,Android系统没有为内存提供交换区,它使用 paging与 memory-mapping(mmapping)的机制来管理内存,下面简要概述一些Android系统中重要的内存管理基础概念。android

1)共享内存

Android系统经过下面几种方式来实现共享内存:git

 

  • Android应用的进程都是从一个叫作Zygote的进程fork出来的。Zygote进程在系统启动,并载入通用的framework的代码与资源以后开始启动。为了启动一个新的程序进程,系统会fork Zygote进程生成一个新的进程,而后在新的进程中加载并运行应用程序的代码。这就使得大多数的RAM pages被用来分配给framework的代码,同时促使RAM资源可以在应用的全部进程之间进行共享。github

  • 大多数static的数据被mmapped到一个进程中。这不只仅让一样的数据可以在进程间进行共享,并且使得它可以在须要的时候被paged out。常见的static数据包括Dalvik Code、app resources、so文件等。算法

  • 大多数状况下,Android经过显式的分配共享内存区域(例如ashmem或gralloc)来实现动态RAM区域可以在不一样进程之间进行共享的机制。好比,Window Surface在App与Screen Compositor之间使用共享的内存,Cursor Buffers在Content Provider与Clients之间共享内存。数据库

 

2)分配与回收内存

 

  • 每个进程的Dalvik Heap都反映了使用内存的占用范围。这就是一般逻辑意义上提到的Dalvik Heap Size,它能够随着须要进行增加,可是增加行为会有一个系统为它设定上限。编程

  • 逻辑上讲的Heap Size和实际物理意义上使用的内存大小是不对等的,Proportional Set Size(PSS)记录了应用程序自身占用以及与其余进程进行共享的内存。缓存

  • Android系统并不会对Heap中空闲内存区域作碎片整理。系统仅仅会在新的内存分配以前判断Heap的尾端剩余空间是否足够,若是空间不够会触发GC操做,从而腾出更多空闲的内存空间。在Android的高级系统版本里面针对Heap空间有一个Generational Heap Memory的模型,最近分配的对象会存放在Young Generation区域。当这个对象在该区域停留的时间达到必定程度,它会被移动到Old Generation,最后累积必定时间再移动到Permanent Generation区域。系统会根据内存中不一样的内存数据类型分别执行不一样的GC操做。例如,刚分配到Young Generation区域的对象一般更容易被销毁回收,同时在Young Generation区域的GC操做速度会比Old Generation区域的GC操做速度更快(如图1所示)。性能优化

 

 

图1  根据不一样内存数据类型执行不一样GC操做

每个Generation的内存区域都有固定的大小。随着新的对象陆续被分配到此区域,当对象总的大小临近这一级别内存区域的阀值时,会触发GC操做,以便腾出空间来存放其余新的对象(如图2所示)。

 

图2  对象值临近阀值触发GC操做

一般状况下,GC发生的时候,全部的线程都是会被暂停的。执行GC所占用的时间和它发生在哪个Generation也有关系,Young Generation中的每次GC操做时间是最短的,Old Generation其次,Permanent Generation最长。执行时间的长短也和当前Generation中的对象数量有关,遍历树结构查找20000个对象比起遍历50个对象天然是要慢不少的。

3)限制应用的内存

 

  • 为了整个系统的内存控制须要,Android系统为每个应用程序都设置一个硬性的Dalvik Heap Size最大限制阈值,这个阈值在不一样的设备上会由于RAM大小不一样而各有差别。若是你的应用占用内存空间已经接近这个阈值,此时再尝试分配内存的话,很容易引起OutOfMemoryError错误。

  • ActivityManager.getMemoryClass()能够用来查询当前应用的Heap Size阈值,这个方法会返回一个整数,代表应用的Heap Size阈值是多少MB(Megabates)。

 

4)应用切换操做

 

  • Android系统并不会在用户切换应用的时候执行交换内存操做。Android会把那些不包含Foreground组件的应用进程放到LRU Cache中。例如,当用户开始启动一个应用时,系统会为它建立一个进程。可是当用户离开此应用,进程不会当即被销毁,而是被放到系统的Cache当中。若是用户后来再切换回到这个应用,此进程就可以被立刻完整地恢复,从而实现应用的快速切换。

  • 若是你的应用中有一个被缓存的进程,这个进程会占用必定的内存空间,它会对系统的总体性能有影响。所以,当系统开始进入Low Memory的状态时,它会由系统根据LRU的规则与应用的优先级,内存占用状况以及其余因素的影响综合评估以后决定是否被杀掉。

  • 对于那些非foreground的进程,Android系统是如何判断Kill掉哪些进程的问题,请参考Processes and Threads

(二)OOM(Out Of Memory)

前面咱们提到过使用getMemoryClass()的方法能够获得Dalvik Heap的阈值。简要地获取某个应用的内存占用状况能够参考下面的示例(更多内存查看的知识,能够参考Google官方教程: Investigating Your RAM Usage

1)查看内存使用状况

经过命令行查看内存详细占用状况,如图3所示。

 

图3  命令行查看内存详细占用状况

经过Android Studio的Memory Monitor查看内存中Dalvik Heap的实时变化,如图四、五、6所示。

图4  Memory Monitor查看内存中Dalvik Heap的实时变化(一)

图5  Memory Monitor查看内存中Dalvik Heap的实时变化(二)

图6  Memory Monitor查看内存中Dalvik Heap的实时变化(三)

2)发生OOM的条件

关于Native Heap、Dalvik Heap、PSS等内存管理机制比较复杂,这里就不展开详细描述。简单的说,经过不一样的内存分配方式(malloc/mmap/JNIEnv/etc)对不一样的对象(Bitmap/etc)进行操做,会由于Android系统版本的差别而产生不一样的行为,对Native Heap与Dalvik Heap以及OOM的判断条件都会有所影响。在2.x的系统上,咱们经常能够看到Heap Size的total值,明显超过了经过getMemoryClass()获取到的阈值而不会发生OOM的状况。那么,针对2.x与4.x的Android系统,到底如何判断会发生OOM呢?

 

  • Android 2.x系统GC LOG中的dalvik allocated + external allocated + 新分配的大小 >= getMemoryClass()值的时候就会发生OOM。 例如,假设有这么一段Dalvik输出的GC LOG:GC_FOR_MALLOC free 2K, 13% free 32586K/37455K, external 8989K/10356K, paused 20ms,那么32586+8989+(新分配23975)=65550>64M时,就会发生OOM。

  • Android 4.x的系统废除了external的计数器,相似Bitmap的分配改到Dalvik的Java Heap中申请。只要allocated + 新分配的内存 >= getMemoryClass()的时候就会发生OOM,如图7所示(注:虽然图示演示的是ART运行环境,可是统计规则仍是和Dalvik保持一致)。

 

 

图7

(三)如何避免OOM总结

前面介绍了一些基础的内存管理机制以及OOM的基础知识,那么在实践操做当中,有哪些指导性的规则能够参考呢?概括下来,能够从四个方面着手,首先是减少对象的内存占用,其次是内存对象的重复利用,而后是避免对象的内存泄露,最后是内存使用策略优化。

减少对象的内存占用

避免OOM的第一步就是要尽可能减小新分配出来的对象占用内存的大小,尽可能使用更加轻量的对象。

1)使用更加轻量的数据结构

例如,咱们能够考虑使用ArrayMap/SparseArray而不是HashMap等传统数据结构。图8演示了HashMap的简要工做原理,相比起Android专门为移动操做系统编写的ArrayMap容器,在大多数状况下,都显示效率低下,更占内存。一般的HashMap的实现方式更加消耗内存,由于它须要一个额外的实例对象来记录Mapping操做。另外,SparseArray更加高效,在于他们避免了对key与value的自动装箱(autoboxing),而且避免了装箱后的解箱。

 

图8  HashMap简要工做原理

关于更多ArrayMap/SparseArray的讨论,请参考《 Android性能优化典范(三)》的前三个段落。

2)避免在Android里面使用Enum

Android官方培训课程提到过“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具体原理请参考《Android性能优化典范(三)》,因此请避免在Android里面使用到枚举。

3)减少Bitmap对象的内存占用

Bitmap是一个极容易消耗内存的大胖子,减少建立出来的Bitmap的内存占用可谓是重中之重,一般来讲有如下2个措施:

 

  • inSampleSize:缩放比例,在把图片载入内存以前,咱们须要先计算出一个合适的缩放比例,避免没必要要的大图载入。

  • decode format:解码格式,选择ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差别。

 

4)使用更小的图片

在涉及给到资源图片时,咱们须要特别留意这张图片是否存在能够压缩的空间,是否可使用更小的图片。尽可能使用更小的图片不只能够减小内存的使用,还能避免出现大量的InflationException。假设有一张很大的图片被XML文件直接引用,颇有可能在初始化视图时会由于内存不足而发生InflationException,这个问题的根本缘由实际上是发生了OOM。

内存对象的重复利用

大多数对象的复用,最终实施的方案都是利用对象池技术,要么是在编写代码时显式地在程序里建立对象池,而后处理好复用的实现逻辑。要么就是利用系统框架既有的某些复用特性,减小对象的重复建立,从而下降内存的分配与回收(如图9所示)。

 

图9  对象池技术

在Android上面最经常使用的一个缓存算法是LRU(Least Recently Use),简要操做原理如图10所示。

 

图10  LRU简要操做原理

1)复用系统自带的资源

Android系统自己内置了不少的资源,好比字符串、颜色、图片、动画、样式以及简单布局等,这些资源均可以在应用程序中直接引用。这样作不只能减小应用程序的自身负重,减少APK的大小,还能够在必定程度上减小内存的开销,复用性更好。可是也有必要留意Android系统的版本差别性,对那些不一样系统版本上表现存在很大差别、不符合需求的状况,仍是须要应用程序自身内置进去。

2)注意在ListView/GridView等出现大量重复子组件的视图里对ConvertView的复用,如图11所示。

 

图11

3)Bitmap对象的复用

在ListView与GridView等显示大量图片的控件里,须要使用LRU的机制来缓存处理好的Bitmap,如图12所示。

 

图12

 

  • 利用inBitmap的高级特性提升Android系统在Bitmap分配与释放执行效率(注:3.0以及4.4之后存在一些使用限制上的差别)。使用inBitmap属性能够告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用以前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存从新申请一块区域来存放Bitmap。利用这种特性,即便是上千张的图片,也只会仅仅只须要占用屏幕所可以显示的图片数量的内存大小,如图13所示。

 

 

图13  利用inBitmap的高级特性提升Android在Bitmap分配与释放执行效率

使用inBitmap须要注意几个限制条件:

 

  • 在SDK 11 -> 18之间,重用的Bitmap大小必须是一致的。例如给inBitmap赋值的图片大小为100-100,那么新申请的Bitmap必须也为100-100才可以被重用。从SDK 19开始,新申请的Bitmap大小必须小于或者等于已经赋值过的Bitmap大小。

  • 新申请的Bitmap与旧的Bitmap必须有相同的解码格式。例如你们都是8888的,若是前面的Bitmap是8888,那么就不能支持4444与565格式的Bitmap了。咱们能够建立一个包含多种典型可重用Bitmap的对象池,这样后续的Bitmap建立都可以找到合适的“模板”去进行重用,如图14所示。

 

 

图14

另外,在2.x的系统上,尽管Bitmap是分配在Native层,但仍是没法避免被计算到OOM的引用计数器里。这里提示一下,很多应用会经过反射vBitmapFactory.Options里面的inNativeAlloc来达到扩大使用内存的目的,可是若是你们都这么作,对系统总体会形成必定的负面影响,建议谨慎采纳。

4)避免在onDraw方法里面执行对象的建立

相似onDraw等频繁调用的方法,必定须要注意避免在这里作建立对象的操做,由于他会迅速增长内存的使用,并且很容易引发频繁的gc,甚至是内存抖动。

5)StringBuilder

在有些时候,代码中会须要使用到大量的字符串拼接的操做,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。

避免对象的内存泄露

内存对象的泄漏,会致使一些再也不使用的对象没法及时释放,这样一方面占用了宝贵的内存空间,很容易致使后续须要分配内存的时候,空闲空间不足而出现OOM。显然,这还使得每级Generation的内存区域可用空间变小,GC就会更容易被触发,容易出现内存抖动,从而引发性能问题(如图15所示)。

 

图15

最新的LeakCanary开源控件,能够很好的帮助咱们发现内存泄露的状况,更多关于LeakCanary的介绍,请看 这里( 中文使用说明)。另外也可使用传统的MAT工具查找内存泄露,请参考 这里( 便捷的中文资料)。

1)注意Activity的泄漏

一般来讲,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广,咱们须要特别注意如下两种状况致使的Activity泄漏:

 

  • 内部类引用致使Activity的泄漏

 

最典型的场景是Handler致使的Activity泄漏,若是Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能由于Handler继续执行而致使Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,能够在UI退出以前,执行remove Handler消息队列中的消息与runnable对象。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。

 

  • Activity Context被传递到其余实例中,这可能致使自身被引用而发生泄漏。

 

内部类引发的泄漏不只仅会发生在Activity上,其余任何内部类出现的地方,都须要特别留意!咱们能够考虑尽可能使用static类型的内部类,同时使用WeakReference的机制来避免由于互相引用而出现的泄露。

2)考虑使用Application Context而不是Activity Context

对于大部分非必须使用Activity Context的状况(Dialog的Context就必须是Activity Context),咱们均可以考虑使用Application Context而不是Activity的Context,这样能够避免不经意的Activity泄露。

3)注意临时Bitmap对象的及时回收

虽然在大多数状况下,咱们会对Bitmap增长缓存机制,可是在某些时候,部分Bitmap是须要及时回收的。例如临时建立的某个相对比较大的bitmap对象,在通过变换获得新的bitmap对象以后,应该尽快回收原始的bitmap,这样可以更快释放原始bitmap所占用的空间。

须要特别留意的是Bitmap类里面提供的createBitmap()方法,如图16所示:

 

图16  createBitmap()方法 

这个函数返回的bitmap有可能和source bitmap是同一个,在回收的时候,须要特别检查source bitmap与return bitmap的引用是否相同,只有在不等的状况下,才可以执行source bitmap的recycle方法。

4)注意监听器的注销

在Android程序里面存在不少须要register与unregister的监听器,咱们须要确保在合适的时候及时unregister那些监听器。本身手动add的listener,须要记得及时remove这个listener。

5)注意缓存容器中的对象泄漏

有时候,咱们为了提升对象的复用性把某些对象放到缓存容器中,但是若是这些对象没有及时从容器中清除,也是有可能致使内存泄漏的。例如,针对2.3的系统,若是把drawable添加到缓存容器,由于drawable与View的强应用,很容易致使activity发生泄漏。而从4.0开始,就不存在这个问题。解决这个问题,须要对2.3系统上的缓存drawable作特殊封装,处理引用解绑的问题,避免泄漏的状况。

6)注意WebView的泄漏

Android中的WebView存在很大的兼容性问题,不只仅是Android系统版本的不一样对WebView产生很大的差别,另外不一样的厂商出货的ROM里面WebView也存在着很大的差别。更严重的是标准的WebView存在内存泄露的问题,请看 这里。因此一般根治这个问题的办法是为WebView开启另一个进程,经过AIDL与主进程进行通讯,WebView所在的进程能够根据业务的须要选择合适的时机进行销毁,从而达到内存的完整释放。

7)注意Cursor对象是否及时关闭

在程序中咱们常常会进行查询数据库的操做,但时常会存在不当心使用Cursor以后没有及时关闭的状况。这些Cursor的泄露,反复屡次出现的话会对内存管理产生很大的负面影响,咱们须要谨记对Cursor对象的及时关闭。

内存使用策略优化

1)谨慎使用large heap

正如前面提到的,Android设备根据硬件与软件的设置差别而存在不一样大小的内存空间,他们为应用程序设置了不一样大小的Heap限制阈值。你能够经过调用getMemoryClass()来获取应用的可用Heap大小。在一些特殊的情景下,你能够经过在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大小。

2)综合考虑设备内存阈值与其余因素设计合适的缓存大小

例如,在设计ListView或者GridView的Bitmap LRU缓存的时候,须要考虑的点有:

 

  • 应用程序剩下了多少可用的内存空间?

  • 有多少图片会被一次呈现到屏幕上?有多少图片须要事先缓存好以便快速滑动时可以当即显示到屏幕?

  • 设备的屏幕大小与密度是多少? 一个xhdpi的设备会比hdpi须要一个更大的Cache来hold住一样数量的图片。

  • 不一样的页面针对Bitmap的设计的尺寸与配置是什么,大概会花费多少内存?

  • 页面图片被访问的频率?是否存在其中的一部分比其余的图片具备更高的访问频繁?若是是,也许你想要保存那些最常访问的到内存中,或者为不一样组别的位图(按访问频率分组)设置多个LruCache容器。

 

3)onLowMemory()与onTrimMemory()

Android用户能够随意在不一样的应用之间进行快速切换。为了让background的应用可以迅速的切换到forground,每个background的应用都会占用必定的内存。Android系统会根据当前的系统的内存使用状况,决定回收部分background的应用内存。若是background的应用从暂停状态直接被恢复到forground,可以得到较快的恢复体验,若是background应用是从Kill的状态进行恢复,相比之下就显得稍微有点慢,如图17所示。

 

图17  从Kill状态进行恢复体验更慢 

 

  • onLowMemory():Android系统提供了一些回调来通知当前应用的内存使用状况,一般来讲,当全部的background应用都被kill掉的时候,forground应用会收到onLowMemory()的回调。在这种状况下,须要尽快释放当前应用的非必须的内存资源,从而确保系统可以继续稳定运行。

  • onTrimMemory(int):Android系统从4.0开始还提供了onTrimMemory()的回调,当系统内存达到某些条件的时候,全部正在运行的应用都会收到这个回调,同时在这个回调里面会传递如下的参数,表明不一样的内存使用状况,收到onTrimMemory()回调的时候,须要根据传递的参数类型进行判断,合理的选择释放自身的一些内存占用,一方面能够提升系统的总体运行流畅度,另外也能够避免本身被系统判断为优先须要杀掉的应用。

  • TRIM_MEMORY_UI_HIDDEN:你的应用程序的全部UI界面被隐藏了,即用户点击了Home键或者Back键退出应用,致使应用的UI界面彻底不可见。这个时候应该释放一些不可见的时候非必须的资源

 

当程序正在前台运行的时候,可能会接收到从onTrimMemory()中返回的下面的值之一:

 

  • TRIM_MEMORY_RUNNING_MODERATE:你的应用正在运行而且不会被列为可杀死的。可是设备此时正运行于低内存状态下,系统开始触发杀死LRU Cache中的Process的机制。

  • TRIM_MEMORY_RUNNING_LOW:你的应用正在运行且没有被列为可杀死的。可是设备正运行于更低内存的状态下,你应该释放不用的资源用来提高系统性能。

  • TRIM_MEMORY_RUNNING_CRITICAL:你的应用仍在运行,可是系统已经把LRU Cache中的大多数进程都已经杀死,所以你应该当即释放全部非必须的资源。若是系统不能回收到足够的RAM数量,系统将会清除全部的LRU缓存中的进程,而且开始杀死那些以前被认为不该该杀死的进程,例如那个包含了一个运行态Service的进程。

 

当应用进程退到后台正在被Cached的时候,可能会接收到从onTrimMemory()中返回的下面的值之一:

 

  • TRIM_MEMORY_BACKGROUND: 系统正运行于低内存状态而且你的进程正处于LRU缓存名单中最不容易杀掉的位置。尽管你的应用进程并非处于被杀掉的高危险状态,系统可能已经开始杀掉LRU缓存中的其余进程了。你应该释放那些容易恢复的资源,以便于你的进程能够保留下来,这样当用户回退到你的应用的时候才可以迅速恢复。

  • TRIM_MEMORY_MODERATE: 系统正运行于低内存状态而且你的进程已经已经接近LRU名单的中部位置。若是系统开始变得更加内存紧张,你的进程是有可能被杀死的。

  • TRIM_MEMORY_COMPLETE: 系统正运行于低内存的状态而且你的进程正处于LRU名单中最容易被杀掉的位置。你应该释听任何不影响你的应用恢复状态的资源。

 

 

由于onTrimMemory()的回调是在API 14才被加进来的,对于老的版本,你可使用onLowMemory)回调来进行兼容。onLowMemory至关与TRIM_MEMORY_COMPLETE。

请注意:当系统开始清除LRU缓存中的进程时,虽然它首先按照LRU的顺序来执行操做,可是它一样会考虑进程的内存使用量以及其余因素。占用越少的进程越容易被留下来。

4)资源文件须要选择合适的文件夹进行存放

咱们知道hdpi/xhdpi/xxhdpi等等不一样dpi的文件夹下的图片在不一样的设备上会通过scale的处理。例如咱们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。须要注意到在这种状况下,内存占用是会显著提升的。对于不但愿被拉伸的图片,须要放到assets或者nodpi的目录下。

5)Try catch某些大内存分配的操做

在某些状况下,咱们须要事先评估那些可能发生OOM的代码,对于这些可能发生OOM的代码,加入catch机制,能够考虑在catch里面尝试一次降级的内存分配操做。例如decode bitmap的时候,catch到OOM,能够尝试把采样比例再增长一倍以后,再次尝试decode。

6)谨慎使用static对象

由于static的生命周期过长,和应用的进程保持一致,使用不当极可能致使对象泄漏,在Android中应该谨慎使用static对象(如图19所示)。

 

图19

7)特别留意单例对象中不合理的持有

虽然单例模式简单实用,提供了不少便利性,可是由于单例的生命周期和应用保持一致,使用不合理很容易出现持有对象的泄漏。

8)珍惜Services资源

若是你的应用须要在后台使用service,除非它被触发并执行一个任务,不然其余时候Service都应该是中止状态。另外须要注意当这个service完成任务以后由于中止service失败而引发的内存泄漏。 当你启动一个Service,系统会倾向为了保留这个Service而一直保留Service所在的进程。这使得进程的运行代价很高,由于系统没有办法把Service所占用的RAM空间腾出来让给其余组件,另外Service还不能被Paged out。这减小了系统可以存放到LRU缓存当中的进程数量,它会影响应用之间的切换效率,甚至会致使系统内存使用不稳定,从而没法继续保持住全部目前正在运行的service。 建议使用IntentService,它会在处理完交代给它的任务以后尽快结束本身。更多信息,请阅读 Running in a Background Service

9)优化布局层次,减小内存消耗

越扁平化的视图布局,占用的内存就越少,效率越高。咱们须要尽可能保证布局足够扁平化,当使用系统提供的View没法实现足够扁平的时候考虑使用自定义View来达到目的。

10)谨慎使用“抽象”编程

不少时候,开发者会使用抽象类做为”好的编程实践”,由于抽象可以提高代码的灵活性与可维护性。然而,抽象会致使一个显著的额外内存开销:他们须要同等量的代码用于可执行,那些代码会被mapping到内存中,所以若是你的抽象没有显著的提高效率,应该尽可能避免他们。

11)使用nano protobufs序列化数据

Protocol buffers是由Google为序列化结构数据而设计的,一种语言无关,平台无关,具备良好的扩展性。相似XML,却比XML更加轻量,快速,简单。若是你须要为你的数据实现序列化与协议化,建议使用nano protobufs。关于更多细节,请参考 protobuf readme的”Nano version”章节。

12)谨慎使用依赖注入框架

使用相似Guice或者RoboGuice等框架注入代码,在某种程度上能够简化你的代码。图20是使用RoboGuice先后的对比图:

 

图20  使用RoboGuice先后对比图 

使用RoboGuice以后,代码是简化了很多。然而,那些注入框架会经过扫描你的代码执行许多初始化的操做,这会致使你的代码须要大量的内存空间来mapping代码,并且mapped pages会长时间的被保留在内存中。除非真的颇有必要,建议谨慎使用这种技术。

13)谨慎使用多进程

使用多进程能够把应用中的部分组件运行在单独的进程当中,这样能够扩大应用的内存占用范围,可是这个技术必须谨慎使用,绝大多数应用都不该该贸然使用多进程,一方面是由于使用多进程会使得代码逻辑更加复杂,另外若是使用不当,它可能反而会致使显著增长内存。当你的应用须要运行一个常驻后台的任务,并且这个任务并不轻量,能够考虑使用这个技术。

一个典型的例子是建立一个能够长时间后台播放的Music Player。若是整个应用都运行在一个进程中,当后台播放的时候,前台的那些UI资源也没有办法获得释放。相似这样的应用能够切分红2个进程:一个用来操做UI,另一个给后台的Service。

14)使用ProGuard来剔除不须要的代码

ProGuard可以经过移除不须要的代码,重命名类,域与方法等等对代码进行压缩,优化与混淆。使用ProGuard可使得你的代码更加紧凑,这样可以减小mapping代码所须要的内存空间。

15)谨慎使用第三方libraries

不少开源的library代码都不是为移动网络环境而编写的,若是运用在移动设备上,并不必定适合。即便是针对Android而设计的library,也须要特别谨慎,特别是在你不知道引入的library具体作了什么事情的时候。例如,其中一个library使用的是nano protobufs, 而另一个使用的是micro protobufs。这样一来,在你的应用里面就有2种protobuf的实现方式。这样相似的冲突还可能发生在输出日志,加载图片,缓存等等模块里面。另外不要为了1个或者2个功能而导入整个library,若是没有一个合适的库与你的需求相吻合,你应该考虑本身去实现,而不是导入一个大而全的解决方案。

16)考虑不一样的实现方式来优化内存占用

在某些状况下,设计的某个方案可以快速实现需求,可是这个方案却可能在内存占用上表现的效率不够好。例如:

 

图21

对于上面这样一个时钟表盘的实现,最简单的就是使用不少张包含指针的表盘图片,使用帧动画实现指针的旋转。可是若是把指针扣出来,单独进行旋转绘制,显然比载入N多张图片占用的内存要少不少。固然这样作,代码复杂度上会有所增长,这里就须要在优化内存占用与实现简易度之间进行权衡了。

总结

 

  • 设计风格很大程度上会影响到程序的内存与性能,相对来讲,若是大量使用相似Material Design的风格,不只安装包能够变小,还能够减小内存的占用,渲染性能与加载性能都会有必定的提高。

  • 内存优化并不就是说程序占用的内存越少就越好,若是由于想要保持更低的内存占用,而频繁触发执行gc操做,在某种程度上反而会致使应用性能总体有所降低,这里须要综合考虑作必定的权衡。

  • Android的内存优化涉及的知识面还有不少:内存管理的细节,垃圾回收的工做原理,如何查找内存泄漏等等均可以展开讲不少。OOM是内存优化当中比较突出的一点,尽可能减小OOM的几率对内存优化有着很大的意义。

相关文章
相关标签/搜索