Android 低内存场景下进程管理优化调研文档

一、背景原因

1.1 问题背景

在 Android 应用的线上运行过程中,我们遇到了两个主要问题:

  1. Background 崩溃率偏高:当应用处于后台时,系统在内存压力下会触发 onTrimMemory 回调,此时如果应用仍在进行资源密集型操作(如布局 inflate、资源解析等),可能导致 SIGSEGV 崩溃。特别是在 TRIM_MEMORY_RUNNING_CRITICAL (level = 40) 时,系统可能已经清理了资源缓存,但应用代码仍在尝试访问这些资源,导致段错误。

  2. 进程被 kill 后重启不走 SplashActivity:部分手机在低内存时系统会 kill 后台进程,当用户重新打开应用时,系统会尝试恢复之前的 Activity 栈,导致应用跳过 SplashActivity 直接进入之前的页面。这不仅影响用户体验,还可能导致应用状态不一致的问题。

1.2 调研目标

  • 了解不同 Android 版本和厂商对 onTrimMemory 的处理差异
  • 研究进程被 kill 后系统恢复 Activity 栈的机制
  • 找到在低内存时主动 kill 进程的最佳实践,确保下次启动是冷启动
  • 降低 background 崩溃率

二、onTrimMemory 触发时机与 Level 说明

2.1 触发时机

onTrimMemory 是 Android 系统在内存压力大时通知应用释放资源的回调方法。系统会根据应用的状态(前台/后台)和内存压力程度,传递不同的 level 值。

2.2 Level 常量说明

前台应用相关 Level(已废弃,API 34+ 不再通知)

Level 常量 说明 状态
TRIM_MEMORY_RUNNING_MODERATE 5 进程不是可消耗的后台进程,但设备内存运行在中等偏低水平。运行中的进程应该释放一些不需要的资源供其他地方使用。 已废弃(API 34+)
TRIM_MEMORY_RUNNING_LOW 10 进程不是可消耗的后台进程,但设备内存运行在较低水平。运行中的进程应该释放不需要的资源,以允许该内存在其他地方使用。 已废弃(API 34+)
TRIM_MEMORY_RUNNING_CRITICAL 15 进程不是可消耗的后台进程,但设备内存运行在极低水平,即将无法保持任何后台进程运行。运行中的进程应该释放尽可能多的非关键资源,以允许该内存在其他地方使用。接下来会发生的是调用 onLowMemory() 来报告后台无法保留任何内容,这种情况可能会开始显著影响用户。 已废弃(API 34+)

后台应用相关 Level

Level 常量 说明 状态
TRIM_MEMORY_UI_HIDDEN 20 进程曾经显示用户界面,现在不再显示。此时应该释放与 UI 相关的大型分配,以允许更好地管理内存。 可用
TRIM_MEMORY_BACKGROUND 40 进程已进入 LRU 列表。这是清理可以高效快速重建的资源的良好机会(如果用户返回应用)。 可用
TRIM_MEMORY_MODERATE 60 进程位于后台 LRU 列表的中间位置;释放内存可以帮助系统保持列表中的其他进程运行,以获得更好的整体性能。 已废弃(API 34+)
TRIM_MEMORY_COMPLETE 80 进程接近后台 LRU 列表的末尾,如果很快找不到更多内存,它将被杀死。 已废弃(API 34+)

2.3 关键理解

  • TRIM_MEMORY_UI_HIDDEN (20):应用刚切到后台时触发,此时应该释放 UI 相关资源
  • TRIM_MEMORY_BACKGROUND (40):应用在后台 LRU 列表中,系统建议释放可重建的资源
  • TRIM_MEMORY_MODERATE (60)TRIM_MEMORY_COMPLETE (80):应用在后台 LRU 列表的中间或末尾,系统内存压力很大,应用很可能被系统 kill

三、调研结果

3.1 测试环境

Android 版本 设备型号 厂商
Android 16 华为荣耀 华为
Android 12 华为 mate60pro+ 华为
Android 10 小米3 小米

3.2 onTrimMemory 执行情况

Android 版本 App 退后台后 onTrimMemory 的执行情况
Android 16(华为荣耀) TRIM_MEMORY_UI_HIDDEN 后立刻 TRIM_MEMORY_BACKGROUND(大概率,偶尔只执行 TRIM_MEMORY_UI_HIDDEN
Android 12(华为 mate60pro+) TRIM_MEMORY_UI_HIDDEN
Android 10(小米3) TRIM_MEMORY_UI_HIDDEN

3.3 进程被 kill 后恢复行为调研

前台应用场景

操作方式 是否在 recent 里 重启是否恢复 Activity 栈 测试结果(Android 16/12/10)
崩溃后 三个版本行为一致
用代码 kill 进程后 三个版本行为一致
先删除 Activity 栈再用代码 kill 进程后 三个版本行为一致

后台应用场景

操作方式 是否在 recent 里 重启是否恢复 Activity 栈 测试结果(Android 16/12/10)
崩溃后 三个版本行为一致
用代码 kill 进程后 ⚠️ 三个版本行为一致
先删除 Activity 栈再用代码 kill 进程后 三个版本行为一致

⚠️ 关键发现:后台应用直接 kill 进程会恢复 Activity 栈,这是导致跳过 SplashActivity 的根本原因。

3.4 关键发现

  1. 后台应用直接 kill 进程会恢复 Activity 栈:当应用在后台时,如果直接使用 Process.killProcess() kill 进程,系统会在 recent 中保留应用,并且重启时会恢复之前的 Activity 栈。这是导致跳过 SplashActivity 的主要原因。

  2. 先清理 Activity 栈再 kill 进程可以避免恢复:无论是前台还是后台,如果先调用 finishAndRemoveTask()killAllActivity() 清理 Activity 栈,再 kill 进程,系统不会在 recent 中保留应用,也不会恢复 Activity 栈。

  3. 崩溃不会恢复 Activity 栈:无论是前台还是后台,应用崩溃后系统不会恢复 Activity 栈,这是因为崩溃时系统没有机会保存状态。


四、最佳实践方案

4.1 实现方案

基于调研结果,我们实现了以下方案:

4.1.1 在 onTrimMemory 中主动 kill 进程

@Override
public void onTrimMemory(int level) {
    super.onTrimMemory(level);
    
    // 如果应用在后台且内存压力大,主动kill进程,避免资源被清理导致的崩溃
    if (level >= Activity.TRIM_MEMORY_BACKGROUND) {
        // 检查应用是否在后台
        boolean isBackground = !SystemUtil.isRunningForeground(this) || HyAppLifeCycle.background;
        if (isBackground) {
            // 调用 killProcess 方法
            killProcess();
        }
    }
    
    // 其他清理操作...
    if (level >= Activity.TRIM_MEMORY_UI_HIDDEN) {
        TtfIconConverter.Companion.getInstance(mContext).clearCache();
        Glide.get(this).onTrimMemory(level);
    }
}

4.1.2 killProcess 方法实现

// 标记是否正在执行清理和 kill 进程的逻辑,避免多次收到 onTrimMemory 重复处理
private static volatile boolean isKillingProcess = false;

public void killProcess(){
    // 如果已经在执行清理和 kill 进程的逻辑,不重复处理
    if (isKillingProcess) {
        LogUtil.d("HyApp", "killProcess: 已在执行 kill 进程逻辑,跳过重复处理");
        return;
    }

    // 设置标记,避免重复处理
    isKillingProcess = true;
    LogUtil.postBuglyException(new Throwable("killProcess: kill self after 500ms!"));

    // 延迟一小段时间再处理,确保日志上报完成
    // 在延迟回调中清理 Activity 栈和 kill 进程
    new Handler(Looper.getMainLooper()).postDelayed(() -> {
        try {
            // 先清理 Activity 栈,避免系统保存状态,确保下次启动是冷启动
            try {
                FragmentActivity topActivity = ActivityStackManager.getInstance().getTopActivity();
                if (topActivity != null) {
                    // 使用 finishAndRemoveTask 清除任务栈,避免系统恢复
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        topActivity.finishAndRemoveTask();
                    } else {
                        topActivity.finish();
                    }
                }
                // 清理所有 Activity
                ActivityStackManager.getInstance().killAllActivity();
            } catch (Exception e) {
                LogUtil.e("HyApp", "清理 Activity 栈失败", e);
            }

            // 执行 kill 操作
            LogUtil.d("HyApp", "killProcess: 执行 kill 进程");
            android.os.Process.killProcess(android.os.Process.myPid());
        } catch (Exception e) {
            LogUtil.e("HyApp", "延迟处理 kill 进程逻辑时发生异常", e);
            // 发生异常时重置标记
            isKillingProcess = false;
        }
    }, 500);
}

4.2 方案要点

4.2.1 触发条件

  • Level 判断level >= Activity.TRIM_MEMORY_BACKGROUND (40)

    • 覆盖 TRIM_MEMORY_BACKGROUND (40)、TRIM_MEMORY_MODERATE (60)、TRIM_MEMORY_COMPLETE (80)
    • 这些 level 都表示系统内存压力大,应用在后台时主动 kill 是合理的
  • 后台检查:确保应用在后台时才执行 kill 操作

    • 使用 SystemUtil.isRunningForeground() 检查应用是否在前台
    • 使用 HyAppLifeCycle.background 作为补充判断

4.2.2 防重复处理

  • 使用 volatile boolean isKillingProcess 标记,避免多次收到 onTrimMemory 时重复处理
  • 在开始处理时设置标记,在异常时重置标记

4.2.3 清理 Activity 栈

  • 先清理再 kill:在 kill 进程前先清理 Activity 栈
    • 使用 finishAndRemoveTask() (Android 5.0+) 或 finish() 清除任务栈
    • 调用 killAllActivity() 清理所有 Activity
    • 这样可以避免系统保存状态,确保下次启动是冷启动

4.2.4 延迟执行

  • 延迟 500ms 再执行 kill 操作
    • 确保日志上报完成
    • 给清理操作足够的时间
    • 如果用户在延迟期间切回前台,可以在延迟回调中再次检查(可选)

4.3 方案优势

  1. 降低崩溃率:在内存压力大时主动退出,避免资源被清理导致的 SIGSEGV 崩溃
  2. 确保冷启动:先清理 Activity 栈再 kill 进程,确保下次启动走 SplashActivity
  3. 用户体验:只在后台时 kill,对用户无感知
  4. 资源释放:主动退出可以释放内存,帮助系统恢复

4.4 注意事项

  1. 数据保存:确保在 kill 进程前,关键数据已经保存
  2. 日志记录:记录 kill 原因,便于问题追踪
  3. 异常处理:添加异常处理,确保即使清理失败也能正常退出
  4. 版本兼容:注意 finishAndRemoveTask() 需要 Android 5.0+

五、总结

5.1 调研结论

  1. 不同 Android 版本和厂商对 onTrimMemory 的处理有差异

    • Android 16(华为荣耀)会在退后台时连续触发 TRIM_MEMORY_UI_HIDDENTRIM_MEMORY_BACKGROUND
    • Android 12 和 Android 10 通常只触发 TRIM_MEMORY_UI_HIDDEN
  2. 后台应用直接 kill 进程会恢复 Activity 栈

    • 这是导致跳过 SplashActivity 的根本原因
    • 必须先清理 Activity 栈再 kill 进程
  3. 先清理 Activity 栈再 kill 进程可以避免恢复

    • 无论是前台还是后台,都能确保下次启动是冷启动
    • 不会在 recent 中保留应用

5.2 实施方案

通过在 onTrimMemory 中检测到内存压力大且应用在后台时,主动清理 Activity 栈并 kill 进程,可以:

  • 降低 background 崩溃率:避免在资源被清理时访问已释放的内存
  • 确保冷启动:避免系统恢复 Activity 栈,确保每次启动都走 SplashActivity
  • 提升用户体验:在后台时主动退出,对用户无感知,且有助于系统恢复

5.3 后续优化建议

  1. 监控效果:上线后监控 background 崩溃率的变化
  2. 数据统计:统计主动 kill 进程的次数和场景
  3. 用户反馈:关注用户反馈,确保方案不影响正常使用
  4. 持续优化:根据线上数据持续优化触发条件和清理逻辑
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容