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