Android 17内存限制:我是怎么发现App被系统悄悄干掉的

上周线上突然来了一波用户反馈,说App在后台待一会儿再切回来就闪退。我一开始以为是普通崩溃,检查了一圈日志却什么都没找到------没有ANR,没有Java异常,连个像样的stack trace都没有。

直到我翻到ApplicationExitInfo,才发现"凶手"是谁:MemoryLimiter。

Android 17的内存限制机制,就这么神不知鬼不觉地杀掉了我的App。

怎么确认是MemoryLimiter干的

说实话,这个问题排查了我两天。不是因为难,是因为根本不知道从哪下手。

常规的崩溃排查根本不管用。ANR日志没有,Crash日志也没有。我甚至怀疑是手机系统问题,换了三台设备测试,结果两台Pixel 8 Pro中招,一台老款小米没事------这才让我意识到可能是版本适配的问题。

最后是Google官方文档救了我。官方说可以通过ApplicationExitInfo来判断退出原因:

kotlin 复制代码
val am = context.getSystemService(ActivityManager::class.java)
val exitInfos = am.getHistoricalProcessExitReasons(
    /* packageName = */ null,
    /* pid = */ 0,
    /* maxNum = */ 10
)

exitInfos.forEach { info ->
    if (info.description?.contains("MemoryLimiter") == true) {
        Log.e("ExitTracker", "Killed by MemoryLimiter, rss=${info.rss}")
        // 这里可以上报到你的监控后台
        reportKilledByMemoryLimiter(info)
    }
}

跑完这段代码,我的心凉了------半小时内被杀了7次。用户的手机是8GB RAM的旗舰机,没想到内存限制这么激进。

内存限制到底是什么

Android 17引入了一个新机制:系统会根据设备总RAM为每个App设定动态的内存上限。超过这个上限,系统就会直接终止进程,不留痕迹。

官方说"目前的限制比较保守",主要针对极端内存泄漏和会把整个系统拖下水的异常行为。但问题是------保守不代表没有。我这台测试机,App的实际内存上限大概在1.2GB左右,而我的某个页面加载了一大堆图片...

这里有个细节:被MemoryLimiter杀掉的进程,退出原因确实是REASON_OTHER,description里包含MemoryLimiter:AnonSwap字符串。如果你不主动去查,根本不知道是被谁干掉的。

触发式分析:在被杀之前抓现场

既然知道了凶手是谁,下一步就是找到"犯罪现场"------到底是哪段代码吃掉了这么多内存。

Google给了个新工具:触发式Profiling。核心是ProfilingManagerTRIGGER_TYPE_ANOMALY

scss 复制代码
val profilingManager = applicationContext
    .getSystemService(ProfilingManager::class.java)

val triggers = arrayListOf(
    ProfilingTrigger.Builder(
        ProfilingTrigger.TRIGGER_TYPE_ANOMALY
    )
    .setAnomalyAllocationThresholdBytes(50 * 1024 * 1024) // 50MB阈值
    .setAnomalyExecutionTimeThresholdMillis(5000) // 5秒阈值
    .build()
)

profilingManager.startProfiling(
    profilingRequest,
    triggers,
    executor
) { result ->
    // 拿到heap dump后,分析哪块内存涨得最快
    analyzeHeapDump(result.heapDump)
}

说实话这套API我没用太熟,官方的Demo跑了几遍才理解。核心思路是:在内存快要超标之前,先把heap dump抓下来,这样你就能看到"凶案现场"的完整快照,而不是事后猜谜。

我的优化踩坑

坑1:以为Glide会自动回收

一开始我以为图片框架会自动处理内存,结果发现不对------Glide的内存缓存策略在某些场景下会hold住大量Bitmap。我那个页面有30多张商品图,用户快速滑动时Glide会预加载,预加载的Bitmap全堆在内存里。

解决方案:手动限制Glide的内存缓存大小

scss 复制代码
Glide.get(this).clearMemory()

// 在Application里配置
val requestOptions = RequestOptions()
    .format(DecodeFormat.PREFER_RGB_565) // 比ARGB_8888省一半内存
    .disallowHardwareConfig() // 某些情况下避免使用Hardware Bitmap

Glide.with(this)
    .load(url)
    .apply(requestOptions)
    .into(imageView)

RGB_565这个参数我一开始没注意,后来查文档才发现默认的ARGB_8888每像素占4字节,RGB_565只占2字节。一张1080P的图片,RGB_565能省大约2MB内存。

坑2:LeakCanary在Debug版本才生效

我一开始把LeakCanary配置到正式包想监控线上内存,结果发现正式包根本不触发------LeakCanary 2.x之后默认只在debuggable=true的包生效。

官方文档说要配合TRIGGER_TYPE_ANOMALY使用,或者在测试阶段充分验证。但我的建议是:在测试阶段就把LeakCanary跑满,线上靠ApplicationExitInfo来监控。

坑3:ViewBinding和ButterKnife混用

项目里有个老模块用的是ButterKnife,新模块用的是ViewBinding,结果findViewById和binding.xxx混用,某些情况下会导致view泄漏。我花了一晚上才排查到这个原因。

统一迁移到ViewBinding之后,内存占用下降了大概15%。

适配Android 17的正确姿势

结合这次踩坑经历,总结几点建议:

  1. 先升级targetSdk到37

这是最基础的一步。Android 17的很多新API和能力需要targetSdkVersion 37才能完全发挥。

  1. 跑一遍内存基准测试

ProfilingManager配合TRIGGER_TYPE_ANOMALY,在测试阶段摸清App的内存峰值。

  1. 监控线上MemoryLimiter事件

ApplicationExitInfo这套API在正式环境也能用,建议集成到你的崩溃监控里。

  1. 优化图片加载策略
  • 使用RGB_565而非ARGB_8888
  • 限制Glide的内存缓存大小
  • 对大图使用缩略图
  1. 避免在后台持有大量对象

特别是Context、Bitmap、LargeHeap的误用。

最后说两句

这次适配Android 17的内存限制,让我重新审视了一下自己写的代码。以前觉得"内存够用就行",现在发现内存管理这事,不出问题是你运气好,出问题是必然。

Google的官方态度很明确:Android 17只是开始,后续版本会越来越严格。现在不优化,以后用户升级系统就等着收投诉吧。

建议各位Android开发同行,有空把自己的App在Android 17上跑一遍ApplicationExitInfo的排查代码,看看有没有被MemoryLimiter"暗杀"的记录。发现问题早优化,比等用户投诉再改要体面得多。

相关推荐
消失的旧时光-19432 小时前
Kotlin 协程设计思想(七):为什么 Kotlin 要设计 SupervisorJob 和 supervisorScope?
android·开发语言·kotlin
故渊at2 小时前
第一板块:Android 系统基石与运行原理 | 第五篇:Context 上下文与资源配置体系
android·人工智能·opencv·context·上下文·资源配置体系
故渊at2 小时前
第一板块:Android 系统基石与运行原理 | 第四篇:进程孵化(Zygote)与 Low Memory Killer 机制
android·虚拟机·zygote·系统启动·low memory·进程孵化
JohnnyDeng942 小时前
【Android】RecyclerView性能优化与缓存机制:从卡顿到丝滑的完整指南
android·性能优化·kotlin·mvvm
zfoo-framework2 小时前
kotlin中体会到一些比较好用的点
android·开发语言·kotlin
189228048612 小时前
NV077固态MT29F16T08ESLCHL6-QAES:C
c语言·开发语言·性能优化
●VON5 小时前
AtomGit Flutter鸿蒙客户端:文件树与代码浏览
android·服务器·安全·flutter·harmonyos·鸿蒙
故渊at11 小时前
系列三:组件化与模块化进阶 | 第11篇 组件化项目规范与问题根治:依赖、资源、Manifest 与混淆的全链路管控
android·架构·mvvm·模块化·组件化
故渊at11 小时前
系列二:MVVM 深度实战与项目重构 | 第7篇 LiveData & StateFlow 状态管理实战:从“粘包弹”到“丝滑流式”
android·重构