在线AI教程 / AI入门

Android 17 内存管理升级|一文详解必备操作,助力打造高性能应用

· 转自:微信公众号原作者

<p>原创 <span>Android</span> <span>2026-07-30 19:10</span> <span style="display: inline-bloc

转载本文转自「微信公众号原作者」,版权归原作者所有,内容已摘录正文、去除外部链接。
本文转自「微信公众号原作者」,内容为转载摘录,版权归原作者所有,已去除外部链接。

原创 Android 2026-07-30 19:10 北京

内存是支撑应用性能与多任务体验的隐性基础。欢迎阅读本文,了解 Android 17 内存限制的底层逻辑与实践指南,借助相关工具和 API 支持,共同打造更稳定高效的设备体验。

作者 / 开发者关系工程师 Alice Yuan,开发者关系工程师 Ajesh Pai,开发者关系工程师 Fung Lam 虽然大家通常将应用性能等同于流畅的界面和快速的启动时间,但内存才是支撑这些显性指标的隐性基础。众所周知,设备内存在当下的重要性逐渐到达前所未有的高度。我们不仅在 Android 17 中取得了 Android 内存优化的长足进步,同时提供相关工具和 API 支持,以帮助开发者提前应对今年晚些时候更为严格的内存要求。 为保障设备稳定性,自 Android 17 起,系统将根据设备总 RAM 强制执行应用内存上限。一旦应用超出该上限,Android 将终止该进程,且不会提供相关的堆栈轨迹 (Stack Trace)。 除了上述强制终止操作之外,未经优化的内存占用必然会对用户体验产生负面影响。当应用接近堆内存上限时,就会频繁触发垃圾回收 (Garbage Collection),造成明显的界面卡顿。此外,当设备的可用内存耗尽,系统将不得不频繁进行页面回收,从而导致 CPU 负载过高、界面延迟、耗电过快等问题。如果内存严重不足,还会触发低内存终止 (LMK) 事件,进而迫使应用经历缓慢的冷启动并丢失用户状态。 为打造高性能应用,并规避此类强制终止问题,我们建议开发者采用以下内存优化策略: 通过 R8 充分优化字节码

优化图像加载

使用 Android Studio 检测并修复内存泄漏

当应用离开可见状态时主动释放内存

通过 ProfilingManager 实现高级内存观测

△ 本文的精简版还有视频版本,欢迎观看!

了解 Android 17 应用内存限制

Android 17 引入了应用内存限制,旨在防止 "单个异常应用" 破坏用户整部设备的多任务体验和稳定性。 以下是本次架构调整的具体原因说明: 防止级联终止:当应用处于特权状态 (例如正在运行前台服务) 时发生内存膨胀或泄漏,系统在初期会对其进行保护,避免被低内存终止 (LMK) 查杀。随着这个单一应用无节制地增长并占用大量 RAM,LMK 被迫通过终止数十个较小的、运行良好的缓存应用和后台任务来回收内存,以优先保障这个特权状态下的应用使用。

保障多任务体验,维持用户状态:当系统为容纳单一泄漏进程而被迫清除缓存应用,多任务体验会被严重削弱。当用户尝试切回之前缓存的应用,就会无法实现近乎即时的热恢复,只能等待缓慢的冷启动。这种低效运行会加剧 CPU 负载,也会加快电量消耗。此外,这种状态还会销毁用户最近使用的应用内 Context,例如滚动位置、导航堆栈和游戏进度等。

如要判断应用会话是否在实际运行环境中受上述限制的影响,您可以调用 ApplicationExitInfo 中的 getDescription()。如果系统采用了限制机制,退出原因报告会显示为 REASON_OTHER,且描述字符串将会包含 "MemoryLimiter:AnonSwap"。您还可以利用基于触发的性能分析,使用 TRIGGER_TYPE_ANOMALY 在达到内存限制时自动捕获堆转储。此外,Android 正积极努力通过 Google Play 管理中心向开发者展示更多实际内存指标。 我们同步扩展了内存限制文档,新增本地调试命令,支持开发者在本地环境模拟内存限制,并验证应用在各类强制内存限制下的表现。 🔗 ApplicationExitInfo

🔗 getDescription()

🔗 REASON_OTHER

🔗 基于触发的性能分析

🔗 TRIGGER_TYPE_ANOMALY

🔗 内存限制文档

通过 R8 充分优化字节码

启用 R8 优化器可以高效缩减应用内存占用。通过缩短类、方法和字段名称,并剔除未使用的代码和资源,R8 能够大幅减少应用运行时的常驻代码体积,从而显著降低内存占用。 R8 能够精简常驻代码,缩小内存占用,并降低被 LMK 终止的风险。这使得应用可以更频繁的实现温启动,而非缓慢的冷启动。此外,精简后的字节码还能减轻主线程 CPU 负载,直接降低 ANR 发生率,提升用户体验的流畅度。例如:数字银行 Monzo 全面启用 R8 优化后,ANR 发生率降低了 35%,冷启动率改善了 30%,应用整体安装包体积也缩减了 9%。

△ 数字银行 Monzo 全面启用 R8 优化后,相关性能指标至高提升 35%。 🔗 Monzo

如需在 build.gradle 文件中正确配置 R8,请按以下步骤操作: 设置 isShrinkResources = true 和 isMinifyEnabled = true。

使用 proguard-android-optimize.txt 替代旧版 proguard-android.txt。旧文件实际上会限制优化,且 Android Gradle Plugin 9 已不再被支持。

从 gradle.properties 中移除 android.enableR8.fullMode = false。

如果您的代码库使用了反射,请添加 Keep 规则,避免 R8 优化该部分代码。请务必在 Keep 规则文件中遵循以下最佳实践,以实现最佳优化效果: 移除 -dontoptimize、-dontshrink 和 -dontobfuscate 等全局选项,此类选项会阻止 R8 优化整个代码库。

移除会限制 Activity、Service、View 或 Broadcast receivers 等 Android 组件优化的 Keep 规则。

细化宽泛的软件包级 Keep 规则,将作用目标精确限定为特定类或方法。

如需了解更多最佳实践,请参阅我们的 Keep 规则文档。 🔗 Keep 规则

🔗 Keep 规则文档

面向库开发者的 R8 最佳实践

如果您是库开发者,请严格地将使用者需要的规则在 consumer-rules 文件中妥善保存,并将库内部保护规则放置在 proguard-rules.pro 文件中。如需了解有关如何优化库的更多内容,请参阅面向库作者的优化:

R8 配置分析器

您可以使用配置分析器 (Configuration Analyzer) 来审查 R8 的优化效果。配置分析器展示了包含混淆、优化和压缩得分在内的当前优化状态。通过配置分析器,您还可以了解每条 Keep 规则阻止了多少类、方法或字段被优化,通过细化宽泛的软件包级 Keep 规则,以实现最佳优化效果。 使用配置分析器,还可以识别涵盖其他规则的 Keep 规则、冗余 Keep 规则以及未使用的 Keep 规则。

△ 配置分析器展示了包含混淆、优化和压缩得分在内的当前优化状态。 🔗 配置分析器

R8 Agent Skill

您还可以搭配 Android Studio 智能体或其他 AI 工具使用 R8 Agent Skill,以解决错误配置并优化规则,以此提升应用性能。(基于 AI 驱动 Skill 的数据洞察仍需经过技术验证) 🔗 R8 Agent Skill

优化图像加载

位图通常是应用内存中占用空间最大的常见对象,它们代表图像加载流程的最后一步,即将 JPEG 或 PNG 等压缩文件解码为用于显示的原始像素数据。因为内存消耗取决于图像的像素尺寸和色彩深度,这意味着一个仅 100KB 的压缩图像在 RAM 中可能会膨胀到几兆字节。由于位图操作往往处于帧绘制的关键路径上,因此未经优化的图像会造成严重的内存膨胀和界面卡顿。 Google 建议在以 Kotlin 为主的项目中选用 Coil 作为图像加载库,特别是在采用 Jetpack Compose 进行开发时;而对于基于 Java 的应用,则推荐使用 Glide。 🔗 Coil

🔗 Glide

采用五项最佳实践

图像降采样:如果您要手动加载位图,请避免将超大图像加载至微型缩略图控件中,应当使用 inSampleSize 来加载较小的版本。Glide 和 Coil 默认会对图像进行降采样,您可以分别通过 DownsampleStrategy 和 ImageLoader 配置降采样策略。

裁剪:避免直接在图像文件中嵌入边衬区以实现信箱模式 (例如,创建透明边框来扩大图像尺寸)。与其将这些边框固化在图像中,不如使用 InsetDrawable,或者直接在包含位图的 View 或 Composable 中应用内边距。

配置:选择合适的像素格式,以平衡内存和画质。如果无需透明度,请用 RGB_565,它仅占用默认 ARGB_8888 格式的一半内存。在 Glide 中,您可以通过 DecodeFormat 进行配置;而在 Coil 中,您可以使用 bitmapConfig 属性。

优先使用矢量可绘制对象:针对基础几何图形资源,可以将 ShapeDrawable 作为解码栅格化位图的轻量级替代方案。通过 XML 一次性定义这些资产,即可在所有显示密度下无缝缩放,同时有效解决资源导致的内存膨胀。

复用:如果应用手动管理位图,为了尽量减少内存抖动 (Memory Churn),当不再需要某个位图时,应当调用 bitmap.recycle() 立即释放该位图引用。如您使用 Glide 或 Coil 等图像加载库,请将位图归还至库托管池。通过为后续的内存需求提供现有缓冲区,该池能有效避免新内存分配的开销。

如需了解详情,请参阅我们的图像性能优化文档。 🔗 inSampleSize

🔗 DownsampleStrategy

🔗 ImageLoader

🔗 InsetDrawable

🔗 DecodeFormat

🔗 bitmapConfig

🔗 ShapeDrawable

🔗 图像性能优化

Android Studio 工具

您还可以利用 Android Studio Narwhal 4 来清除冗余位图。根据以下五步操作,即可轻松找到目标并清理它们: 进入 Android Studio,打开 Profiler 标签页。

点击 Heap Dump (或 "Analyze Memory Usage"),并开始录制,以捕获应用当前内存状态的快照。

扫描分析结果中的黄色警告三角 ⚠️ (Android Studio 的标志,表示多次重复存储的位图)。此外,您也可以前往性能分析器顶部,点击 "Filter by",选择 "Duplicate Bitmaps" 设置。

点击任意标记条目,即可打开 Bitmap Preview 窗格,查看重复占用内存的图像。

根据可视化定位结果,找到代码的重复加载逻辑,并实施更高效的缓存策略。

△ 使用 Android Studio Profiler 时,请留意 Heap Dump 中的黄色警告三角 ⚠️。

借助 Android Studio 检测并修复内存泄漏

在 Android 中,当一个对象的生命周期已经结束,但您的代码依然长期保留对其的引用时,就会发生内存泄漏。这种情况会阻碍垃圾回收器 (GC) 回收对应内存,最终导致性能迟滞或 OutOfMemoryError (OOM)。 Android Studio Panda 3 内置专属 LeakCanary 性能分析任务,以便开发者直接在 IDE 内实时分析内存泄漏并映射轨迹。 Android Studio 中的 LeakCanary 性能分析任务会主动将内存泄漏分析从设备转移至开发机器,与设备端泄漏分析相比,这能在泄漏分析阶段带来显著的性能提升。

△ LeakCanary 内存泄漏分析与 "转至声明" 调试功能相结合 🔗 LeakCanary

此外,泄漏分析目前已在 IDE 中关联上下文,并与源代码完全集成,支持 "转至声明" 等实用的代码连接功能,大幅降低排查和修复内存泄漏的操作成本与时间。 常见的内存泄漏示例 当某个对象在内存中存留的时间超过了其预期的生命周期时,就会产生内存泄漏,导致这种情况的原因通常有以下几种: 保留对已不再使用的 Fragment、Activity 或 View 的引用。

Context 引用管理不当。

未正确取消注册观察者、监听器和接收器。

为绑定至较短生命周期组件的对象创建静态引用。

部分场景示例如下: 场景

基于 Compose 的示例

基于 View 的示例

Context 泄漏

示例: 将LocalContext.current 传入 ViewModel 修复: 将依赖 Context 的逻辑保留在界面层。对于非界面层重构采用依赖注入,或通过 Kotlin Flow 观察界面状态。

示例: 在伴生对象或静态变量中存储 Activity。 修复: 请勿保留界面组件的静态引用。建议重构代码,改用依赖注入,或通过 Kotlin Flow 观察界面状态。

监听器泄漏

示例: 使用 DisposableEffect 启动监听器,但 onDispose 留空。 修复: 在 onDispose 代码块内执行注销和清理逻辑。

示例: 注册 SensorManager 更新,但忘记注销。 修复: 在 onStop() 或 onDestroy() 生命周期中手动调用 unregisterListener()。

View 泄漏

示例: AndroidView 内保留有对旧版 View 的引用,且未配置释放策略。 修复: 使用 AndroidView 可组合项的 release 代码块,清理旧版 View。

示例: Fragment 销毁后,依然保留对视图绑定对象的引用。 修复: 在 onDestroyView() 生命周期方法中,将绑定变量设为 null。

🔗 依赖注入

🔗 Kotlin Flow

🔗 清理逻辑

当应用退出可见状态时精简内存

如内存管理概览中所述,当系统需要为关键任务释放内存时,Android 就会回收应用内存;如有必要,还会彻底终止应用。当应用对用户不可见时,Android 通常会回收内存 (例如舍弃内存中的部分应用代码和数据页,或对已分配的堆内存进行压缩)。当用户重新使用您的应用,且该应用尝试访问已被回收的部分内存时,操作系统会按需将这部分内存重新交换回来。这种交换机制可能会导致应用出现意外卡顿或延迟。 🔗 内存管理概览

如果交由操作系统决定从应用回收哪些内存,可能会在恢复应用后,发现操作系统回收了即将用到的内存。相反,应用可以主动舍弃能以低成本按需重新生成的内存分配。为此,可以采用 ComponentCallbacks2 接口,并在 Activity、Fragment、Service,甚至是自定义 Application 类中采用 onTrimMemory。在 Application 类中使用它,能够高效管理全局缓存。 onTrimMemory() 回调方法会向应用推送生命周期或内存相关事件,表示此刻就是应用主动减少内存用量的理想时机。 🔗 onTrimMemory() ) 在内存生命周期管理方面,代码实现应专门关注于TRIM_MEMORY_UI_HIDDEN 和 TRIM_MEMORY_BACKGROUND。自 Android 14 起,系统已不再推送其他旧版常量通知,这些常量已在 Android 15 阶段被正式弃用。 TRIM_MEMORY_UI_HIDDEN:该信号表明应用界面已从用户视图中移出。此时正是释放于界面相关的大量内存分配资源的合适机会,例如位图、视频播放缓冲区或复杂动画资源。 TRIM_MEMORY_BACKGROUND:该信号表明当前进程已转入后台,并且为满足系统的全局内存需求,该进程随时可能被系统终止。如要延长该进程的缓存状态持续时长,同时减少应用的冷启动次数,建议您主动释放所有在用户重新切回应用时可轻松重建的资源。 import android.content.ComponentCallbacks2 // Other import statements. class MainActivity : AppCompatActivity(), ComponentCallbacks2 { /**

  • Release memory when the UI becomes hidden or when system resources become low.
  • level the memory-related event that is raised.

*/ override funonTrimMemory(level: Int) { if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { // Release memory related to UI elements, such as bitmap caches. } if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { // Release memory related to background processing, such as by // closing a database connection. } } }

注意:onTrimMemory 集成可能取决于 SDK 支持。例如,某些游戏需要其游戏引擎来启用该功能。详情请参阅游戏内存优化文档:

通过 ProfilingManager 实现高级内存可观测性

当线上环境存在本地无法复现的内存异常问题时,可使用 ProfilingManager API 来捕获并诊断故障。Android 15 已经引入该高级观测 API,以便开发者程序化采集真实用户的 Perfetto 性能分析文件。 对于暂未搭建专用基础设施来管理和托管性能工件的团队,Crashlytics 正在研发一套专属解决方案以简化工作流,并诚邀开发者提供反馈:

Android 17 引入了新的事件驱动触发器,其中的 TRIGGER_TYPE_OOM 和 TRIGGER_TYPE_ANOMALY 值得特别关注: 在 OutOfMemoryError 崩溃时,OOM 触发器会自动采集 Java 堆转储,提供精准的内存分配状态。下次启动应用并注册 registerForAllProfilingResults 回调时,会上报采集到的 OOM 文件。

异常触发器 (Anomaly Trigger) 用于检测严重性能问题,例如 Binder 调用过量或内存阈值超限等。内存异常触发会在系统终止应用之前生成堆转储。

val profilingManager = applicationContext.getSystemService(ProfilingManager::class.java) val triggers = ArrayList() triggers.add(ProfilingTrigger.Builder( ProfilingTrigger.TRIGGER_TYPE_ANOMALY)) val mainExecutor: Executor = Executors.newSingleThreadExecutor() val resultCallback = Consumer { profilingResult -> if (profilingResult.errorCode != ProfilingResult.ERROR_NONE) { // upload profile result to server for further analysis setupProfileUploadWorker(profilingResult.resultFilePath) } profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback) profilingManager.addProfilingTriggers(triggers)

收集到堆转储后,可以通过服务器下载该剖析文件,或在本地通过 adb pull 命令,将文件拖放至 Perfetto 界面。为了简化内存调试工作流,建议使用 Heap Dump Explorer,这是 Perfetto 界面堆转储的新默认视图。该工具提供了直观界面用于查看 Java 堆转储,以便可视化展示对象分配层级、计算留存内存大小,并快速追踪到 GC Root 的最短路径。借助 Heap Dump Explorer,一站式快速定位内存泄漏,找到体积膨胀的保留对象 (例如过度的位图分配),并分析堆对象分配。

△ 借助 Heap Dump Explorer 内置的火焰图,可以直观审查并浏览堆分配量最高的对象。 🔗 Perfetto 界面

🔗 Heap Dump Explorer

结论

通过 R8 优化字节码、落实图像加载最佳实践,以及解决内存泄漏问题,都是在压力下高效管控资源的同时保障优质用户体验的必要手段。采用以上主动方案,有助于维持应用的稳定性和性能、避免出现意外终止,并且保护用户上下文。如要深入了解性能相关专业知识,欢迎您查阅我们修订后的内存指南。也欢迎您持续关注 "Android 开发者" ,及时了解更多开发技术和产品更新等资讯动态。 🔗 内存指南

跳转微信打开