管理应用内存

随机存取存储器(RAM)在任何软件开发环境中都是宝贵的资源,在物理内存受限的移动操作系统上更是如此。尽管Android运行时(ART)和Dalvik虚拟机均执行常规垃圾回收,但这并不意味着您可以忽略掉应用在何时何地分配和释放内存。您仍然需要避免引入内存泄露,这通常是由于在静态成员变量中保留对象引用而引起的,并且应在生命周期回调定义的适当时间释放任何Reference对象。

本页说明如何主动减少应用程序内的内存使用量。有关Android操作系统如何管理内存的信息,参见Android内存管理概述

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

在解决应用程序中的内存使用问题之前是定位问题。Android Studio的Memory Profiler可以通过以下方式帮助你找到和诊断内存问题:

  1. 查看您的应用如何随着时间分配内存。Memory Profiler实时显示您的应用程序正在使用多少内存,分配的Java对象的数量以及如何进行垃圾回收。
  2. 启动垃圾回收事件,并在应用运行时获取Java堆的内存快照。
  3. 记录应用程序的内存分配,然后检查所有分配的对象,查看每个分配的堆栈跟踪,然后跳转到Android Studio编辑器中的相应代码。

响应事件释放内存

Android内存管理概述所述,Android可以通过多种方式从您的应用程序中回收内存,或者在必要时完全杀死掉您的应用程序以释放关键任务的内存。为了进一步帮助平衡系统内存并避免系统需要终止您的应用程序,可以在Activity类中实现ComponentCallback2接口。提供的onTrimMemory()回调方法允许您的应用程序处于前台或者后台时侦听与内存相关的事件,然后释放对象以响应应用程序声明周期或指示系统需要回收内存的系统事件。

例如,您可以实现onTrimMemory()回调来响应不同的内存相关的事件:

KOTLIN

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

class MainActivity : AppCompatActivity(), 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.
     */
    override fun onTrimMemory(level: Int) {

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

            ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN -> {
                /*
                   Release any UI objects that currently hold memory.

                   The user interface has moved to the background.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,
            ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,
            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.
                */
            }

            ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
            ComponentCallbacks2.TRIM_MEMORY_MODERATE,
            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.
                */
            }

            else -> {
                /*
                  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.
                */
            }
        }
    }
}

JAVA

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 level 14) 中被添加。对于更早的版本,你可以使用onLowMemory(), 这个基本等同于TRIM_MEMORY_COMPLETE事件。

检查你应该使用的内存量

为了允许多个正在运行的进程,Android对分配给每个应用程序的堆大小设置了硬限制。确切的堆大小限制在设备之间会有所不同,具体取决于设备总体上有多少可用RAM。如果您的应用程序已经达到堆容量并尝试分配更多的内存,则系统将引发OutofMemoryError

为了避免内存不足,您可以查询系统以确定当前设备上有多少堆空间。您可以通过调用getMemoryInfo()在系统中查询该数据。这将返回一个ActivityManager.MemoryInfo对象,该对象提供有关设备当前内存状态的信息,包括可用内存,总内存和内存阈值(系统开始杀死进程的内存级别)。ActivityManager.MemoryInfo对象还提供一个简单的布尔变量lowMemory,它告诉您对象是否内存不足。以下的代码显示了如何在应用程序中使用getMemoryInfo()方法的示例。

KOTLIN

fun doSomethingMemoryIntensive() {

    // Before doing something that requires a lot of memory,
    // check to see whether the device is in a low memory state.
    if (!getAvailableMemory().lowMemory) {
        // Do memory intensive work ...
    }
}

// Get a MemoryInfo object for the device's current memory status.
private fun getAvailableMemory(): ActivityManager.MemoryInfo {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    return ActivityManager.MemoryInfo().also { memoryInfo ->
        activityManager.getMemoryInfo(memoryInfo)
    }
}

JAVA

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;
}

使用更高效的代码结构

某些Android功能,Java类和代码构造往往比其他功能使用更多的内存。通过在代码中选择更有效的替代方案,可以最大程度地减少应用程序使用的内存量。

谨慎使用服务

在不需要的时候保持服务运行时Android应用程序可能犯的最严重的内存管理错误之一。如果您的应用程序需要一项服务来在后台执行工作,请不要使其保持运行状态,除非它需要运行一项工作。切记在完成任务后停止服务。否则,可能会无意间导致内存泄露。

启动服务时,系统倾向于始终保持该服务的进程运行。此行为使服务进程非常昂贵,因为服务使用的RAM对于其他进程仍然不可用。者减少了系统可以保留在LRU缓存中的缓存进程的数量,从而降低了应用程序切换的效率。当内存不足并且系统无法维护足够的进程来承载正在运行的所有服务时,甚至可能导致系统崩溃。

通常,应避免使用持久性服务,因为它们对可用内存的持续需求。相反,我们建议你使用类似于JobScheduler这些替代来实现。有关如何使用JobScheduler安排后台进程的更多信息,请参阅后台优化

如果你必须使用服务,则限制服务寿命的最佳方法是使用IntentService,该服务会在处理启动它的意图后立即完成。有关更多信息,请阅读在后台服务中运行

使用优化的数据容器

编程语言提供的某些类未针对在移动设备上进行优化。例如,通用的HashMap实现可能在内存方面效率很低,因为他需要为每个映射使用单独的入口对象。

Android框架包括几个优化的数据容器,包括SparseArraySparseBooleanArrayLoingSparseArray。例如,SparseArray类效率更高,因为他们避免了系统自动对键(有时设置对值)进行装箱(每个条目又创建一个或两个对象)的需求。

如有必要,您始终可以切换到原始阵列以获得真正精简的数据结构。

谨慎对待代码抽象

开发人员通常只是将抽象用作良好的编程习惯,因为抽象可以提高代码的灵活性和维护性。 但是,抽象要付出巨大的代价:通常,抽象需要大量的代码才能执行,需要更多的时间和更多的RAM才能将该代码映射到内存中。 因此,如果您的抽象不能带来很大的好处,则应避免使用它们。

对于序列化数据使用轻度protobuf

Protocol Buffer是Google设计的一种与语言无关,与平台无关的可拓展机制,用于序列化结构化数据(类似于XML,但更小,更快,更简单)。如果决定对数据使用protobuf,则应始终在客户端代码中使用lite protobuf。常规的protobuf会生成极其冗长的代码,这可能会在您的应用程序中引起许多问题,例如增加的RAM使用量,显著的APK大小增加以及执行速度降低。

有关更多信息,可以参加protobuf readme 的 “Lite Version”部分。

避免内存混乱

如前所述,垃圾回收事件通常不会影响您应用的性能。但是,在很短时间内发生多次垃圾回收会耗尽帧时间。系统花费在垃圾回收上的时间越长,执行诸如渲染或流音频之类的其他事情所花费的时间就越少。

通常,内存流失会导致发生大量垃圾回收事件。实际上,内存流失描述了在给定的时间内发生的已分配临时对象的数量。

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

当然,您需要在代码中找到内存流失率较高的地方,然后才能对其进行修复。为此,您应该在Android Studio中使用Memory Profiler。

在代码中确定问题区域后,将尝试减少性能关键区域内的分配数量。考虑将这些操作移出内层的循环,或者将其移入基于工厂的分配结构中。

删除占用大量内存的资源和库

代码中的一些资源或者库可能会在您不知情的情况下吞噬内存。APK的总体大小(包括第三方库或嵌入式资源)可能会影响您的应用消耗的内存量。通过从代码中删除任何多余的,不必要的或者臃肿的组件,资源或库,可以提高应用程序的内存消耗。

减少整体APK大小

通过减小应用程序的整体大小,可以大大减少应用程序的内存使用量。 位图的大小,资源,动画框架和第三方库都可以影响APK的大小。 Android Studio和Android SDK提供了多种工具来帮助您减少资源和外部依赖项的大小。 这些工具支持现代的代码缩减方法,例如R8编译。 (Android Studio 3.3及更低版本使用ProGuard而不是R8编译。)

有关如何减小APK总体大小的详细信息,请参阅有关减小应用程序大小的指南

使用Dagger 2 进行依赖注入

依赖项注入框架可以简化您编写的代码,并提供适用于测试和其他配置更改的自适应环境。

如果您打算在应用程序中使用依赖项注入框架,请考虑使用Dagger 2。Dagger不使用反射来扫描应用程序的代码。 Dagger的静态,编译时实现意味着它可以在Android应用中使用,而无需不必要的运行时成本或内存使用。

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

谨慎使用外部库

外部库代码通常不是为移动环境编写的,并且在移动客户端上使用时效率低下。当您决定使用外部库时,可能需要针对移动设备优化该库。在决定完全使用它之前,请先计划该工作,并根据代码大小和RAM占用空间分析该库。

甚至某些针对移动设备进行优化的库也可能由于实现方式不同而引起问题。例如,一个库可能使用精简协议,而另一个库则使用微型协议,从而在您的应用程序中实现两种不同的协议实现。日志记录,分析,图像加载框架,缓存以及许多您不期望的其他事情的不同实现可能会发生这种情况。

尽管ProGuard可以使用正确的标志来帮助删除API和资源,但是它不能删除库的大型内部依赖项。这些库中所需的功能可能需要较低级别的依赖性。当您使用库中的Activity子类时(这往往会具有广泛的依赖关系),而库中使用反射时(这很常见,这意味着您需要花费大量时间来手动调整ProGuard才能使它到达),这尤其成问题工作),等等。

还应避免将共享库仅用于数十种功能中的一项或两项。您不想引入甚至不使用的大量代码和开销。在考虑是否使用库时,请寻找与您的需求完全匹配的实现。否则,您可能决定创建自己的实现。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 本文由作者自行翻译,未经作者授权,不得随意转发。后续作者会陆续发布一系列关于JVM内存管理的文章,敬请期待。 1、...
    猿学堂阅读 1,301评论 0 50
  • 本文主要有以下三部分内容:第一部分:简单介绍开发者指南上内存相关的文章。第二部分:总结移动App性能评测与优化内存...
    htkeepmoving阅读 3,914评论 0 5
  • 第一章 简介 J2SE平台的一大优势是它的自动化内存管理,避免了开发者去面对内存管理的复杂性。 本文以Sun J2...
    tianyiliusha阅读 885评论 0 1
  • 走出你给我带来的冬天 让心坚强 克服自己 活出价值
    卑辰尊月阅读 165评论 1 2