内存优化对于确保顺畅的性能、防止应用崩溃以及维护系统稳定性和平台运行状况至关重要。虽然应监控和优化每个应用的内存用量,但适用于 TV 设备的内容应用面临的特定挑战与适用于手持设备的典型 Android 应用不同。
高内存消耗可能会导致应用和系统行为出现问题,包括:
- 应用本身可能会变慢或出现延迟,在最糟糕的情况下,应用可能会被终止。
- 可供用户查看的系统服务(音量控制、图片设置信息中心、语音助理等)会出现严重延迟,或者可能完全无法运行。
- 系统组件可能会被终止;然后,这些组件会重启,从而触发极端资源争用激增,并直接影响前台应用。
- 转换到启动器可能会出现明显延迟,并且在转换完成之前,前台应用会看起来无响应。
- 系统可能会进入直接回收状态,暂时暂停线程执行,同时等待内存分配。这可能会发生在任何线程(例如主线程或编解码器相关线程)上,可能会导致音频和视频帧丢失以及界面故障。
电视设备上的内存注意事项
电视设备的内存通常比手机或平板电脑少得多。例如,我们在电视上看到的配置是 1 GB RAM 和 1080p 视频分辨率。同时,大多数 TV 应用具有类似的功能;因此,实现方式和常见问题也类似。这两种情况会出现其他类型设备和应用中没有的问题:
- 媒体 TV 应用通常由网格图片视图和全屏背景图片组成,这需要在短时间内将大量图片加载到内存中
- 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/高清 (1280x720) 界面分辨率、1080p/全高清 (1920x1080) 视频分辨率
- 1.5 GB 设备:1.5 GB RAM、1080p/全高清 (1920x1080) 界面分辨率、2160p/超高清/4K (3840x2160) 视频分辨率
- 由于存在额外的内存限制,原始设备制造商 (OEM) 定义
ActivityManager.isLowRAMDevice()
标志的其他情况。
普通电视设备
这些设备不会出现如此严重的内存压力情况。我们认为这些设备具有以下特征:
- RAM 至少为 1.5 GB、720p 或 1080p 界面和 1080p 视频分辨率
- RAM 至少为 2 GB、界面分辨率为 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 设备的总内存用量(Anon+Swap + Graphics + File)为 280 MB。
强烈建议不要超过以下值:
- 内存用量为 200 MB(Anon+Swap + Graphics)。
文件内存
作为有文件支持的内存的一般指南,请注意以下几点:
- 通常,操作系统内存管理会妥善处理文件内存。
- 目前,我们尚未发现它是导致内存压力的主要原因。
不过,通常在处理文件内存时:
- 请勿将未使用的库添加到 build 中,并尽可能使用库的一小部分,而不是完整的库。
- 请勿将打开的大型文件保留在内存中,并在使用完毕后立即释放它们。
- 如需最大限度地减小 Java 和 Kotlin 类的编译代码大小,请参阅缩减、混淆处理和优化应用指南。
特定电视推荐
本部分提供了有关优化 TV 设备内存用量的具体建议。
显存
使用适当的图片格式和分辨率。
- 请勿加载分辨率高于设备界面分辨率的图片。 例如,在 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 分钟的视频播放时间:
- 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 应用必须遵循以下准则:
在 Android TV 和 Google TV 中,只有在满足以下条件时,前台服务才可以在用户离开应用后继续运行:
- 对于音频应用:前台服务仅可在用户离开应用后继续运行,以便继续播放音轨。音频播放结束后,必须立即停止服务。
- 对于任何其他应用: 在用户退出应用后,必须停止所有前台服务,因为没有通知会告知用户应用仍在运行并消耗资源。
- 对于后台作业(例如更新推荐内容或接下来观看),请使用
WorkManager
。
作业和闹钟
WorkManager
是用于安排后台周期性作业的先进 Android API。WorkManager 会在可用时(SDK 23 及更高版本)使用新的 JobScheduler
,在不可用时使用旧的 AlarmManager
。如需了解在电视上执行定期作业的最佳实践,请遵循以下建议:
- 在 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 中的目录浏览器中所示)或现已废弃的 Leanback 界面工具包中的RecyclerView
,以重复使用视图,而不是重新创建列表元素。 - 在本地缓存从外部内容提供程序读取的元素(这些元素不太可能发生变化),并定义更新间隔以防止分配额外的外部内存。
- 检查是否存在可能的内存泄漏。
- 注意典型的内存泄漏情况,例如匿名线程内的引用、永远不会释放的视频缓冲区重新分配,以及其他类似情况。
- 使用堆转储来调试内存泄漏问题。
- 生成基准配置文件,以最大限度地减少在冷启动时执行应用时所需的即时编译量。
工具摘要
- 使用 Android Studio 内存分析器工具检查使用期间的内存用量。
- 使用 Android GPU 检查器检查图形分配。