内存优化对于确保流畅的性能、防止应用崩溃以及维持系统稳定性和平台健康状况至关重要。虽然每款应用都应监控和优化内存使用情况,但 TV 内容应用面临的挑战与手持设备上的典型 Android 应用不同。
高内存消耗量可能会导致应用和系统行为出现问题,包括:
- 应用本身可能会变得缓慢或卡顿,最糟糕的情况下会被终止。
- 用户可见的系统服务(音量控制、画面设置信息中心、语音助理等)变得非常卡顿,甚至可能无法正常运行。
- 低内存终止守护进程 (LMK) 进程可能会通过终止最不必要的进程来应对内存压力较高这一问题;然后,这些组件可能会在不久后重新启动,从而触发进一步的资源争用高峰,这会直接影响前台应用。
- 过渡到启动器可能会严重延迟,导致前台应用在过渡完成之前看起来无响应。
- 系统可能会开始使用直接回收,在等待内存分配时暂时暂停线程的执行。这种情况可能会发生在任何线程(例如主线程或编解码器相关线程)中,可能会导致音频和视频丢帧以及界面故障。
电视设备上的内存注意事项
电视设备的内存通常比手机或平板电脑少得多。例如,我们在电视上看到的配置是 1 GB RAM 和 1080p 视频分辨率。与此同时,大多数电视应用都具有类似的功能;因此,实现方式类似,面临的挑战也类似。 这两种情况会带来其他设备类型和应用中未曾出现的问题:
- 媒体 TV 应用通常由网格状图片视图和全屏背景图片组成,需要在短时间内将大量图片加载到内存中
- 电视应用播放多媒体流,这需要分配一定量的内存来播放视频和音频,并且需要相当多的媒体缓冲区来确保流畅播放。
- 如果未正确实现,其他媒体功能(例如搜索、更改剧集、更改音轨等)可能会增加内存压力。
了解电视设备
本指南主要侧重于应用内存用量和低 RAM 设备的内存目标。
在电视设备上,请考虑以下特征:
- 设备内存:设备已安装的随机存取存储器 (RAM) 的大小。
- 设备界面分辨率:设备用于渲染操作系统和应用界面的分辨率;此分辨率通常低于设备视频分辨率。
- 视频分辨率:设备可以播放视频的最高分辨率。
这有助于对不同类型的设备进行分类,并确定这些设备应如何使用内存。
电视设备摘要
| 设备内存 | 设备视频分辨率 | 设备界面分辨率 | 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/全高清 (1920x1080) 界面分辨率、2160p/超高清/4K (3840x2160) 视频分辨率
- 由于存在额外的内存限制,OEM 定义了
ActivityManager.isLowRamDevice()标志的其他情况。
普通电视设备
这些设备不会出现如此严重的内存压力情况。我们认为这些设备具有以下特征:
- ≥1.5 GB RAM、720p 或 1080p 界面和 1080p 视频分辨率
- ≥2 GB RAM、1080p 界面和 1080p 或 2160p 视频分辨率
这并不意味着应用不应关注这些设备上的内存用量,因为某些特定的内存滥用行为仍可能会耗尽可用内存,导致性能不佳。
低 RAM TV 设备上的内存目标值
在测量这些设备上的内存时,我们强烈建议使用 Android Studio 内存分析器监控内存的每个部分。电视应用应分析其内存用量,并努力将其类别控制在本部分定义的阈值以下。

在内存统计方式部分,您可以找到有关报告的内存数据的详细说明。对于 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 类的已编译代码大小,请参阅缩减、混淆处理和优化应用指南。
具体电视推荐
本部分针对优化电视设备上的内存用量提供了具体建议。
显存
使用合适的图片格式和分辨率。
- 请勿加载分辨率高于设备界面分辨率的图片。 例如,在 720p 界面设备上,1080p 图片应缩小为 720p。
- 尽可能使用由硬件支持的位图。
- 在 Glide 等库上,启用默认处于停用状态的
Downsampler.ALLOW_HARDWARE_CONFIG功能。启用此功能可避免重复使用位图,否则位图会同时位于显存和匿名内存中。
- 在 Glide 等库上,启用默认处于停用状态的
- 避免中间渲染和重新渲染
- 您可以使用 Android GPU 检查器来识别这些问题:
- 在“纹理”部分中,查找属于最终渲染步骤的图片,而不是仅包含构成最终渲染的元素,这通常称为“中间渲染”。
- 对于 Android SDK 应用,您通常可以使用布局标志
forceHasOverlappedRendering:false停用此布局的中间渲染,从而移除这些内容。 - 如需了解有关重叠渲染的更多信息,请参阅避免重叠渲染这篇出色的资源文章。
- 尽可能避免加载占位图片,请使用
@android:color/或@color作为占位纹理。 - 如果可以在离线状态下执行合成,请避免在设备上合成多张图片。优先加载独立图片,而不是从下载的图片进行图片合成
- 请按照处理位图指南更好地处理位图。
匿名内存 + 交换内存
Anon+Swap 由 Android Studio 内存分析器中的原生内存分配、Java 内存分配和堆栈内存分配组成。使用 ActivityManager.isLowMemoryDevice() 检查设备是否受内存限制,并按照以下准则适应这种情况。
- 媒体:
- 根据设备 RAM 和视频播放分辨率,为媒体缓冲区指定可变大小。这应计为 1 分钟的视频播放时间:
- 40-60 MB(1 GB / 1080p)
- 1.5 GB / 1080p 视频的60-80 MB
- 1.5 GB / 2160p 视频的80-100 MB
- 2 GB / 2160p 视频的100-120 MB
- 在更改剧集时释放媒体内存分配,以防止匿名内存总量增加。
- 当应用停止时,立即释放并停止媒体资源:使用 activity 生命周期回调来处理音频和视频资源。如果您的应用不是音频应用,请在活动中发生
onStop时停止播放,保存您正在执行的所有工作,并释放资源。如需安排稍后需要完成的工作,请参阅作业和闹钟部分。- 您可以使用 生命周期感知型组件(例如
LiveData和LifecycleOwner)来帮助您处理 Activity 生命周期调用。 - 如需让工作感知生命周期,您还可以使用 Kotlin 协程和 Kotlin Flow。
- 您可以使用 生命周期感知型组件(例如
- 在视频搜索时注意缓冲区的内存:开发者通常会在搜索时分配额外的 15-60 秒的未来内容,以便让视频随时可供用户观看,但这会产生额外的内存开销。一般来说,在用户选择新的视频位置之前,不要占用超过 5 秒的未来缓冲区。如果您在搜索时确实需要预先缓冲额外的时间,请务必执行以下操作:
- 提前分配搜索缓冲区并重复使用。
- 缓冲区空间不应超过 15-25 MB(具体取决于设备内存)。
- 根据设备 RAM 和视频播放分辨率,为媒体缓冲区指定可变大小。这应计为 1 分钟的视频播放时间:
- 分配:
- 使用显存指南确保您不会在匿名内存中复制图片
- 图片通常是内存用量大户,因此重复使用图片可能会给设备带来很大压力。在大量浏览图片网格视图时尤其如此。
- 在移动屏幕时,通过舍弃引用来释放分配:确保没有留下对位图和对象的引用。
- 使用显存指南确保您不会在匿名内存中复制图片
- 库:
- 添加新库时,分析来自库的内存分配,因为它们也可能会加载其他库,而这些库也可能会进行分配并创建 Binding。
- 网络:
- 请勿在应用启动期间执行阻塞的网络调用。它们会延长应用启动时间,并在启动时产生额外的内存开销,而此时内存会因应用加载而受到特别的限制。先显示加载屏幕或启动画面,然后在界面就位后执行网络请求。
绑定
绑定会带来额外的内存开销,因为它们会将其他应用带入内存或增加绑定应用的内存消耗(如果该应用已在内存中),以方便进行 API 调用。这会导致前台应用的可用内存减少。绑定服务时,请注意您使用绑定的时间和时长。请务必在不再需要绑定时立即释放绑定。
典型绑定和最佳实践:
- Play Integrity API:用于检查设备完整性
- 在加载屏幕之后和媒体播放之前检查设备完整性
- 在播放内容之前,释放对 PlayIntegrity
StandardIntegrityManager的引用。
- Play 结算库:用于管理使用 Google Play 的订阅和购买交易
- 在加载屏幕后初始化库,并在播放任何媒体之前处理所有结算工作。
- 使用完库后,以及在播放视频或媒体之前,请务必使用
BillingClient.endConnection()。 - 使用
BillingClient.isReady()和BillingClient.getConnectionState()检查服务是否已断开连接(以防需要重新完成任何结算工作),然后在完成后再次执行BillingClient.endConnection()。
- GMS FontsProvider
- 在低 RAM 设备上,最好使用独立字体,而不是使用字体提供程序,因为下载字体成本高昂,并且 FontsProvider 会绑定服务来执行此操作。
- Google 助理库:有时用于搜索和应用内搜索,如果可能,请替换此库。
- 对于 Leanback 应用:使用 Gboard 文字转语音或 androidx.leanback 库。
- 遵循搜索指南来实现搜索功能。
- 注意:leanback 已弃用,应用应迁移到 TV Compose。
- 对于 Compose 应用:
- 使用 Gboard 文字转语音功能实现语音搜索。
- 实现接下来观看功能,以便用户发现您应用中的媒体内容。
- 对于 Leanback 应用:使用 Gboard 文字转语音或 androidx.leanback 库。
前台服务
前台服务是一种与通知相关联的特殊类型的服务。此通知会显示在手机和平板电脑的通知托盘中,但电视设备没有与这些设备相同的通知托盘。即使前台服务非常有用,因为它们可以在应用处于后台时保持运行,但电视应用也必须遵循以下准则:
在 Android TV 和 Google TV 中,前台服务只能在用户离开应用后继续运行:
- 对于音频应用:前台服务仅允许在用户离开应用后继续运行,以继续播放音轨。音频播放结束后,必须立即停止服务。
- 对于任何其他应用:用户离开应用后,必须停止所有前台服务,因为没有通知会告知用户应用仍在运行并消耗资源。
- 对于更新推荐或接下来观看等后台作业,请使用
WorkManager。
作业和闹钟
WorkManager 是用于安排后台周期性作业的最新 Android API。
WorkManager 将在可用时(SDK 23 及更高版本)使用新的 JobScheduler,否则使用旧的 AlarmManager。为了在电视上以最佳方式执行预定作业,请遵循以下建议:
- 在 SDK 23 及更高版本上,避免使用
AlarmManagerAPI,尤其是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 中的目录浏览器或在现已弃用的 Leanback 界面工具包中的RecyclerView中所示)来重复使用视图,而不是重新创建列表元素。 - 缓存从外部内容提供程序读取的可能不会更改的元素,并定义更新间隔,以防止分配额外的外部内存。
- 检查是否存在可能的内存泄漏。
- 请注意典型的内存泄漏情况,例如匿名线程中的引用、从未释放的视频缓冲区重新分配以及其他类似情况。
- 使用 堆转储来调试内存泄漏问题。
- 生成基准配置文件,以最大限度地减少在冷启动时执行应用所需的即时编译量。
了解直接内存回收
当 Android TV 应用请求内存且系统压力过大时,作为 Android 基础的 Linux 内核可能必须使用直接内存回收。
该过程涉及完全暂停任何分配线程,以等待释放的内存页。当后台回收无法主动维持足够的内存池时,就会发生这种情况。
这可能会导致用户体验出现明显的暂停或卡顿,因为系统会暂停分配线程,直到有足够的内存可用为止。从这个意义上讲,分配线程并不限于 malloc() 等应用代码调用;例如,需要为换入代码页分配内存。
工具摘要
- 使用 Android Studio 内存分析器工具检查使用期间的内存消耗情况。
- 使用 Android GPU 检查器检查图形分配。