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。

bash 复制代码
fun findMemoryLimiterExit(context: Context): Boolean {
    val activityManager = context.getSystemService(ActivityManager::class.java)
    val exits = activityManager.getHistoricalProcessExitReasons(
        context.packageName,
        0,
        20
    )

    return exits.any { info ->
        info.reason == ApplicationExitInfo.REASON_OTHER &&
            info.description?.contains("MemoryLimiter:AnonSwap") == true
    }
}

判断条件比较具体:reason 是 REASON_OTHER,description 里包含 MemoryLimiter:AnonSwap。只看 REASON_OTHER 不够,因为这个 reason 还会覆盖其他退出情况。

本地复现

Android 17 的行为变更文档里补了 am memory-limiter 命令。它可以查看当前 memory limiter 状态,也可以给某个进程手动设置限制。

先拿到包名对应的 pid:

bash 复制代码
adb shell pidof com.example.app

再看 memory limiter 当前状态:

bash 复制代码
adb shell am memory-limiter status

给目标进程设置一个较低限制,例如 300 MB:

bash 复制代码
adb shell am memory-limiter manual <pid> 300

如果要恢复系统默认限制:

bash 复制代码
adb shell am memory-limiter manual <pid> none

如果要移除当前进程上的所有限制:

bash 复制代码
adb shell am memory-limiter manual <pid> max

还有一个 ignore 子命令,用来让 memory limiter 忽略某个 UID、忽略全部,或者取消忽略:

bash 复制代码
adb shell am memory-limiter ignore <uid>
adb shell am memory-limiter ignore all
adb shell am memory-limiter ignore none

这些命令只在启用了 memory limiter 的设备上生效。如果设备本身不施加这类限制,命令不会产生实际影响。

R8 先打开

内存优化不要只盯着 heap dump。发布包里的代码、资源、反射 keep 规则,也会影响运行时常驻内存。

release 构建里至少要确认 R8 优化是打开的:

bash 复制代码
android {
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

这里不要继续用 proguard-android.txt。这个文件偏兼容旧行为,会阻止一部分优化;AGP 9 里也不再支持它。

再检查 gradle.properties 里有没有关闭 full mode:

bash 复制代码
# 如果项目里还留着这一行,删掉
android.enableR8.fullMode=false

proguard-rules.pro 里也要少用全局开关。下面这些写法会直接挡住 R8 对整个代码库的优化:

bash 复制代码
-dontoptimize
-dontshrink
-dontobfuscate

反射、序列化、三方 SDK 需要 keep 时,规则要收窄到具体类、字段或注解。比如只保护某个 JSON 模型包,通常比 -keep class com.example.** { *; } 更可控。

如果是 SDK 或组件库,消费者需要的规则放在 consumer-rules.pro,库内部为了自己编译和测试保留的规则放在模块自己的 proguard-rules.pro。这两个文件混在一起,会让接入方拿到过宽的 keep 规则。

图片和泄漏

图片是 Android 内存里很容易被低估的部分。一个压缩后只有几百 KB 的 PNG,解码成 ARGB_8888 后,内存按宽、高和每像素字节数计算。图片尺寸大,内存就会直接上去。

Compose 项目里用 Coil,View 项目里用 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 没注销、Compose DisposableEffect 没释放这类问题,比只看一段文本 trace 更快。

主动释放缓存

App 退到后台以后,系统可能回收一部分内存。问题是系统不一定知道哪些对象马上会用,哪些对象可以低成本重建。

可以在 Application 或组件里处理 onTrimMemory(),把 UI 相关缓存、图片缓存、临时 buffer 这类可重建对象释放掉。

bash 复制代码
class App : Application(), ComponentCallbacks2 {

    override fun onTrimMemory(level: Int) {
        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            imageMemoryCache.clear()
            videoPreviewCache.clear()
        }

        if (level >= ComponentCallbacks2.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 又加了事件触发能力。

这次和内存关系比较大的触发类型有两个:

bash 复制代码
ProfilingTrigger.TRIGGER_TYPE_OOM
ProfilingTrigger.TRIGGER_TYPE_ANOMALY

TRIGGER_TYPE_OOM 面向 OutOfMemoryError,在 OOM crash 发生时采集 Java heap dump。采集结果会在 App 下次启动并注册 registerForAllProfilingResults 回调后返回。

TRIGGER_TYPE_ANOMALY 面向系统识别出的严重性能异常,内存限制触发时可以在进程被杀前采集 heap dump。这个点适合补在"没有堆栈的系统杀进程"问题上。

最小接入只需要把结果回调接住,拿到文件路径后交给自己的上传任务:

bash 复制代码
val profilingManager = context.getSystemService(ProfilingManager::class.java)
val executor = Executors.newSingleThreadExecutor()

profilingManager.registerForAllProfilingResults(executor) { result ->
    if (result.errorCode == ProfilingResult.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 #Android17 #性能优化 #内存优化 #R8

相关推荐
传奇开心果编程9 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程11 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程11 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程11 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓11 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程11 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc12312 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
Dovis(誓平步青云)12 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物
五彩小白1 天前
自回归和上下文
android
事圆则缓1 天前
Android 多渠道打包实战:Product Flavors、签名、AAB 与发包校验
android