Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help


title: AppFlow 研究原型:GB 级应用冷启动内存联合调度 chapter: '18.8' section: '18.8' status: finalized applicable_versions: Android 15 - Android 17(研究原型,非 AOSP 主线) last_verified: '2026-08-14' last_verified_against: AppFlow arXiv 2603.17259v1; Android Developers startup/ComponentCallbacks2/LMK docs; AOSP android-17.0.0_r1 lmkd/ProcessList/CachedAppOptimizer/UsageStatsManager/ApplicationExitInfo; Android Common Kernel android17-6.18-2026-06_r6 mm/vmscan.c; Linux PSI docs confidence: high pipeline_stage: ready-to-publish task6_state: reviewed sources:

  • type: paper path: https://arxiv.org/abs/2603.17259
  • type: paper path: https://arxiv.org/html/2603.17259v1
  • type: note path: Android-2026-05-23-AppFlow-ColdLaunch/03-精读.md
  • type: research-feed path: intake/research-feeds/2026-04-02-15-ch05-appflow-cold-launch-scheduler.md
  • type: official path: https://source.android.com/docs/core/perf/lmkd
  • type: official path: https://developer.android.com/topic/performance/vitals/launch-time
  • type: official path: https://developer.android.com/topic/performance/vitals/lmk
  • type: official path: https://developer.android.com/reference/android/content/ComponentCallbacks2
  • type: kernel-doc path: https://docs.kernel.org/accounting/psi.html
  • type: aosp path: https://android.googlesource.com/platform/system/memory/lmkd/+/refs/tags/android-17.0.0_r1/lmkd.cpp
  • type: aosp path: https://android.googlesource.com/platform/system/memory/lmkd/+/refs/tags/android-17.0.0_r1/include/lmkd.h
  • type: aosp path: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-17.0.0_r1/services/core/java/com/android/server/am/ProcessList.java
  • type: aosp path: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-17.0.0_r1/services/core/java/com/android/server/am/CachedAppOptimizer.java
  • type: aosp path: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-17.0.0_r1/core/java/android/app/usage/UsageStatsManager.java
  • type: aosp path: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-17.0.0_r1/core/java/android/app/ApplicationExitInfo.java
  • type: kernel path: https://android.googlesource.com/kernel/common/+/refs/tags/android17-6.18-2026-06_r6/mm/vmscan.c tags:
  • aosp-performance
  • cold-start
  • memory-scheduling
  • lmkd
  • file-preload related_chapters:
  • '4.3'
  • '6.2'
  • '8.2'
  • '18.6'
  • '21.1'

AppFlow 研究原型:GB 级应用冷启动内存联合调度

GB 级应用冷启动会同时竞争文件页、匿名页、CPU 与进程生存空间,单点预读可能把延迟转移成更强的内存压力。AppFlow 的价值和边界需要从论文证据出发,分别审视预加载、回收和杀进程策略如何协同。

证据边界

AppFlow 是 MobiCom 2026 论文提出的研究原型,它同时协调文件预读、内存页回收和后台进程终止。 当前可公开核查的是 2026 年 3 月发布的 arXiv v1;实验系统以 Android 15 为基础,部署在 Pixel 7、Pixel 8 和 Raspberry Pi 4B 车载试验台上。 论文没有公开完整的 Framework 与 kernel patch:前者指 Android 系统服务层改动,后者指 Linux 内核改动。AppFlow 也没有进入 Android 17 / API 37 的 AOSP 主线。以下内容使用两个独立锚点:

  • 原型的设计与实验数字,以 AppFlow 论文 为准;
  • 平台已有能力,以 android-17.0.0_r1 与 kernel android17-6.18-2026-06_r6 为准。

128KB 文件分界、100MB 预加载预算、每 100ms 统计 12,800 个分配页等值来自论文参数搜索。 66.5%、67.9%、95% 等结果也只描述论文设备、应用集合和测试方式,不能当作 Android 17 的默认配置或性能承诺。

冷启动为什么会变成系统问题

Android 官方把 cold start(冷启动)定义为从头创建应用进程,并完成应用和首个 Activity 的初始化。系统终止后台进程后再次打开应用,也属于冷启动。 TTID(time to initial display)衡量首帧出现时间;TTFD(time to full display)从系统收到启动 intent 开始计时,到应用调用 reportFullyDrawn() 为止,用来表示应用已经完整显示并可用。 TTFD 依赖应用在正确时机主动上报,系统无法自动判断业务是否真的可操作。 大型应用还可能在 TTFD 之后加载模型、贴图或媒体素材,因此实验应另记“关键功能首次可用”的业务时刻。

一次大型应用冷启动可以粗略拆成三段成本:

成本形成方式常见证据
CPU 与调度进程创建、类加载、初始化、已编译代码执行、线程竞争sched、主线程 slice(trace 时间片)、ART(Android 运行时)与 Binder(进程间调用)trace
文件 I/OAPK、DEX、SO、资源、模型、贴图或数据库页不在 page cache(文件页缓存)major fault(需要存储读取的缺页)、块设备 I/O、线程 D 状态、文件系统 trace
内存分配与回收新分配触发 kswapd、direct reclaim、内存规整或 swapPSI、pgscan_*allocstall、zRAM、workingset refault

kswapd 是内核后台回收线程;direct reclaim 表示发起内存分配的线程同步参与回收,应用可能直接等待。 内存规整(compaction)尝试得到连续物理页,不等同于 zRAM 的数据压缩;swap 会把匿名页移出 DRAM,Android 常把它们压入 zRAM。 线程处于 D 状态时正在不可中断睡眠,常见原因是等待块设备或内核 I/O。 pgscan_* 统计回收器扫描的页面数,allocstall 统计内存分配因同步回收而停顿的次数;workingset refault 表示工作集页面逐出后很快又被访问,常用来识别缓存抖动。

多任务会让三段成本互相影响。预读会占用 page cache;内存回收可能在使用前逐出刚读入的页;匿名页换出会与预读竞争存储带宽。 lmkd 终止后台应用后,用户返回时还要重新创建进程并加载文件。只分析 Application.onCreate() 会漏掉这些系统等待。

论文用 T_cold = T_I/O + T_cpu + T_alloc 表示问题,并把系统可调部分放在 I/O 与分配等待上。 这是分析模型,三段在设备上可能并行或互相阻塞,不能直接把 trace 中的 wall time(端到端经过时间)按公式相加。

论文给出的四个观察

AppFlow 的设计来自论文样本中的四组测量。引用这些数据时,应同时保留样本语境。

论文观察论文报告的数据能支持的判断
启动访问具有重复性24 个应用中,97.2% 的 cold-launch I/O wait 涉及可预测静态数据稳定资源存在预读机会
切换期 I/O 有空闲论文原文称 79% 的 “DRAM I/O bandwidth” 未使用作者的实验窗口存在预读机会,不能直接推导目标设备的 UFS 或内存带宽余量
小文件数量多、体积小图 5 汇总为数量约 4.7 倍、内存占比 2.2%;TikTok 个例为 1,002 个、数量 7.47 倍、23MB、约 4%小文件适合在有限预算内提前读
预读和回收会互相抵消低内存下,预读页被回收后启动 I/O 延迟增至 6.4 倍只加 prefetch(预读)可能增加压力与重复读取

论文正文在汇总值与 TikTok 个例之间使用了不同数字。Figure 5 的汇总值是 4.7 倍和 2.2%;TikTok 个例是 7.47 倍、23MB、约 4%。 工程文档应注明数字来自图表汇总还是单个应用,不能交叉拼接后再当成所有应用共有的常数。

论文还引用既有研究中的“30 分钟内再次访问 92.5%”,并报告 Android 基线终止了其中 62% 的高概率应用。 前一项不是 AppFlow 自己的 100 天数据,后一项依赖论文的 workload(应用组合与切换序列)和判定方式。产品预测器需要用自身用户群、场景和隐私约束重新训练与验证。

Selective File Preloader

预加载器处理“何时读、读哪些内容、每次读多大”三个问题。

启动前:频次优先

系统从历史 launch trace(启动期间的文件路径、偏移和访问时间记录)提取稳定访问的文件区间,在应用启动前读入小文件以及大文件中反复访问的区间。 小文件会产生较多离散请求,但总字节数较小,适合在严格预算下减少同步等待。

论文为每个应用选择文件大小分界 s_i,并把候选值限制在 4KB、8KB 等 2 的幂。每个候选值都对应预计预读体积 P(s_i) 和启动期 I/O 吞吐 V(s_i)。 随后使用 multiple-choice knapsack(多选背包):每个应用只能从自己的候选分界中选一个,同时让所有应用的预读体积不超过全局预算。 论文把启动前总预算设为 100MB,求解 60 个应用的配置耗时低于 0.1ms。

128KB 只是论文观察和搜索空间中的代表值,具体应用仍有自己的 s_i。 若产品规则固定预读所有小于 128KB 的文件,就会忽略压缩包内部偏移、资源版本、F2FS/ext4 文件系统布局、UFS 请求队列、16KB page size(内存页大小)和应用更新造成的访问变化。

启动中:吞吐优先

应用开始启动后,预加载器用较大块读取余下的大文件。论文把它放在独立低优先级进程中,希望预读跟不上时给前台读取让路。 低调度优先级只能减少 CPU 竞争,无法保证 block 层或文件系统一定先完成前台请求。 产品实现还要观测 I/O priority(存储请求优先级)、queue depth(同时在途的请求数)和读放大(实际读取量与最终使用量之比)。

历史访问记录何时失效

文件路径相同不代表内容和访问区间稳定。以下变化应降低历史记录的权重,严重时应直接失效:

  • APK、APEX、资源包、模型或动态特性更新;
  • 用户登录态、语言、主题、A/B 实验改变启动路径;
  • 低内存模式、车载用户切换或屏幕形态改变资源选择;
  • 加密状态、文件系统、page size 或存储固件发生变化;
  • 预读命中后仍出现较高 refault(文件页逐出后很快又被访问)或前台 I/O 延迟。

记录键至少包含包版本、build fingerprint(系统构建标识)、ABI(应用所用指令集接口)、page size 与资源版本。回滚时应能立即停用预测与预读,不能依赖清空用户数据恢复。

Adaptive Memory Reclaimer

预读成功只说明数据进入 page cache;启动线程使用它之前,内核仍可能回收这些文件页。AppFlow 因此修改了 Linux 内核的回收路径。

论文原型的两种控制

论文每 100ms 读取一次可用内存和页分配计数。 当可用内存低于设备阈值、最近窗口分配量高于 N_alloc 时,原型先进入 file-first 阶段,优先扫描有文件后备的页;论文参数搜索得到的 N_alloc 是 12,800 页。 这个门槛按页计数,换算成字节取决于 page size:4KB 页约为 50MiB,16KB 页约为 200MiB。 file-backed 页扫描完成后,原型进入 anonymous rebalance,重新提高匿名页的回收份额;压力窗口结束后恢复常规策略。该顺序符合论文算法,也避免匿名页长期堆积。

对预加载页,原型按两个窗口处理:

  • 启动前读入的少量页每 10 秒访问一次,用来刷新内核记录的活跃度;这不会锁住页面,应用活跃后也会停止;
  • 启动中读入的文件通过 /proc(内核导出的伪文件系统)发布清单。原型修改 shrink_page_list() 回收函数,扫描到清单中的页时暂不逐出,并把页标为 active;启动完成后清除清单。

这项行为来自 kernel patch。readahead() 可以提前把文件内容读入 page cache,madvise(MADV_WILLNEED) 可以提示内核即将访问某段映射。 两者都不保证页面一直留到应用使用时刻,普通 page-cache 访问也没有这种保证。 论文报告一次活跃度刷新约耗时 10ms,CPU 开销约 0.1%;目标 SoC(系统芯片)、文件集合和电源状态变化后仍需复测。

Android 17 kernel 6.18 的现状

android17-6.18-2026-06_r6/mm/vmscan.c 没有 AppFlow 的预加载文件清单或启动窗口。 classic LRU(传统的近似最近使用链表)路径中的 get_scan_count() 会综合 swap 能力、swappiness(匿名页与文件页的相对回收权重)、reclaim priority 和 file LRU 大小。 它还会参考 refault 成本与 cache-trim 状态,最终选择 SCAN_FILESCAN_ANONSCAN_EQUAL 或比例扫描。 启用 MGLRU(Multi-Generational LRU,按访问代际管理页面)时,则进入另一套老化和逐出路径。

其中,reclaim priority 表示当前回收轮次的扫描强度,file LRU 大小表示文件页链表规模;cache-trim 状态表示系统有足够的非活跃文件缓存可优先回收。

论文对其 Android 15 基线“文件页与匿名页交替回收”的概括,不能直接套到 Android 17 kernel 6.18。 Android 17 已经会按运行状态调整扫描比例,但内核仍不知道“某个文件页将在本次应用启动中使用”。AppFlow 增加的正是这种短时启动上下文。

file-backed 页也有不同成本。干净文件页可以直接丢弃,脏文件页要先回写;被丢弃的页若很快 refault,会再次触发存储读取和 stall(任务因资源不足而停顿)。 匿名页可能压入 zRAM,代价由压缩算法、CPU、swap 空间和后续 swap-in(把页读回内存)共同决定。回收策略不能只按 file/anon 二分。

Context-Aware Process Killer

当页回收不能及时释放足够内存时,AppFlow 再选择后台进程。论文使用两类上下文:

  • 近期使用过的应用具有较高返回概率,延后终止;
  • 长时间运行且内存相对刚启动基线增长 30%~50% 的应用,重启后缓存和临时分配会回到较低水平。

论文用 ΔM = M_current - M_relaunch 估算一次终止带来的长期净释放量,并优先选择 ΔM 较大的候选。 M_relaunch 表示应用被终止后再次冷启动的内存基线。这个量需要记录应用重新启动后的历史数据,仅凭当前 RSS/PSS 的大小,无法推导出重启后的占用。 RSS 是进程映射到物理内存的页面总量,PSS 会按比例分摊共享页;两者都不完整覆盖 GPU 内存。 memcg(memory cgroup)的记账归属、isolated process(使用隔离 UID 的辅助进程)和独立服务进程也会让“应用占用”难以由单一 PSS 表示。

候选集合仍要服从 Android 的进程重要性规则。 导航、通话、音频、可感知前台服务、设备管理、车载安全界面和系统持久进程,不能因为预测分数低就进入普通 cached process 的可终止集合。 预测器只能给系统原本允许终止的候选重新排序,还要限制同一应用连续成为候选的次数,并设置公平性和最大等待时间,避免长期得不到运行机会的 starvation(饥饿)。

Android 17 r1 的 LMKD 基线

Android 17 r1 使用 userspace lmkd,也就是在用户空间运行的低内存终止守护进程。 源码同时保留三类路径:以 PSI(Pressure Stall Information,压力停顿信息)检测内存压力的新策略; 依据 minfree 阈值的 legacy(兼容旧设备)策略;以及检测旧版内核 LMK 接口的兼容分支。 它们都属于同一个守护进程;AOSP 没有名为“LMKD v2”的正式组件或第二个守护进程。

压力检测

r1 默认使用 PSI,设备可以通过属性覆盖。源码默认值如下:

参数常规设备low-RAM 设备含义
psi_partial_stall_ms70ms200ms1,000ms 窗口内 PSI some 的停顿门槛
psi_complete_stall_ms700ms700ms1,000ms 窗口内 PSI full 的停顿门槛
thrashing_limit100%30%workingset refault 相对 file page cache 的比例门槛
thrashing_limit_decay10%50%因 thrashing 终止进程后,下一轮门槛的下调比例

PSI some 表示窗口内至少有部分任务因内存不足而停顿,full 表示所有非空闲任务同时停顿。 变量名使用 partial/complete,Linux /proc/pressure/memory 中对应的字段名则是 some/full。表内数字是 r1 源码默认值,产品属性可以覆盖;low-RAM 的 partial 默认值更高,代表触发较不敏感。 旧材料中的“LOW 70 / MEDIUM 100 / CRITICAL 70”不对应 r1 的 init_psi_monitors() 配置,不应继续沿用。

lmkd 收到压力事件后还会检查 zone watermark(各内存区域的空闲页水位)、swap、thrashing(页面反复逐出又读回)和 direct reclaim 等状态。 PSI 提供唤醒和压力严重度信号,具体终止哪个进程还要经过候选选择。

候选进程和 oom_score_adj

ActivityManagerService(AMS)根据进程状态、组件关系和用户可感知性计算 oom_score_adj,数值越高通常越容易成为低内存终止候选。 ProcessList.setOomAdj()batchSetOomAdj() 把结果发给 lmkdlmkd 从较高 adj 分组向较低分组查找;kill_heaviest_task=false 时通常取该分组队尾,配置为 true 时选取组内占用较大的进程。 源码还规定,当搜索进入 PERCEPTIBLE_APP_ADJ 或更重要的范围时,选择逻辑会强制切换为 heaviest,希望用更少的终止次数释放足够内存。

adj 表达 Android 组件此刻的重要性。 若把“预计稍后会打开”的应用伪装成 perceptible(用户能感知)或 visible(界面可见),会破坏 AMS、LMKD 和 cached process 管理共同使用的优先级约定。 这种做法也可能把更多内存压力转移给真正可感知的进程。

LMK_PROCS_PRIO 的准确含义

Android 17 r1 的 LMK_PROCS_PRIO 是 framework 到 lmkd 控制 socket(进程间通信通道)的内部批量协议:

  • include/lmkd.h 把每个控制包的记录上限定为 3;
  • ProcessList.batchSetOomAdj() 读取每个 ProcessRecordInternal.getCurAdj(),每 3 个进程发送一包;
  • lmkd.cpp::cmd_procs_prio() 在收包线程内循环调用 apply_proc_prio()
  • r1 lmkd.cpp 没有 io_uring 处理器,也没有 32 条记录的主线协议。

这项批处理可以减少控制消息次数,但没有提供“保护将启动应用”的策略接口。它不能改变文件页回收,也不能接收 AppFlow 的 ΔM、访问概率或预加载清单。 io_uring 是 Linux 的异步 I/O 接口,AOSP 17 r1 的 lmkd.cpp 没有相关处理器。移植设计若参考第三方 fork(从 AOSP 分出的代码分支),必须先区分分支扩展与主线能力。

全局 LMKD 属性不能代替预测器

ro.lmk.lowmem_min_oom_scorero.lmk.kill_heaviest_task、thrashing 门槛等属性影响整台设备。 调高最低可终止 adj 可能使 lmkd 在严重压力下找不到足够候选,增加 direct reclaim、系统 stall 或 kernel OOM(内核因内存耗尽而终止进程)的风险;调低值会扩大候选集合。 这些全局属性无法按应用或启动窗口动态表达预测结果。

am set-isolated-process-uid-list 管理 isolated process 的 UID 范围,与后台应用保护、文件页预读或 LMKD 候选排序无关。

CachedAppOptimizer 能做什么

CachedAppOptimizer 在 Android 17 r1 中负责 cached app compaction 和 freezer。 这里的 compaction 是针对目标进程回收文件页或匿名页的操作,SOME/FULL/ANON profile 决定处理哪类页面;它不同于前文为取得连续物理页而进行的内核内存规整。 freezer 会暂停 cached 进程的调度,并先处理该进程的 Binder 通信,恢复使用时再解冻。这个类不记录启动文件热集,也不会在 vmscan 中保护预读文件页。

若要让 AppFlow 的进程终止分数影响 CachedAppOptimizer,仍需修改 AMS 的进程策略,还要验证 compaction、freezer、adj 更新和 LMKD 选择之间没有 race condition(执行先后变化导致结果不一致)。 源码中也没有可直接复用的 AppFlow hook(扩展入口)。

原型组件与 Android 17 能力映射

AppFlow 组件Android 17 已有基础主线缺失部分需要的权限或改动
Selective File Preloader应用启动事件、文件 I/O、page cache、UsageStats跨应用文件访问记录、两阶段预算器、启动预测服务平台服务、文件访问权限、预读执行器
Adaptive Memory Reclaimerclassic LRU/MGLRU、zRAM、memcg、PSI、vmscan启动窗口、预加载文件标记、逐出跳过规则kernel core mm patch 与接口
Context-Aware Process KillerAMS adj、lmkd、cached LRU、PSS/RSS 采样relaunch 基线、返回概率、净释放排序Framework 与 lmkd 策略改动
验证与回滚Perfetto、statsd(系统指标收集服务)、ApplicationExitInfo、系统属性AppFlow 专用原因码、命中率和页保护统计指标记录、实验开关、版本化访问记录

UsageStatsManager.queryEvents() 需要 PACKAGE_USAGE_STATS,在 AndroidManifest.xml 声明后还需用户到设置中授予 usage access(使用情况访问权);只查询本包事件的接口不需要该权限。 论文使用后台采集应用,不代表普通三方应用可以无提示收集整台设备的应用使用序列。系统镜像内的预测服务也要遵守多用户、工作资料、访客和数据保留期限。

三种实施权限下的可做范围

普通应用

应用可以优化自己的初始化、生成 Baseline Profile、记录 TTID/TTFD、采样自己的文件访问,并控制自己的资源读取。 TRIM_MEMORY_UI_HIDDEN 表示应用 UI 从可见变为不可见,应用可在该回调中释放能够重建的界面缓存。 从 API 34 起,系统不再发送多档运行中内存压力回调;API 35 将相应常量标记为 deprecated,但没有废弃 TRIM_MEMORY_UI_HIDDEN。 应用还可用 ApplicationExitInfo 查询此前的退出原因。

普通应用不能访问 lmkd 控制 socket、写其它进程的 oom_score_adj、修改 vmscan、保护 page cache 中的指定文件,也不能默认读取其它应用的使用历史。 应用内预读可能有收益,但它不等同于 AppFlow。

特权 Framework 原型

系统厂商可以在 platform service(系统服务)中采集经过授权的应用切换序列,维护带版本号的文件访问记录,并在 launch observer(启动事件观察器)收到事件前后调度预读。 这个阶段可以验证预测准确率、I/O 干扰、100MB 级预算是否适合目标设备,以及进程终止模型是否有稳定信号。

若 kernel 未改,预读页随时可能按常规策略被回收。 实验报告要分别记录“读取内容最终被使用”和“页面一直留到使用时刻”,避免把偶然的 page-cache 命中算成回收保护效果。这个阶段也不应随意降低 adj 来模拟后台存活收益。

产品级 OS 与 kernel 原型

要复现论文完整设计,需要同时修改 Framework 和 kernel:建立启动会话 ID;传递文件或 inode/offset(文件对象编号与字节位置)标识;在 classic LRU 和 MGLRU 中都定义短时保护。 还要处理文件截断、更新、卸载、memcg 迁移和多用户隔离;最后把进程排序限制在 AMS/LMKD 已允许终止的候选集合内。

GKI(Generic Kernel Image,通用内核镜像)允许 vendor module 通过受控 hook 扩展部分功能,但模块不能直接替换 core mm/vmscan.c 的回收语义。 修改 core mm 意味着长期维护产品 kernel 分支、内核 ABI(二进制接口)和安全更新合并。 验证范围还要覆盖 CTS/VTS/GTS(应用兼容、厂商接口和 Google 认证测试)、SELinux 权限、OTA 升级、suspend/resume(休眠与唤醒)、low-RAM 设备和 kernel OOM 回归。

如何设计一轮可信实验

固定启动口径

测试脚本应记录目标应用在每轮前的进程状态和 page-cache 条件。 cold 表示应用进程不存在;hot 表示进程和 Activity 都仍在内存中,只需回到前台;warm 处于两者之间,例如进程仍在但 Activity 要重建。 三类样本必须分组,不能只根据 am start -W 的 ActivityManager 时间输出推断缓存状态。Android 官方的 TTID/TTFD、Jetpack Macrobenchmark 的启动模式和 Perfetto launch slice 可以互相校验。

清空全局 page cache 只适合受控实验室镜像,因为它会改变所有进程和文件系统状态;线上设备不能用这种方式制造 cold 样本。 线上应观察自然发生的冷启动,并按上次退出原因、包版本、设备 uptime(自开机起的运行时间)和内存压力分层。

建立负载组合

每个设备至少覆盖:

  • 空闲、常规多任务、内存紧张和 swap 接近上限;
  • 4KB/16KB page size、不同 DRAM 档位和存储型号;
  • 首次安装、普通版本、应用更新后、系统 OTA 后;
  • 屏幕亮灭、充电/电池、温度和 thermal throttling(温控降频);
  • 前台导航、音频、通话、端侧模型推理等不可随意中断的并发任务。

每种组合都要报告样本数、P50/P90/P95/P99(有 50%/90%/95%/99% 样本不超过该值)、置信区间和异常值规则,平均数仅作补充。

同时观察四组证据

目标指标反例信号
启动变快TTID、TTFD、关键功能可用时刻、cold relaunch(后台进程被终止后的冷重启)比例首帧更快但关键功能更晚可用
预读有效预读字节、使用字节、major fault、block wait、refault读放大、命中低、前台 I/O 被拖慢
内存更稳memory PSI、pgscan_direct、allocstall、zRAM、swap-in/outstall 下降但 swap 或功耗大增
多任务可接受LMK 次数、后台存活、短时间 relaunch、用户可感知 LMK保护目标应用后其它任务更早被终止

ApplicationExitInfo.REASON_LOW_MEMORY 可用于应用侧回看,但并非所有设备都支持该原因。 ActivityManager.isLowMemoryKillReportSupported() 返回 false 时,低内存终止可能只记录为 REASON_SIGNALEDSIGKILLApplicationExitInfo 也不包含完整的 LMKD 候选评分,平台实验仍需结合 lmkd 日志、statsd 和 trace。

分离三个组件的贡献

至少保留四组:

  1. Android 17 原生基线;
  2. 只有 Selective File Preloader;
  3. Preloader 加 Adaptive Memory Reclaimer;
  4. 三个组件全部开启。

AppFlow 论文的 ablation(逐个关闭组件的消融实验)显示,只开 SFP 时启动变快,但 cold relaunch 数增加 30%。可以理解为预读带来的内存压力抵消了一部分收益。 按组件分组后,才能区分“读取时间提前”“页面保留更久”和“后台进程选择变化”各自造成的影响。

论文实验结果该怎样阅读

论文在 Android 15 原型上修改约 1,672 行 Framework 和 1,107 行 Linux kernel 代码。 Pixel 设备通过修改 arm64_memblock_init(),把内核可见内存限制为 6GB 或 8GB;这是受控实验配置,不代表设备实际只有这些物理内存。Raspberry Pi 4B 车载试验台使用 4GB 内存。 测试包含 60 多个应用、模拟多任务负载,以及一条记录应用使用顺序的 100 天 trace。

场景论文报告结果限定条件
论文摘要cold-launch latency(冷启动耗时)最多下降 66.5%,示例从 2s 降到 690ms;100 天中 95% 的启动小于 1s作者原型与工作负载
Pixel 8 cold launch相对 Android 平均下降 33.7%~43.6%;6GB 高负载个例最多下降 57%8 个 GB 级应用,三档负载
17 应用多任务保留后台应用从 7/17 增到 13/17,平均 relaunch 时间下降 37.6%Pixel 7,6GB/8GB,高负载
高压力机制指标direct reclaim 次数下降 67.9%,LMK 事件下降 33.7%Pixel 8 6GB 高负载
100 天案例平均 cold-launch latency 下降 23%,GB 级 cold relaunch 次数下降 31.6%一条 60+ 应用 trace
车载案例4.3s 降到 2.0s,下降 53.4%BYD Seal 供电、Pi 4B 计算、5 个应用
端侧生成式负载后台终止数从 8~9 个降到 0~3 个,survivability(任务存活率)从 66.6% 升到 100%Pixel 8 8GB 的论文场景

这些结果展示了原型潜力,也带有明确的复现限制:公开论文没有提供完整 Android/kernel patch、每个参数的产品配置、全部原始 trace 和功耗数据。 am start -W、清文件缓存和人为限制内存也不同于线上自然发生的启动。产品评估应把论文作为待验证假设,再在目标设备和真实负载上重新测量。

与 Baseline Profile、Cloud Profile 的关系

Profile 与 AppFlow 处理不同成本:

技术主要对象主要收益证据无法解决的部分
Baseline/Startup Profile热方法、类、DEX 布局与预编译CPU running(线程实际执行)时间、JIT/解释执行、class load模型和资源文件不在 page cache
Cloud Profile用户群聚合后的 ART profile 与 dexopt编译 artifact(编译产物)、启动 CPU 和代码布局当前设备的内存压力与后台存活
AppFlow启动文件页、回收窗口、后台进程选择major fault、I/O wait、refault、LMK、relaunch应用自身重初始化与低效业务逻辑

同一次启动可能同时受 CPU 和 I/O 限制。做 A/B 实验时,应先固定 dexopt 状态和 Profile 版本,再比较预读策略;评估 Profile 时也要记录 page-cache 和后台进程状态。 缺少这些控制变量,就无法判断收益来自代码编译、文件缓存还是后台进程存活。

车载、端侧模型和大型游戏

车载系统有导航、媒体、语音、仪表、多屏和多用户并发,后台进程的重要性不能仅按最近使用时间推断。 冷启动优化需要服从驾驶安全和音视频连续性,预测失误的代价比手机社交应用场景更高。

端侧模型的权重常通过 mmap 映射文件,使访问到的权重页成为 file-backed 页面;推理中间张量和 runtime(模型运行时)的堆又会增加匿名页。 页面类型取决于具体 runtime,不能笼统地把模型权重都算作匿名页。 大型游戏同样混合 APK/asset pack(按需分发的资源包)、native heap(原生代码堆)、图形驱动和 GPU 分配,单看进程 RSS 会漏掉关键资源。

这三类负载适合验证 AppFlow 提出的联合问题,但进程终止规则需要按角色设置硬约束:导航、通话、正在播放音频和安全相关服务不得由预测模型降级。 模型或游戏只有在温度、电量和前台 I/O 都满足启用条件时才能预读。

产品化风险与退出条件

风险典型后果保护与退出条件
预测错误读入未使用数据,增加 I/O 与 DRAM 占用命中率、读放大、版本化访问记录、快速停用
页保护过强其它工作集 refault、direct reclaim 或 OOM最大保护字节、最大时长、压力强制退出
进程终止偏置某些后台应用长期成为牺牲对象角色白名单、每应用终止频率、公平性
隐私越界跨应用使用序列泄露用户行为本地最小化、多用户隔离、保留期限、审计
kernel 分叉安全补丁合并困难、MGLRU/classic LRU 行为分裂尽量小的 patch、双路径测试、持续 rebase(把改动迁移到新基线)
功耗与温度空闲预读唤醒存储,竞争前台任务电源与温度启用条件、能耗 A/B、存储队列监控

任一压力档位出现 kernel OOM、SystemUI/Launcher 被终止、导航或音频中断、P99 变差或 I/O 读放大失控时,策略都应自动停用并恢复 Android 原生行为。 退出路径也必须经过同等规模的压力测试。

源码核对入口

与其他章节的边界

参考资料