前言html
本文翻译自Android开发者官网的一篇文档,主要用于介绍管理应用内存方面的知识。java
中国版官网原文地址为:https://developer.android.google.cn/topic/performance/memory。android
路径为:Android Developers > Docs > 指南 > Best practies > Performance > Manager Your App`s Memorygit
正文github
随机可存取内存(RAM)在任何软件开发环境中都是有价值的资源,可是在移动操做系统中是更加有价值的,由于在移动操做系统中物理内存常常是受到限制的。虽然Android Runtime(ART)和Dalvik虚拟机执行常规的垃圾回收,但这并不意味着您能够忽略应用于什么时候何处分配和释放内存。您仍然须要避免引入内存泄漏以及在合适的时间释放全部由生命周期回调定义的引用对象,这些内存泄漏常常是由在静态成员变量中持有对象引用引发的。编程
本页阐述了您能够如何积极主动地减小应用的内存使用。关于Android操做系统如何管理内存的信息,请查阅【Android内存管理概述】。缓存
监视可用内存和内存使用数据结构
在您能够修复应用中内存使用问题以前,您首先应该找到它们。Android Studio中的【Memory Profiler】能够经过以下的方式帮您找到并诊断内存问题:app
1. 查看随着时间的推移应用是如何分配内存的。【Memory Profiler】显示了一份实时图像,该图像包含了应用正在使用多少内存,被分配的Java对象数量,以及何时发生垃圾收集。框架
2. 当应用运行时,发起垃圾收集事件而且抓取Java堆快照。
3. 记录应用的内存分配,而后检查全部分配的对象,查看每个分配的栈追踪,而且跳转到Android Studio编辑器中相应的代码处。
在事件响应中释放内存
正如【Android内存管理概述】中所描述的,Android会以多种方式从应用中收回内存,或者若是有必要的话,为了极重要的任务甚至会杀死整个应用。为了进一步地帮助平衡系统内存以及避免系统杀死您应用进程的需求,您能够在Activity类中实现ComponentCallbacks2接口。提供的onTrimMemory()回调方法容许应用不管在前台仍是后台都能监听内存相关的事件,而后释放对象以响应应用生命周期或者指示须要收回内存的系统事件。
例如,您能够实现onTrimMemory()回调来响应不一样的内存相关事件,正以下面所展现的:
1 import android.content.ComponentCallbacks2; 2 // Other import statements ... 3 4 public class MainActivity extends AppCompatActivity 5 implements ComponentCallbacks2 { 6 7 // Other activity code ... 8 9 /** 10 * 当UI隐藏或者系统资源变得不足时,释放内存. 11 * @param level 引起的内存相关事件. 12 */ 13 public void onTrimMemory(int level) { 14 15 // 判断引起了哪一个生命周期或系统事件. 16 switch (level) { 17 18 case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: 19 20 /* 21 释放全部当前持有内存的UI对象. 22 23 用户接口已经转移到了后台。 24 */ 25 26 break; 27 28 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: 29 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: 30 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: 31 32 /* 33 释放全部您的应用再也不须要运行的内存。 34 35 当应用正在运行时,设备内存不足。 36 被引起的事件指示了内存相关事件的严重性。 37 若是引起的事件是TRIM_MEMORY_RUNNING_CRITICAL, 而后系统将会开始杀死背景进程。*/ 38 39 break; 40 41 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: 42 case ComponentCallbacks2.TRIM_MEMORY_MODERATE: 43 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: 44 45 /* 46 进程尽量多地释放内存。 47 48 应用在LRU(译者注:Least Recently Used,最近最少使用)列表中,而且系统内存不足。 49 该引起的事件指示了应用在LRU列表中的位置。 50 若是该事件是TRIM_MEMORY_COMPLETE,该进程将会成为首先被终止的进程之一。*/ 51 52 break; 53 54 default: 55 /* 56 释放全部不是特别重要的数据结构。 57 58 应用收到了一个来自系统的不被识别的内存等级值。把它当成是一个通常的低内存消息。*/ 59 break; 60 } 61 } 62 }
onTrimMemory()回调方法是在Android4.0(API等级为14)中新增的。对于更早的版本,您可使用onLowMemory(),它大体等同于TRIM_MEMORY_COMPLETE事件。
检查您应该使用多少内存
为了容许多个进程同时运行,Android为每个应用的堆大小分配设置了一个硬性限制。确切的堆大小限制因设备整体可用RAM的多少不一样而不一样。若是应用已经达到了堆的容量,而且试图分配更多内存,系统就会抛出OutOfMemoryError。
为了不运行时内存溢出,您能够查询系统来肯定在当前设备上有多少堆空间可使用。您能够经过调用getMemoryInfo()查询系统的这份数据。该方法会返回一个ActivityManager.MemoryInfo对象,该对象会提供关于设备当前的内存状态,包括可用内存,总内存,以及内存阈值——系统开始杀死进程的内存水平。ActivityManager.MemoryInfo对象也提供了一个简单的boolean型变量lowMemory,它会告诉您是否设备内存不足。
下面的代码片断显示了一个示例,用于展现如何在应用中使用getMemoryInfo()方法。
1 public void doSomethingMemoryIntensive() { 2 3 // 在作须要申请大量内存的事情以前,检查看看是否设备处于内存不足状态。 4 ActivityManager.MemoryInfo memoryInfo = getAvailableMemory(); 5 6 if (!memoryInfo.lowMemory) { 7 // 处理内存密集型的工做 ... 8 } 9 } 10 11 // 为设备当前的内存状态获取MemoryInfo对象. 12 private ActivityManager.MemoryInfo getAvailableMemory() { 13 ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE); 14 ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); 15 activityManager.getMemoryInfo(memoryInfo); 16 return memoryInfo; 17 }
使用更节省内存的代码结构
一些Android特性,Java类,以及代码结果趋向于比其它的使用更多内存。您能够经过在代码中选择更高效的替代者来最小化应用使用的内存数量。
谨慎使用service
当不须要使用service时仍让它保持运行是Android应用可能犯的最严重的内存管理错误之一。若是应用须要service在后台执行工做,不要保持它一直运行,除非须要运行一项做业。当service完成任务后,记得中止它。不然,您可能在不经意间引入内存泄漏。
当您启动一个service,系统偏向于始终保持该service的进程运行。这个行为让service进程消耗很大,由于被service使用的RAM不能被其它进程使用。这减小了系统保留在LRU缓存的缓存进程的数量,使得应用的切换变得更加低效。当内存紧张而且系统不会维持足够的进程来承载全部正在运行的service时,这甚至可能致使系统震荡。
通常来讲,您应该避免使用持久性的service,由于它们对可用内存的持续需求。相反,咱们建议您使用一个替代的实现方案,好比【JobScheduler】。关于怎样使用【JobScheduler】来调度后台进程的信息,请查【后台优化】。
若是您必须使用service,限制service寿命最好的办法就是使用【IntentService】,当处理完启动它的intent,它会尽快结束本身。更多的信息,请查阅【运行后台service】。
使用优化过的数据容器
有一些由编程语言提供的类在移动设备上的使用没有被优化。例如,通常的HashMap实现可能内存效率很低,由于每个映射都须要一个单独的入口对象。
Android框架包含了若干优化后的数据容器,包括SparseArray,SparseBooleanArray,以及LongSparseArray。例如,SparseArray类更有效,由于它们避免了系统对键或值装箱(译者注:原始类型到对象类的自动转换,如int到Integer)的需求(这会为每一个条目建立一个或两个对象)。
若是有必要,您能够始终切换到原始队列来得到真正精简的数据结构。
注意代码抽象
开发者常用抽象简单地做为一个良好的编程实践,由于抽象能够改善代码灵活性和可维护性。但是,抽象带来了显著的代价:一般它们须要跟多的代码来执行,须要更多时间和更多RAM来将这些代码映射到内存。因此,若是您的抽象没有提供显著的好处,您应该避免它们。
为序列化数据使用纳米协议缓存
【协议缓存】是一个由Google为序列化结构化数据设计的语言无关,平台无关,可继承的机制——相似于XML,可是更小,更快,更简单。若是您决定为您的数据使用协议缓存,您应该始终在客户端代码中使用纳米协议缓存。常规的协议缓存生成了极其冗余的代码,它们可能致使应用中各类类型的问题,好比RAM使用的增加,显著的APK大小增加,以及执行更慢。
更多信息,请查阅【protobuf readme】中“Nano 版本”部分。
避免内存搅动
正如前面提到的,垃圾收集事件通常不会影响应用的性能。可是,许多很短期内发生的垃圾收集事件可能很快占用您的帧时间。系统花费在垃圾收集上的时间越多,那么花费在处理渲染或者流式音频等事件上的时间就越少。
一般,内存搅动会致使大量的垃圾收集事件发生。实际上,内存搅动描述了发生在给定的时间内分配的临时对象的数量。
例如,您可能在for循环中分配多个临时对象。或者您可能在一个视图的onDraw()方法中建立新的Paint或者Bitmap对象。在这两种状况下,应用迅速以高容量建立了许多对象。这些可能快速地消耗全部“年轻代”中可用的内存,强制发生垃圾收集事件。
固然,在您能够修复它们以前,您须要在代码中找到这些内存搅动高地方。为了实现这些,您应用使用Android Studio中的【Memory Profiler】。
一旦您确认了您代码中发生问题的区域,就尝试下降性能重要区域内的分配数量。考虑将事物移出内部循环,或者可能把它们移动到一个基于分配结构的【工厂】。
移除内存密集型的资源和库
您代码中的一些资源和库可能在您绝不知情的状况下吞噬掉内存。您的APK的整体大小,包括第三方库或者嵌入的资源,可能影响到您应用消耗了多少内存。您能够经过从您代码中移除冗余的、没必要要的、臃肿的组件、资源、或者库来改善应用内存消耗。
减小整体APK大小
经过减小应用的整体大小,您能够显著地下降应用的内存使用。Bitmap大小、资源、动画帧、以及第三方库都有可能影响APK的大小。Android Studio和Android SDK提供了多种工具来帮您缩小资源的大小和外部依赖。
更多关于如何下降总体应用大小的信息,请查阅【缩减应用大小】。
为依赖注入使用Dagger2
依赖注入框架能够简化您写的代码,而且提供一个能够适应的环境,该环境对测试和其它配置的改变是有用的。
若是您意图在应用中使用依赖注入框架,考虑使用【Dagger2】。Dagger没有使用反射来扫描应用的代码。Dagger的静态编译时实现意味着它能够应用于Android应用,而不用在乎没必要要的运行时消耗或者内存使用。
其它的使用反射的依赖注入框架趋向于经过扫描注解代码来初始化进程。该进程可能须要明显多的CPU周期和RAM,而且当应用启动时可能致使引入注目的滞后。
谨慎使用外部库
外部库代码常常不是为移动环境编写的,而且在移动客户端使用时多是无效的。当您决定使用外部库时,您可能须要为移动设备优化那个库。在全然决定使用它前,为当前的工做作好计划,而且依据代码大小和RAM足迹分析这个库。
即便是一些为移动优化库也可能致使问题,由于不一样的实现方式。例如,一个库可能使用纳米协议缓存,然而其它应用却使用微型协议缓存,这致使了在您应用中有两种不一样的协议缓存实现。这可能发生于不一样的日志、分析、图片加载框架、缓存以及您没法预料的许多其余事情的实现方式中。
虽然【ProGuard】能够帮您移除具备正确标记的API和资源,但它没法移除库中大型的内部依赖项。这些库中您想要的特性可能须要较低级别的依赖项。当您使用库中Activity子类时(它将趋向于拥有普遍的依赖链),当库使用反射时(这是很广泛的而且意味着您须要花费不少时间手动调整ProGuard来让它工做),等等,这将变得尤其有问题。
同时避免为了几十个特性中的一两个而使用共享库。您不想导入大量的您甚至不使用的代码和开销。当您考虑是否使用库时,寻找一种和您所需的高度匹配的实现方案。否者,您可能要决定建立您本身的实现方式。
结语
本文最大限度保持原文的意思,因为笔者水平有限,如有翻译不许确或不稳当的地方,请指正,谢谢!