在app开发的各个阶段中要考虑RAM的限制问题, 包括在设计阶段(正式开发以前). 使用下面的不一样的方法能够达到很好的效果. 当您在设计和开发Android应用时用下面的方法可使内存运用最高效. html
若是你的应用须要使用 service 在后台执行业务功能, 除非是一直在进行活动的工做(好比每隔几秒向服务器端请求数据之类)不然不要让它一直保持在后台运行. 而且, 当你的 service 执行完成可是中止失败时要当心 service 致使的内存泄露问题.java
当你启动 service 时, 系统老是优先保持服务的运行. 这会致使内存应用效率很是低, 由于被该服务使用的内存不能作其它事情. 也会减小系统一直保持的LRU缓存处理数目, 使不一样的app切换效率下降. 当前全部 service 的运行会致使内存不够不能维持正常系统的运行时, 系统会发生卡顿的现象严重时能致使系统不断重启.android
最好的方式是使用 IntentSevice 控制 service 的生命周期, 当使用 intent 开始任务后, 该 service 执行完全部的工做时会自动中止.c++
在android应用中当不须要使用常驻 service 执行业务功能而去使用一个常驻 service 是最糟糕的内存管理方式之一. 因此不要贪婪的使用 service 使你的应用一直运行状态. 这样不只使你由于内存的限制提升了应用运行的风险, 也会致使用户发现这些异常行为后而卸载应用.git
当用户跳转到不一样的应用而且你的视图再也不显示时, 你应该释放应用视图所占的资源. 这时释放所占用的资源能显著的提升系统的缓存处理容量, 而且对用户的体验质量有直接的影响.github
当实现当前 Activity 类的 onTrimMemory() 回调方法后, 用户离开视图时会获得通知. 使用该方法能够监听 TRIM_MEMORY_UI_HIDDEN 级别, 当你的视图元素从父视图中处于隐藏状态时释放视图所占用的资源.shell
注意只有当你应用的全部视图元素变为隐藏状态时你的应用才能收到 onTrimMemory() 回调方法的 TRIM_MEMORY_UI_HIDDEN . 这个和 onStop() 回调方法不一样, 该方法只有当 Activity 的实例变为隐藏状态, 或者有用户移动到应用中的另外的 activity 才会引起. 因此说你虽然实现了 onStop() 去释放 activity 的资源例如网络链接或者未注册的广播接收者, 可是应该直到你收到 onTrimMemory(TRIM_MEMORY_UI_HIDDEN)才去释放视图资源不然不该该释放视图所占用的资源. 这里能够肯定的是若是用户经过后退键从另外的 activity 进入到你的应用中, 视图资源会一直处于可用的状态能够用来快速的恢复 activity.编程
在应用生命周期的任何阶段 onTrimMemory() 回调方法均可以告诉你设备的内存愈来愈低的状况, 你能够根据该方法推送的内存紧张级别来释放资源.api
应用处于运行状态而且不会被杀掉, 设备使用的内存比较低, 系统级会杀掉一些其它的缓存应用.缓存
应用处于运行状态而且不会被杀掉, 设备可使用的内存很是低, 能够把不用的资源释放一些提升性能(会直接影响程序的性能)
还有, 当你的应用被系统正缓存时, 经过 onTrimMemory() 回调方法能够收到如下几个内存级别:
系统处于低内存的运行状态中而且你的应用处于缓存应用列表的初级阶段. 虽然你的应用不会处于被杀的高风险中, 可是系统已经开始清除缓存列表中的其它应用, 因此你必须释放资源使你的应用继续存留在列表中以便用户再次回到你的应用时能快速恢复进行使用.
系统处于低内存的运行状态中而且你的应用处于缓存应用列表的中级阶段. 若是系运行内存收到限制, 你的应用有被杀掉的风险.
系统处于低内存的运行状态中若是系统如今没有内存回收你的应用将会第一个被杀掉. 你必须释放掉全部非关键的资源从而恢复应用的状态.
由于 onTrimMemory() 是在级别14的android api中加入的, 因此低版本的要使用 onLowMemory() 方法, 该方法大体至关于 TRIM_MEMORY_COMPLETE 事件.
注意: 当系统开始清除缓存应用列表中的应用时, 虽然系统的主要工做机制是自下而上, 可是也会经过杀掉消费大内存的应用从而使系统得到更多的内存, 因此在缓存应用列表中消耗更少的内存将会有更大的机会留存下来以便用户再次使用时进行快速恢复.
前面提到, 不一样的android设备系统拥有的运行内存各自都不一样, 从而不一样的应用堆内存的限制大小也不同. 你能够经过调用 ActivityManager 中的 getMemoryClass() 函数能够经过以兆为单位获取当前应用可用的内存大小, 若是你想获取超过最大限度的内存则会发生 OutOfMemoryError .
有一个特别的状况, 能够在 manifest 文件中的 <application> 标签中设置 largeHeap 属性的值为 "true"时, 当前应用就能够获取到系统分配的最大堆内存. 若是你设置了该值, 能够经过 ActivityManager 的 getLargeMemoryClass() 函数获取最大的堆内存.
而后, 只有一小部分应用须要消耗大量堆内存(好比大照片编辑应用). 历来不须要使用大量内存仅仅是由于你已经消耗了大量的内存而且必须快速修复它, 你必须使用它是由于你刚好知道全部的内存已经被分配完了而你必需要保留当前应用不会被清除掉. 甚至当你的应用须要消耗大量内存时, 你应该尽量的避免这种需求. 使用大量内存后, 当你切换不一样的应用或者执行其它相似的操做时, 由于长时间的内存回收会致使系统的性能降低从而渐渐的会损害整个系统的用户体验.
另外, 大内存不是全部的设备都相同. 当跑在有运行内存限制的设备上时, 大内存和正常的堆内存是同样的. 因此若是你须要大内存, 你就要调用 getMemoryClass() 函数查看正常的堆内存的大小而且尽量使内存使用状况维护在正常堆内存之下.
当你加载 bitmap 时, 须要根据分辨率来保持它的内存时最大为当前设备的分辨率, 若是下载下来的原图为高分辨率则要拉伸它. 要当心bitmap的分辨率增长后所占用的内存也要进行相应的增长, 由于它是根据x和y的大小来增长内存占用的.
注意: 在 Android 2.3.x(api level 10)如下, 不管图片的分辨率多大 bitmap 对象在内存中始终显示相同大小, 实际的像素数据被存储在底层 native 的内存中(c++内存). 由于内存分析工具没法跟踪 native 的内存状态全部调试 bitmap 内存分配变得很是困难. 然而, 从 Android 3.0(api level 11)开始, bitmap 对象的内存数据开始在应用程序所在Dalvik虚拟机堆内存中进行分配, 提升了回收机率和调试的可能性. 若是你在老版本中发现 bitmap 对象占用的内存大小始终同样时, 切换设备到系统3.0或以上来进行调试.
利用 Android 框架优化后的数据容器, 好比 SparseArray, SparseBooleanArray 和 LongSparseArray. 传统的 HashMap 在内存上的实现十分的低效由于它须要为 map 中每一项在内存中创建映射关系. 另外, SparseArray类很是高效由于它避免系统中须要自动封箱(autobox)的key和有些值.
在你设计应用各个阶段都要很谨慎的考虑所使用的语言和库带来的内存上的成本和开销. 一般状况下, 表面上看起来无害的会带来巨大的开销, 下面在例子说明:
使用不少的没必要要类和对象时, 增长了分析堆内存问题的复杂度.
一般来讲, 使用简单的抽象是一种好的编程习惯, 由于必定程度上的抽象能够提供代码的伸缩性和可维护性. 然而抽象会带来很是显著的开销: 须要执行更多的代码, 须要更长时间和更多的运行内存把代码映射到内存中, 因此若是抽象没有带来显著的效果就尽可能避免.
Protocol Buffers 是 Google 公司开发的一种数据描述语言,相似于XML可以将结构化数据序列化. 可是它更小, 更快, 更简单. 若是你决定使用它做为你的数据, 你必须在你的客户端代码中一直使用纳米 protocol buffer, 由于正常的 protocol buffer 会产生极其冗余的代码, 在你的应用生会引发不少问题: 增长了使用的内存, 增长了apk文件的大小, 执行速度较慢以及会快速的把一些限定符号打入 dex 包中.
使用像 Guice 和 RoboGuice 依赖注入框架会有很大的吸引力, 由于它使咱们能够写一些更简单的代码和提供自适应的环境用来进行有用的测试和进行其它配置的更改. 然而这些框架经过注解的方式扫描你的代码来执行一系列的初始化, 可是这些也会把一些咱们不须要的大量的代码映射到内存中. 被映射后的数据会被分配到干净的内存中, 放入到内存中后很长一段时间都不会使用, 这样形成了内存大量的浪费.
许多的外部依赖库每每不是在移动环境下写出来的, 这样当在移动使用中使用这些库时就会很是低效. 因此当你决定使用一个外部库时, 你就要承担为优化为移动应用外部库带来的移植问题和维护负担. 在项目计划前期就要分析该类库的受权条件, 代码量, 内存的占用再来决定是否使用该库.
甚至听说专门设计用于 android 的库也有潜在的风险, 由于每一个库作的事情都不同. 例如, 一个库可能使用的是 nano protobuf 另一个库使用的是 micro protobuf, 如今在你的应用中有两个不一样 protobuf 的实现. 这将会有不一样的日志, 分析, 图片加载框架, 缓存, 等全部你不可预知的事情的发生. Proguard 不会保存你的这些, 由于全部低级别的 api 依赖须要你依赖的库里所包含的特征. 当你使用从外部库继承的 activity 时尤为会成为一个问题(由于这每每产生大量的依赖). 库要使用反射(这是常见的由于你要花许多时间去调整ProGuard使它工做)等.
也要当心不要陷入使用几十个依赖库去实现一两个特性的陷阱; 不要引入大量不须要使用的代码. 一天结束时, 当你没有发现符合你要求的实现时, 最好的方式是建立一个属于本身的实现.
除了上述状况外, 还能够优化CPU的性能和用户界面, 也会带动内存的优化
代码混淆工具 ProGuard 经过去除没有用的代码和经过语义模糊来重命名类, 字段和方法来缩小, 优化和混淆你的代码. 使用它能使你的代码更简洁, 更少许的RAM映射页.
若是构建apk后你没有作后续的任何处理(包括根据你的证书进行签名), 你必须运行 zipalign 工具为你的apk从新签名, 若是不这样作会致使你的应用使用更多的内存, 由于像资源这样的东西不会再从apk中进行映射(mmap).
注意:goole play store 不接受没有签名的apk
使用adb shell dumpsys meminfo +包名 等工具来分析你的应用在各个生命周期的内存使用状况, 这个后续博文会有所体现.
一种更高级的技术能管理应用中的内存, 分离组件技术能把单进程内存划分为多进程内存. 该技术必定要谨慎的使用而且大多数的应用都不会跑多进程, 由于若是你操做不当反而会浪费更多的内存而不是减小内存. 它主要用于后台和前台能各自负责不一样业务的应用程序
当你构建一个音乐播放器应用而且长时间从一个 service 中播放音乐时使用多进程处理对你的应用来讲更恰当. 若是整个应用只有一个进程, 当前用户却在另一个应用或服务中控制播放时, 却为了播放音乐而运行着许多不相关的用户界面会形成许多的内存浪费. 像这样的应用能够分隔为两个进程:一个进程负责 UI 工做, 另一个则在后台服务中运行其它的工做.
在各个应用的 manifest 文件中为各个组件申明 android:process 属性就能够分隔为不一样的进程.例如你能够指定你一运行的服务从主进程中分隔成一个新的进程来并取名为"background"(固然名字能够任意取).
<service android:name=".PlaybackService"
android:process=":background" />
进程名字必须以冒号开头":"以确保该进程属于你应用中的私有进程.
在你决定建立一个新的进程以前必须理解对这样作内存的影响. 为了说明每一个进程的影响, 一个基本空进程会占用大约1.4兆的内存, 下面的堆内存信息说明这一点
adb shell dumpsys meminfo com.example.android.apis:empty
** MEMINFO in pid 10172 [com.example.android.apis:empty] **
Pss Pss Shared Private Shared Private Heap Heap Heap
Total Clean Dirty Dirty Clean Clean Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------ ------
Native Heap 0 0 0 0 0 0 1864 1800 63
Dalvik Heap 764 0 5228 316 0 0 5584 5499 85
Dalvik Other 619 0 3784 448 0 0
Stack 28 0 8 28 0 0
Other dev 4 0 12 0 0 4
.so mmap 287 0 2840 212 972 0
.apk mmap 54 0 0 0 136 0
.dex mmap 250 148 0 0 3704 148
Other mmap 8 0 8 8 20 0
Unknown 403 0 600 380 0 0
TOTAL 2417 148 12480 1392 4832 152 7448 7299 148
注意: 上面关键的数据是 private dirty 和 private clean 两项, 第一项主要使用了大约是1.4兆的非分页内存(分布在Dalvik heap, native分配, book-keeping, 和库的加载), 另外执行业务代码使用150kb的内存.
空进程的内存占用是至关显著的, 当你的应用加入了许多业务后会增加得更加迅速. 下面的例子是使用activity显示一些文字, 当前进程的内存使用情况的分析.
** MEMINFO in pid 10226 [com.example.android.helloactivity] **
Pss Pss Shared Private Shared Private Heap Heap Heap
Total Clean Dirty Dirty Clean Clean Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------ ------
Native Heap 0 0 0 0 0 0 3000 2951 48
Dalvik Heap 1074 0 4928 776 0 0 5744 5658 86
Dalvik Other 802 0 3612 664 0 0
Stack 28 0 8 28 0 0
Ashmem 6 0 16 0 0 0
Other dev 108 0 24 104 0 4
.so mmap 2166 0 2824 1828 3756 0
.apk mmap 48 0 0 0 632 0
.ttf mmap 3 0 0 0 24 0
.dex mmap 292 4 0 0 5672 4
Other mmap 10 0 8 8 68 0
Unknown 632 0 412 624 0 0
TOTAL 5169 4 11832 4032 10152 8 8744 8609 134
这个比上面多花费了3倍的内存, 只是在 界面 上显示一些简单的文字, 用了大约4兆. 从这里能够得出一个很重要的结论:若是你的想在应用中使用多进程, 只能有一个进程来负责 UI 的工做, 在其它进程中不能出现任何 UI的工做, 不然会迅速提升内存的使用率(尤为当你加载 bitmap 资源和其它资源时). 一旦加入了UI的绘制工做就不可能会减小内存的使用了.
另外, 当你的应用超过一个进程时, 保持代码的紧凑很是重要, 由于如今由相同实现形成的没必要要的内存开销会复制到每个进程中, 会形成内存浪费更严重的情况出现. 例如, 你使用了枚举, 不一样的进程在内存中都会建立和初始化这些常量.而且你全部的抽象适配器和其它临时的开销也会和前面同样被复制过来.
另外要关心的问题是多进程之间的依赖关系. 例如, 当应用中运行默认的进程须要为UI进程提供内容, 后台进程的代码为进程自己提供内容还要留在内存中为UI运行提供支持, 若是你的目标是在一个拥有重量级的UI进程的应用里拥有一个独立运行的后台进程, 那么你在UI进程中则不能直接依赖它, 而要在UI进程使用 service 处理它.