Android17新规:内存超限直接杀App,没有崩溃日志怎么排查?
Android 17 开始引入 App 内存限制限制值会根据设备总 RAM 决定。如果进程超过限制系统可以直接杀掉这个进程而且不会给一段常规 crash 堆栈。这个变化对大多数正常会话影响不大但对内存泄漏、图片缓存过大、前台服务长期占内存这类问题会更早暴露出来。限制怎么触发以前遇到低内存很多时候是 LMK 先处理后台进程。某个 App 如果占了很多内存系统可能会连续回收其他 cached app用户回到这些 App 时就变成冷启动页面状态也可能丢。Android 17 的做法更确定一些在部分设备上系统会按设备总 RAM 给 App 设置限制。进程越过这个限制后系统可以终止当前进程避免一个异常进程把整机多任务体验拖下去。这里要注意两点。第一内存限制只会在一部分 Android 17 设备上启用不是所有设备都有同样行为。第二这不是 Java 堆 OOM也不一定会在 Crash 平台里看到一段清晰堆栈。如果用户反馈“App 被系统杀了”但没有普通 crash历史退出原因是第一个入口。Android 11 以后可以通过ActivityManager.getHistoricalProcessExitReasons()读取ApplicationExitInfo。fun findMemoryLimiterExit(context: Context): Boolean{val activityManagercontext.getSystemService(ActivityManager::class.java)val exitsactivityManager.getHistoricalProcessExitReasons(context.packageName,0,20)returnexits.any{info -info.reasonApplicationExitInfo.REASON_OTHERinfo.description?.contains(MemoryLimiter:AnonSwap)true}}判断条件比较具体reason是REASON_OTHERdescription里包含MemoryLimiter:AnonSwap。只看REASON_OTHER不够因为这个 reason 还会覆盖其他退出情况。本地复现Android 17 的行为变更文档里补了am memory-limiter命令。它可以查看当前 memory limiter 状态也可以给某个进程手动设置限制。先拿到包名对应的 pidadb shell pidof com.example.app再看 memory limiter 当前状态adb shell am memory-limiter status给目标进程设置一个较低限制例如 300 MBadb shell am memory-limiter manualpid300如果要恢复系统默认限制adb shell am memory-limiter manualpidnone如果要移除当前进程上的所有限制adb shell am memory-limiter manualpidmax还有一个ignore子命令用来让 memory limiter 忽略某个 UID、忽略全部或者取消忽略adb shell am memory-limiter ignoreuidadb shell am memory-limiter ignore all adb shell am memory-limiter ignore none这些命令只在启用了 memory limiter 的设备上生效。如果设备本身不施加这类限制命令不会产生实际影响。R8 先打开内存优化不要只盯着 heap dump。发布包里的代码、资源、反射 keep 规则也会影响运行时常驻内存。release 构建里至少要确认 R8 优化是打开的android{buildTypes{release{isMinifyEnabledtrueisShrinkResourcestrueproguardFiles(getDefaultProguardFile(proguard-android-optimize.txt),proguard-rules.pro)}}}这里不要继续用proguard-android.txt。这个文件偏兼容旧行为会阻止一部分优化AGP 9 里也不再支持它。再检查gradle.properties里有没有关闭 full mode# 如果项目里还留着这一行删掉android.enableR8.fullModefalseproguard-rules.pro里也要少用全局开关。下面这些写法会直接挡住 R8 对整个代码库的优化-dontoptimize-dontshrink-dontobfuscate反射、序列化、三方 SDK 需要 keep 时规则要收窄到具体类、字段或注解。比如只保护某个 JSON 模型包通常比-keep class com.example.** { *; }更可控。如果是 SDK 或组件库消费者需要的规则放在consumer-rules.pro库内部为了自己编译和测试保留的规则放在模块自己的proguard-rules.pro。这两个文件混在一起会让接入方拿到过宽的 keep 规则。图片和泄漏图片是 Android 内存里很容易被低估的部分。一个压缩后只有几百 KB 的 PNG解码成ARGB_8888后内存按宽、高和每像素字节数计算。图片尺寸大内存就会直接上去。Compose 项目里用 CoilView 项目里用 Glide都不要绕过库自己手写一套大图加载。缩略图场景要让加载尺寸贴近目标 View 或 Composable 的显示尺寸不要把原图解码后再交给 UI 缩放。如果图片不需要透明通道可以评估RGB_565。它比ARGB_8888少一半像素内存但颜色质量和透明能力会受影响适合头像占位、列表缩略图这类对透明要求不高的场景。重复 Bitmap 可以直接从 Android Studio Profiler 里查。Heap Dump 结果里会标出 duplicate bitmaps点进去能看到图片预览再回到代码里定位是缓存策略错了还是列表项重复解码。内存泄漏排查也有新入口。Android Studio Panda 3 里加入了 LeakCanary profiler task分析工作放到开发机侧leak trace 还能和源码跳转连起来。对 Fragment binding 没清空、listener 没注销、ComposeDisposableEffect没释放这类问题比只看一段文本 trace 更快。主动释放缓存App 退到后台以后系统可能回收一部分内存。问题是系统不一定知道哪些对象马上会用哪些对象可以低成本重建。可以在Application或组件里处理onTrimMemory()把 UI 相关缓存、图片缓存、临时 buffer 这类可重建对象释放掉。class App:Application(), ComponentCallbacks2{override fun onTrimMemory(level: Int){if(levelComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN){imageMemoryCache.clear()videoPreviewCache.clear()}if(levelComponentCallbacks2.TRIM_MEMORY_BACKGROUND){searchResultCache.clear()temporaryBufferPool.trim()}}}这里主要看TRIM_MEMORY_UI_HIDDEN和TRIM_MEMORY_BACKGROUND。Android 14 以后其他一些旧的 trim 常量不再继续下发Android 15 里也已经标记废弃。TRIM_MEMORY_UI_HIDDEN适合清 UI 相关的大对象比如图片、视频预览 buffer、动画资源。TRIM_MEMORY_BACKGROUND说明进程已经在后台更适合清掉重新进入页面时能再生成的缓存。不要在这里释放马上无法恢复的业务状态。比如正在编辑的草稿、支付流程状态、用户选择路径这些应该进入持久化或 ViewModel / saved state 的设计里而不是当成普通缓存清掉。线上抓现场有些内存问题本地不容易复现。Android 15 引入的ProfilingManager可以在 App 内注册 profiling 结果回调Android 17 又加了事件触发能力。这次和内存关系比较大的触发类型有两个ProfilingTrigger.TRIGGER_TYPE_OOM ProfilingTrigger.TRIGGER_TYPE_ANOMALYTRIGGER_TYPE_OOM面向OutOfMemoryError在 OOM crash 发生时采集 Java heap dump。采集结果会在 App 下次启动并注册registerForAllProfilingResults回调后返回。TRIGGER_TYPE_ANOMALY面向系统识别出的严重性能异常内存限制触发时可以在进程被杀前采集 heap dump。这个点适合补在“没有堆栈的系统杀进程”问题上。最小接入只需要把结果回调接住拿到文件路径后交给自己的上传任务val profilingManagercontext.getSystemService(ProfilingManager::class.java)val executorExecutors.newSingleThreadExecutor()profilingManager.registerForAllProfilingResults(executor){result -if(result.errorCodeProfilingResult.ERROR_NONE){enqueueProfileUpload(result.resultFilePath)}else{logProfilingError(result.errorCode)}}真正接线上时还要考虑采样比例、用户同意、文件大小、上传时机和保留时间。heap dump 里可能包含对象内容不适合当普通日志随便传。最后Android 17 的 App 内存限制最关键的判断点是ApplicationExitInfo.REASON_OTHER和MemoryLimiter:AnonSwap。本地验证用am memory-limiter status/manual/ignore代码里补onTrimMemory()release 包确认 R8 优化没有被关掉。线上再用ProfilingManager的 OOM / anomaly 触发能力补 heap dump定位会比只等用户复现清楚很多。[#Android](javascript: [#Android17](javascript: [#性能优化](javascript: [#内存优化](javascript: [#R8](javascript: