管理应用内存

本文翻译自谷歌开发者网站: https://developer.android.com/topic/performance/memory.html#remove

内存在任何软件开发环境中都是一种宝贵资源,但在物理内存通常受限制的移动操作系统上更为珍贵。 尽管Android Runtime(ART)和Dalvik虚拟机都执行常规的垃圾回收,但这并不意味着你可以忽略应用分配和释放内存的时间和位置。 你仍然需要避免引入内存泄漏,内存泄漏通常是通过保持静态成员变量中的对象引用引起的,并在生命周期回调定义的适当时间释放任何引用对象。

1. 监视可用内存和内存使用情况

Android framework 和Android Studio可以帮助分析和调整应用程序的内存使用情况。 Android框架公开了几个API,可通过它们使得应用程序在运行时可以动态地减少内存使用。 Android Studio包含几个工具,可用来分析应用程序如何使用内存。

1.1 分析内存使用的工具

在你可以修复应用程序内存使用问题之前,首先需要找到它们。 Android Studio和Android SDK包含几个用于分析应用程序内存使用情况的工具:

  1. Android Studio中的Memory Monitor会显示应用程序在单个会话过程中如何分配内存。 该工具显示可用和分配的Java内存随时间的图,包括垃圾回收事件。 还可以启动垃圾收集事件,并在应用程序运行时抓取Java堆的快照。 内存监视器工具的输出可以帮助识别出因应用程序遇到过多的垃圾收集事件从而导致应用程序运行缓慢。

  2. Android Studio中的Allocation Tracker工具可让你详细了解应用程序如何分配内存。 分配跟踪器记录应用程序的内存分配,并列出分析快照中的所有已分配对象。可以使用此工具来跟踪分配太多对象的部分代码。

1.2 释放内存以回应事件

Android设备可以运行不同的可用内存,具体取决于设备上RAM的物理量以及用户的操作方式。 系统广播信号以指示何时处于内存压力下,应用程序应监听这些信号并酌情调整其内存使用情况。
您可以使用ComponentCallbacks2 API监听这些信号,然后根据应用程序生命周期或设备事件调整内存使用情况。 onTrimMemory()方法允许您的应用程序在应用程序在前台运行(可见)以及在后台运行时侦听与内存相关的事件。
为了监听这些事件,可在你的Activity的子类中实现 onTrimMemory()的回调,如下面的代码片段所示。

import android.content.ComponentCallbacks2;
// Other import statements ...

public class MainActivity extends AppCompatActivity
    implements ComponentCallbacks2 {

    // Other activity code ...

    /** * Release memory when the UI becomes hidden or when system resources become low. * @param level the memory-related event that was raised. */
    public void onTrimMemory(int level) {

        // Determine which lifecycle or system event was raised.
        switch (level) {

            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

                /* Release any UI objects that currently hold memory. The user interface has moved to the background. */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

                /* Release any memory that your app doesn't need to run. The device is running low on memory while the app is running. The event raised indicates the severity of the memory-related event. If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will begin killing background processes. */

                break;

            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

                /* Release as much memory as the process can. The app is on the LRU list and the system is running low on memory. The event raised indicates where the app sits within the LRU list. If the event is TRIM_MEMORY_COMPLETE, the process will be one of the first to be terminated. */

                break;

            default:
                /* Release any non-critical data structures. The app received an unrecognized memory level value from the system. Treat this as a generic low-memory message. */
                break;
        }
    }
}

onTrimMemory调是在Android 4.0(API级别14)中添加的。 对于早期版本,您可以使用onLowMemory调作为旧版本的回退,大致相当于TRIM_MEMORY_COMPLETE事件。

1.3检查你应该使用多少内存

为了可以运行多个进程,Android为每个应用程序分配的堆大小设置了一个硬限制。 基于设备可用总体RAM数量,设备之间的确切的堆大小限制会有所不同。 如果你的应用程序已达到堆容量并尝试分配更多内存,系统将抛出OutOfMemoryError。

为了避免内存不足,你可以查询系统以确定当前设备上可用的堆空间。 你可以通过调用getMemoryInfo()进行查询系统的内存信息。 它将返回一个ActivityManager.MemoryInfo对象,该对象提供有关设备当前内存状态的信息,包括可用内存,总内存和内存阈值 – (内存级别,低于该级别系统将开始杀死进程)。 ActivityManager.MemoryInfo类还暴露了一个简单的boolean字段,lowMemory,告诉你设备是否内存不足。

下面的代码片段展示了一个如何在应用中使用 getMemoryInfo()这个方法的例子。

public void doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check to see whether the device is in a low memory state.
    ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();

    if (!memoryInfo.lowMemory) {
        // Do memory intensive work ...
    }
}

// Get a MemoryInfo object for the device's current memory status.
private ActivityManager.MemoryInfo getAvailableMemory() {
    ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    return memoryInfo;
}

2. 使用更多内存高效的代码构造

一些Android功能,Java类和代码结构往往比其他功能使用更多的内存。 您可以通过在代码中选择更有效的替代方案来最小化您的应用程序使用的内存量。

2.1 谨慎使用服务

在不需要时运行服务是Android应用程序可能造成的最糟糕的内存管理错误之一。 如果您的应用程序需要服务才能在后台执行工作,请勿将其运行,除非需要运行作业。 记得在完成任务后停止服务。 否则,您可能会无意中导致内存泄漏。
当您启动服务时,系统更愿意始终保持该服务的进程运行。 这种行为使服务进程非常昂贵,因为服务使用的RAM对其他进程仍然不可用。 这减少了系统可以在LRU缓存中保存的缓存进程的数量,从而使应用程序切换效率降低。 当内存紧张,系统无法维护足够的进程来承载当前正在运行的所有服务时,甚至可能导致在系统中的颠簸。

一般应避免使用persistent services ,因为它们对可用内存的持续需求。 相反,我们建议您使用其他实现,例如JobScheduler。
如果您必须使用服务,则限制服务使用寿命的最佳方法是使用IntentService,该服务将在处理启动的意图后立即完成。

2.2 使用优化过的数据容器

编程语言提供的一些类不会针对移动设备进行优化。 例如,通用的HashMap实现可能是非常低效的内存,因为它需要每个映射的单独的条目对象。

Android框架包括几个优化的数据容器,包括SparseArray,SparseBooleanArray和LongSparseArray。 例如,SparseArray类更有效率,因为它们避免了系统自动对key和一些values进行自动装箱的需要(这些值可能为每个条目创建另外一个对象或两个)。

如果需要,您可以随时切换到一个非常精简的数据结构的原始数组。

2.3 注意代码抽象

开发人员通常将抽象简单地用作良好的编程实践,因为抽象可以提高代码的灵活性和维护性。 然而,抽象成本很高:一般来说,它们需要相当多的代码,需要执行,需要更多的时间和更多的RAM才能将该代码映射到内存中。 所以如果你的抽象没有提供重大的收益,你应该避免它们。

例如,枚举通常要求的是静态常量的两倍多的内存。 您应该严格避免在Android上使用枚举。

2.4 使用nano protobufs 序列化数据

Protocol buffers是一种语言中立的,平台中立的,可扩展的机制,由Google设计,用于序列化结构化数据,类似于XML,但更小,更快,更简单。 如果您决定对您的数据使用protobufs,则应始终在客户端代码中使用nano protobufs。 定期的protobuf生成非常详细的代码,这可能会导致您的应用程序中的各种问题,如RAM使用量增加,APK大小增加,执行速度较慢。

2.5 避免内存抖动

如前所述,垃圾收集事件通常不会影响您的应用程序的性能。 然而,许多在短时间内发生的垃圾回收事件可以快速消耗您的帧时间。 系统花费在垃圾收集上的时间越多,它必须做更多的时间来做其他的东西,如渲染或流音频。

通常,内存流失可能导致大量的垃圾收集事件发生。 在实践中,内存流失描述了在给定时间内发生的分配的临时对象的数量。

例如,您可以在for循环中分配多个临时对象。 或者,您可以在视图的onDraw()函数内创建新的Paint或Bitmap对象。 在这两种情况下,应用程序可以快速创建大量的对象。 这些可以快速消耗年轻一代中的所有可用内存,从而迫使垃圾回收事件发生。

当然,您需要在代码中找到内存流失高的位置,然后再修复它们。 使用分析您的RAM使用中讨论的工具
确定代码中的问题区域后,尝试减少性能关键区域内的分配数量。 考虑将这些对象从内部循环中移出或者将其移动到基于工厂的分配结构中。

3 删除占用较多内存的资源和库

你的代码中的一些资源和库可以在不知道的情况下消除内存。 您的APK的总体尺寸(包括第三方库或嵌入式资源)可能会影响应用程序消耗多少内存。 您可以通过从代码中删除任何冗余的,不必要的或膨胀的组件,资源或库来提高应用程序的内存消耗。

3.1 减少APK整体大小

您可以通过减少应用程序的总体大小来显着降低应用程序的内存使用量。 位图大小,资源,动画框架和第三方库都可以帮助您的APK的大小。 Android Studio和Android SDK提供多种工具来帮助您减少资源和外部依赖关系的大小。

3.2 使用Dagger 2进行依赖注入

依赖注入框架可以简化您编写的代码,并提供可用于测试和其他配置更改的自适应环境。
如果您打算在应用程序中使用依赖注入框架,请考虑使用Dagger 2. Dagger不使用反射来扫描应用程序的代码。 Dagger的静态编译时实现意味着它可以在Android应用程序中使用,而无需运行时费用或内存使用。

使用反射的其他依赖注入框架倾向于通过扫描代码来注释来初始化进程。 此过程可能需要显着更多的CPU周期和RAM,并且在应用启动时可能会导致明显的滞后。

3.3 谨慎的使用外部库

外部库代码通常不是专门用于移动环境,并且它们在在移动客户端上工作时效率低下。 当您决定使用外部库时,可能需要优化移动设备的库。 根据代码大小和RAM占用空间,在决定使用它之前,先进行这个工作的计划并分析库。

即使一些移动优化的库可能由于不同的实现而导致问题。 例如,一个库可以使用nano protobufs,而另一个库可以使用micro protobufs,从而在您的应用程序中导致两种不同的protobuf实现。 这可能发生在不同的日志记录,分析,图像加载框架,缓存以及您不期望的许多其他事情上。

虽然ProGuard可以帮助使用正确的标志来删除API和资源,但它不能删除库的大型内部依赖关系。 这些库中所需的功能可能需要较低级别的依赖关系。 当您使用库中的Activity子类(这将趋向于具有宽的依赖关系)时,这会变得特别有问题,当库使用反射(这是常见的,意味着您需要花费大量的时间手动调整ProGuard才能将其转换为 工作),等等。

同时避免仅使用共享库中数十个中的一个或两个功能。 你肯定不想引入大量未使用到的代码和开销。 当考虑是否使用库时,请查找与需要的密切匹配的实现。 否则,可能会决定创建自己的实现。

    原文作者:Android
    原文地址: http://www.caiyeccy.com/android/2017/04/20/%E7%AE%A1%E7%90%86%E5%BA%94%E7%94%A8%E5%86%85%E5%AD%98
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞