优化内存使用情况

内存优化对于确保性能顺畅、防止应用崩溃以及维持系统稳定性和平台健康状况至关重要。虽然每款应用都应监控和优化内存用量,但 TV 设备的内容应用 面临的挑战与手持设备的典型 Android 应用不同。

内存消耗过高可能会导致应用和系统行为出现问题,包括:

  • 应用本身 可能会变慢或卡顿,最糟糕的情况下会被终止。
  • 用户可见的系统服务 (音量控制、图片设置信息中心、语音助理等)会变得非常卡顿,甚至可能无法正常运行。
  • **低内存终止 (LMK) 守护进程** 可能会应对内存压力过高的问题,方法是终止最不必要的进程;然后,这些组件可能会在不久后重新启动,从而触发进一步的资源争用峰值,这会直接影响前台应用。
  • 过渡到启动器 可能会显著延迟,并导致前台应用在过渡完成之前显示为无响应。
  • 系统可能会开始使用直接回收, 暂时暂停线程执行,同时等待内存分配。 任何线程(例如主线程或编解码器相关线程)都可能发生这种情况,从而可能导致音频和视频丢帧以及界面故障。

TV 设备上的内存注意事项

TV 设备的内存通常比手机或平板电脑少得多。例如,我们在 TV 上看到的一种配置是 1 GB RAM 和 1080p 视频分辨率 。同时,大多数 TV 应用都具有类似的功能;因此,实现方式类似,面临的挑战也类似。 这两种情况会带来其他设备类型和应用中未出现的问题:

  • 媒体 TV 应用通常由网格状图片视图全屏背景图片 组成,这需要在短时间内将大量图片加载到内存中
  • TV 应用播放多媒体流 ,这需要分配一定的内存来播放视频和音频,并且需要大量的媒体缓冲区来确保播放顺畅。
  • 如果实现不当,额外的媒体功能(搜索、剧集更改、音轨更改等)可能会带来额外的内存压力。

了解 TV 设备

本指南主要侧重于低 RAM 设备的内存用量和内存目标。

在 TV 设备上,请考虑以下特征:

  • 设备内存:设备安装的随机存取存储器 (RAM) 容量。
  • 设备界面分辨率:设备用于渲染操作系统 和应用界面的分辨率;此分辨率通常低于设备视频分辨率。
  • 视频分辨率:设备可以播放视频的最高分辨率。

这有助于对不同设备类型进行分类,并了解它们应如何使用内存。

TV 设备摘要

设备内存 设备视频分辨率 设备界面分辨率 isLowRAMDevice()
1 GB 1080p 720p
1.5 GB 2160p 1080p
≥1.5 GB 1080p 720p 或 1080p 否*
≥2 GB 2160p 1080p 否*

低 RAM TV 设备

这些设备处于 内存受限的情况 ,并将 ActivityManager.isLowRAMDevice() 报告为 true。在低 RAM TV 设备上运行的应用需要实现额外的内存控制措施

我们认为具有以下特征的设备属于此类:

  • 1 GB 设备:1 GB RAM、720p/HD (1280x720) 界面分辨率、 1080p/FullHD (1920x1080) 视频分辨率
  • 1.5 GB 设备:1.5 GB RAM、1080p/FullHD (1920x1080) 界面 分辨率、2160p/UltraHD/4K (3840x2160) 视频分辨率
  • 由于额外的内存限制,OEM 定义了 ActivityManager.isLowRAMDevice() 标志的其他情况。

常规 TV 设备

这些设备不会面临如此严重的内存压力。我们认为这些设备具有以下特征:

  • ≥1.5 GB RAM、720p 或 1080p 界面和 1080p 视频分辨率
  • ≥2 GB RAM、1080p 界面和 1080p 或 2160p 视频分辨率

这并不意味着应用不应关注这些设备上的内存用量,因为某些特定的内存滥用行为仍然会耗尽可用内存 并导致性能不佳。

低 RAM TV 设备上的内存目标

在衡量这些设备上的内存时,我们强烈建议 使用 Android Studio 内存分析器监控内存的每个部分。TV 应用应分析其内存用量,并努力将其类别置于本部分中定义的阈值以下。

内存分析器

内存的计算方式 部分,您可以找到有关报告的内存数据的详细说明。对于 TV 应用的阈值定义,我们将重点关注三个内存 类别:

  • 匿名 + 交换:由 Android Studio 中的 Java + 原生 + 堆栈分配内存组成。
  • 图形:直接在分析器工具上报告。通常由图形纹理组成。
  • 文件:在 Android Studio 中报告为“代码”+“其他”类别。

根据这些定义,下表指出了每种类型的内存组应使用的最大值:

内存类型 用途 用量目标 (1 GB)
匿名 + 交换(Java + 原生 + 堆栈) 用于分配、媒体缓冲区、 变量和其他内存密集型任务。 < 160 MB
图形 由 GPU 用于纹理和显示 相关缓冲区 30-40 MB
文件 用于内存中的代码页和文件。 60-80 MB

总内存上限 (匿名 + 交换 + 图形 + 文件)不得 超过以下值:

  • 对于 1 GB 低 RAM 设备,总内存用量(匿名 + 交换 + 图形 + 文件 )为 280 MB

强烈建议 不要超过以下值:

  • 匿名 + 交换 + 图形 )的内存用量为 200 MB

文件内存

作为文件支持内存的一般指导原则 ,请注意:

  • 一般来说,文件内存由操作系统内存管理系统妥善处理。
  • 目前,我们尚未发现它是内存压力的主要原因。

但是,在处理一般文件内存时:

  • 请勿将未使用的库 纳入 build 中,并尽可能使用 库的小型子集,而不是完整的库。
  • 请勿将大型文件保持打开状态 并将其保留在内存中,而应在完成操作后立即释放它们。
  • 尽可能缩小 Java 和 Kotlin 类的编译代码大小,请参阅 缩减、混淆处理和优化应用指南。

TV 专用建议

本部分提供了有关优化 TV 设备内存用量的具体建议。

显存

使用适当的图片格式和分辨率。

  • 请勿加载分辨率高于设备界面分辨率的图片。 例如,在 720p 界面设备上,应将 1080p 图片缩小到 720p。
  • 尽可能使用由硬件支持的位图
    • 在 Glide 等库中,启用默认处于停用状态的 Downsampler.ALLOW_HARDWARE_CONFIG 功能。启用此功能可避免重复位图,否则这些位图将同时存在于显存和匿名内存中。
  • 避免中间渲染 和重新渲染
    • 可以使用 Android GPU 检查器 识别这些渲染:
    • 在“纹理”部分中查找作为 最终渲染步骤的图片,而不是仅构成最终渲染的元素,这 通常是所谓的“中间渲染”。
    • 对于 Android SDK 应用,您通常可以使用 布局标志 forceHasOverlappedRendering:false 来停用此布局的中间渲染,从而移除这些渲染。
    • 请参阅避免重叠渲染 ,了解有关重叠渲染的实用资源。
  • 尽可能**避免加载占位图片**,请使用 @android:color/@color 作为占位纹理。
  • 如果可以在离线状态下执行合成,请避免在设备上合成多张图片 。最好加载独立图片,而不是从下载的图片进行图片合成
  • 请遵循 处理位图 指南,以便更好地处理位图。

匿名 + 交换内存

匿名 + 交换 由 Android Studio 内存分析器中的原生 + Java + 堆栈分配组成。使用 ActivityManager.isLowMemoryDevice() 检查设备是否内存受限,并遵循以下准则来适应这种情况 。

  • 媒体
    • 根据设备 RAM 和视频播放分辨率为媒体缓冲区指定可变大小 。这应考虑 1 分钟的视频播放时间:
      1. 对于 1 GB / 1080p,为 40-60 MB
      2. 对于 1.5 GB / 1080p,为 60-80 MB
      3. 对于 1.5 GB / 2160p,为 80-100 MB
      4. 对于 2 GB / 2160p,为 100-120 MB
    • 在更改剧集时释放媒体内存分配 ,以防止匿名内存总量增加。
    • 当应用停止时, **立即释放并停止媒体资源**:使用 activity 生命周期回调 来处理音频和视频资源。如果您不是音频应用,请在 activity 上发生 onStop()停止播放 ,保存您正在执行的所有工作,并将资源设置为释放状态。如需安排您稍后可能需要的工作,请参阅 作业和提醒部分。
    • 在视频搜索时注意缓冲区的内存:开发者 通常会在搜索时分配额外的 15-60 秒的未来内容,以便为用户准备好 视频,但这会产生额外的内存开销。 一般来说,在用户选择新的视频位置之前,请勿获取超过 5 秒的未来缓冲区。如果您确实需要在搜索时预先缓冲额外的时间,请务必:
      • 提前分配搜索缓冲区并重复使用。
      • 缓冲区空间不应超过 15-25 MB (具体取决于设备内存)。
  • 分配
    • 使用显存指南,确保 您不会在匿名内存中重复图片
      • 图片通常是内存用量最大的用户,因此重复图片可能会给设备带来很大的压力。在大量浏览图片网格视图时尤其如此。
    • 在移动 屏幕时通过删除引用来释放分配 :确保没有遗留的位图和对象引用。
    • 添加新库时,分析库的内存分配,因为 它们也可能会加载其他库,这些库也可能会进行 分配并创建绑定
  • 网络
    • 请勿在应用启动期间执行阻塞网络调用,它们会减慢应用启动时间,并在启动时产生额外的内存开销,此时内存尤其受到应用加载的限制。先显示加载或启动画面,然后在界面就位后执行网络请求。

绑定

绑定 会带来额外的内存开销 ,因为它们会将其他应用引入内存增加绑定应用的内存消耗 (如果该应用已在内存中 ),以方便 API 调用。因此,这会减少前台应用的可用内存 。绑定服务时,请注意使用绑定的时间和时长。请务必在不再需要绑定时立即释放绑定

典型绑定 和最佳实践:

  • Play Integrity API:用于检查设备 完整性
    • 在加载画面之后和媒体播放之前检查设备完整性
    • 在播放内容之前释放对 PlayIntegrity StandardIntegrityManager 的引用。
  • Play 结算库:用于管理 订阅和购买交易(使用 Google Play)
  • GMS FontsProvider
    • 最好在低 RAM 设备上使用独立字体,而不是使用字体提供方,因为下载字体的成本很高,并且 FontsProvider 会绑定服务来执行此操作。
  • Google 助理库:有时 用于搜索和应用内搜索,如果可能,请替换此库。
    • 对于 Leanback 应用:使用 Gboard 语音转文字或 androidx.leanback 库。
      • 请遵循 搜索指南 来实现搜索功能。
      • 注意:Leanback 已被废弃 ,应用应迁移到 TV Compose。
    • 对于 Compose 应用
      • 使用 Gboard 语音转文字来实现语音搜索。
    • 实现 “稍后观看” 功能,以便在您的应用中 发现媒体内容。

前台服务

前台服务是 一种特殊类型的服务,它与通知相关联。此通知会显示在手机和平板电脑的通知栏中,但 TV 设备没有与这些设备相同的通知栏。即使 前台服务很有用,因为它们可以在应用处于 后台时保持运行,但 TV 应用也必须遵循以下准则:

在 Android TV 和 Google TV 中,只有 在用户离开应用后,前台服务才能保持运行:

  • 对于 音频应用: 前台服务只有在用户离开 应用后,才能保持运行,以继续播放音轨。音频播放结束后,服务必须立即停止。
  • 对于任何其他应用用户离开应用后所有前台服务都必须停止,因为没有通知来告知 用户应用仍在运行并消耗资源。
  • 对于后台作业 (例如更新推荐内容或 “稍后观看”),请使用 WorkManager

作业和提醒

WorkManager 是用于安排后台定期作业的最新 Android API。 WorkManager 将在可用时使用新的 JobScheduler (SDK 23+),并在不可用时使用旧的 AlarmManager。如需了解在 TV 上执行安排的作业的最佳实践,请遵循以下建议:

  • 避免 在 SDK 23+ 上使用 AlarmManager API, 尤其是 AlarmManager.set()AlarmManager.setExact() 和类似方法,因为它们不 允许系统决定运行作业的适当时间(例如,当 设备处于空闲状态时)。
  • 在低 RAM 设备上,除非绝对必要,否则请避免运行作业。如果需要, 请 仅在播放后更新推荐内容时 使用 WorkManager WorkRequest,并尝试在应用仍处于打开状态时执行此操作。
  • 定义 WorkManager Constraints ,以便系统在适当的时间运行您的作业:

Kotlin

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()

Java

Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresStorageNotLow(true)
    .setRequiresDeviceIdle(true)
    .build()
  • 如果您必须定期运行作业(例如,根据用户在其他设备上观看应用内容的活动来更新 “稍后观看”), 请将作业的内存消耗量保持在 30 MB 以下,以降低内存用量。

一般内存注意事项

以下准则提供了有关 Android 应用开发的一般信息:

  • 尽可能减少对象分配,优化对象重用,并及时解除分配所有未使用的对象。
    • 请勿保留 对对象(尤其是位图)的引用。
    • 避免使用 System.gc() 和直接释放内存调用 ,因为它们会干扰系统的内存处理流程:例如, 在使用 zRAM 的设备中,强制调用 gc() 可能会因内存的压缩和解压缩而暂时增加 内存用量。
    • 使用 LazyList(如 Compose 中的 目录浏览器所示)或RecyclerView(如 现已废弃的 Leanback 界面工具包所示),以重复使用视图,而不是重新创建 列表元素。
    • 在本地缓存从外部内容提供方读取的元素(这些元素不太可能发生更改),并定义更新间隔以防止分配额外的外部内存。
  • 检查是否存在内存泄漏。
    • 注意 典型的内存泄漏 情况,例如匿名线程内的引用、从未释放的视频缓冲区重新分配以及其他类似情况。
    • 使用堆转储来 调试内存泄漏。
  • 生成基准配置文件 以最大限度地减少在冷启动时执行 应用所需的即时编译量。

了解直接内存回收

当 Android TV 应用请求内存且系统面临压力时,作为 Android 基础的 Linux 内核可能必须使用直接内存回收

该流程涉及完全暂停任何分配线程 ,以等待释放的内存页。当后台回收无法主动维持足够的内存池时,就会发生这种情况。

这可能会导致用户体验出现明显的暂停或卡顿 ,因为系统会暂停分配线程,直到有足够的内存可用为止。从这个意义上讲,分配线程不限于 malloc() 等应用代码调用;例如,需要为代码页分配内存。

工具摘要