1. 从一次系统卡顿说起211MB 到底意味着什么第一次在 RK3568 上跑 Android 14 的时候我盯着dumpsys meminfo里那个不断攀升的 PSS 数值心里其实没底。板子只有 2GB 物理内存系统起来之后可用内存常年徘徊在 400MB 上下随便开两个应用就开始触发 lowmemorykiller后台音乐播放器被干掉、输入法重新加载、桌面图标重新绘制——用户体验直接崩盘。后来花了大概三周时间把系统常驻内存从接近 1.1GB 压到了 890MB 左右抠出来整整 211MB。这个数字不是某一项优化单独贡献的而是十几个改动叠加的结果每一项单独看可能只有几MB到几十MB但加在一起就非常可观了。这篇文章想聊的就是这 211MB 是怎么一点点抠出来的。适合谁看如果你正在做 RK3568 或者类似瑞芯微平台的 Android 系统裁剪、内存优化或者你是个应用开发者想搞清楚系统内存到底被谁吃掉了那这篇内容应该能给你一些可以直接抄作业的思路。我不会只告诉你关掉这个服务能省 20MB而是会把为什么能省、省在哪里、有什么副作用、怎么验证效果都讲清楚。毕竟内存优化这件事最怕的就是改完之后系统看着正常实际某个功能悄悄挂了等到量产才发现。RK3568 这颗芯片本身是四核 A55主频 2.0GHzGPU 是 Mali-G52NPU 算力 1TOPS定位就是中低端平板、商显、车机、工业网关这类场景。Android 14 对内存的要求比 Android 11 高了不少光系统本身的基础占用就涨了一截。所以在这个平台上做内存优化本质上是在新系统需求和老硬件规格之间找平衡点。211MB 这个数字大概相当于把系统常驻内存砍掉了接近 20%对于 2GB 内存的设备来说等于多留出了 10% 的可用空间给应用层体感差异非常明显。2. 先搞清楚内存被谁吃了分析工具与基线测量2.1 别急着动手先把基线数据拿到手我见过太多人一上来就开始关服务、删应用改了一堆东西之后发现内存没降多少反而系统变得不稳定。问题出在没有基线数据你不知道改之前是什么状态改之后也没法准确归因。所以在动手之前第一件事是建立一套可重复的测量流程。在 RK3568 的 Android 14 上我主要用这几个工具dumpsys meminfo看整体内存分布重点关 Total PSS、Free RAM、Lost RAM 这几个值procrank需要 root能看到每个进程的 PSS、USS、RSS排序之后一眼就能看出谁是大头smaps/smaps_rollup看单个进程的内存映射细节定位是堆、栈还是共享库占的dumpsys activity processes看当前进程的 oom_adj 和状态判断哪些是常驻的meminfo节点/proc/meminfo里的 MemAvailable 比 MemFree 更有参考价值测量条件必须统一冷启动后等待 5 分钟让系统稳定清空后台应用屏幕亮度固定关闭自动同步。我一般会连续测三次取平均值因为 Android 的内存回收有随机性单次数据波动可能有几十MB。2.2 基线数据长什么样在未做任何优化之前我的基线数据大概是这样的指标数值Total RAM1998 MBFree RAM412 MBUsed RAM1586 MBLost RAM87 MBZRAM 使用210 MB系统常驻 PSS 合计约 1100 MB其中系统常驻部分的大头分布进程/服务PSS 大致范围system_server180-220 MBsurfaceflinger60-80 MBzygote6440-60 MB各种 native 服务150-200 MB预装应用常驻200-300 MB内核 驱动150-200 MB看到这个分布你就明白了内存不是被某一个东西吃掉的而是分散在几十个进程里。所以优化策略必须是多点开花而不是指望找到一个银弹。2.3 一个容易被忽略的细节Lost RAMLost RAM 这个指标很多人不看但在 RK3568 上它经常能到 80-100MB。Lost RAM 指的是内核分配了但没被任何进程统计到的内存常见来源是 ion/dma-buf 缓存、GPU 驱动保留、以及各种硬件编解码器的缓冲区。这部分内存你没法通过关服务来省但可以通过调整驱动参数和缓冲区策略来压缩。我后面会专门讲这一块。3. 系统服务裁剪哪些能关哪些碰不得3.1 先列清单再动刀Android 14 的 system_server 里跑着上百个系统服务但很多服务在 RK3568 这种设备上根本用不到。我的做法是先通过dumpsys -l列出所有服务然后逐个判断这个服务在当前产品形态下有没有实际用途关掉之后会不会影响系统启动举几个我实际关掉或者延迟启动的服务打印相关服务print、companion这些在商显和工业设备上完全没用直接不启动NFC 相关如果硬件没有 NFCnfc服务没必要跑蓝牙相关如果产品不带蓝牙bluetooth_manager可以砍掉Telephony 相关平板和商显不需要通话功能telephony、phone相关服务可以大幅精简自动填充服务autofill在嵌入式设备上没意义文本转语音tts如果不用可以关但要注意有些服务之间有依赖关系。比如你关了telephony可能会影响ConnectivityService的某些分支逻辑导致网络状态判断异常。所以每关一个服务都要跑一遍完整的回归测试。3.2 具体怎么关三种手段第一种是改frameworks/base/services/java/com/android/server/SystemServer.java把对应的startService调用注释掉或者加条件判断。这是最彻底的方式但需要重新编译 framework。第二种是通过config.xml里的config_disabledSystemServices数组来配置。这个方式不用改代码但支持的服务有限不是所有服务都能通过这个开关关掉。第三种是在init.rc里控制 native 服务的启动。很多 native 服务是通过 init 启动的直接在 rc 文件里把对应的 service 段注释掉就行。我一般优先用第二种不行再改代码。因为改代码的维护成本高每次 Android 版本升级都要重新 merge。3.3 关服务的收益和风险关掉这些服务大概能省多少我的实测数据优化项节省 PSS关闭打印/companion 相关8-12 MB关闭 NFC5-8 MB关闭蓝牙12-18 MB精简 Telephony20-30 MB关闭 autofill/tts6-10 MB延迟启动非关键服务15-25 MB加起来大概 70-100MB。但这里有个坑有些服务关了之后系统会在某些场景下反复尝试重启它反而造成 CPU 唤醒和内存抖动。所以关服务之后一定要用dumpsys activity services确认没有反复重启的情况。注意关服务之前一定要确认产品需求。我曾经在一个项目上关了蓝牙结果客户后来要求加蓝牙遥控功能又得全部加回来返工成本很高。4. 预装应用与后台进程的瘦身4.1 预装应用不是装上就行很多方案商的做法是把一堆应用预装进去觉得反正用户可以用。但在 2GB 内存的设备上每个常驻应用都是负担。我的原则是能不做预装的就不做必须预装的尽量做成按需启动。具体做法系统应用精简把packages/apps下用不到的应用从编译配置里去掉比如Calendar、Contacts、Gallery这些如果产品不需要直接不编译进去GMS 精简如果产品不需要 GMS那能省一大块。如果需要也要用 GMS 的裁剪配置只保留必要的模块后台限制对必须预装的应用通过allowlist和background restriction限制其后台运行4.2 用 cgroup 和 oom_adj 控制进程优先级Android 的 lowmemorykiller 和 LMKD 是根据进程的 oom_adj 来决定杀谁的。我们可以通过调整进程的 oom_adj 值让不重要的进程更早被杀掉从而保护关键进程。在 RK3568 上我一般会做这些调整把桌面启动器的 oom_adj 调低更不容易被杀把预装但非关键的应用 oom_adj 调高更容易被杀对某些服务设置persistent属性要谨慎因为 persistent 进程被杀后会立即重启反而造成内存抖动4.3 一个实际案例输入法常驻问题输入法是个典型的内存大户。Android 默认输入法在系统启动后就会常驻占用大概 30-50MB。如果产品不需要复杂输入可以考虑换用轻量级输入法延迟输入法的启动时机在不需要输入的场景下主动释放输入法进程我试过把输入法改成按需启动系统启动后不预加载等用户第一次点击输入框时再拉起。这样冷启动内存能省 30MB 左右代价是第一次输入时会有轻微延迟。对于商显这类输入频率低的设备这个取舍是值得的。5. 图形与显示子系统的内存压缩5.1 SurfaceFlinger 的缓冲区策略SurfaceFlinger 在 RK3568 上通常占 60-80MB其中很大一部分是图形缓冲区。Android 14 默认使用 triple buffering也就是三个缓冲区轮转。在内存紧张的设备上可以改成 double buffering能省一个缓冲区的内存。具体改法是在SurfaceFlinger的配置里调整MAX_BUFFER_COUNT或者在BoardConfig.mk里设置NUM_FRAMEBUFFER_SURFACE_BUFFERS : 2。但要注意改成 double buffering 之后如果 GPU 渲染偶尔卡顿可能会出现画面撕裂。所以这个改动要配合 GPU 性能调优一起做。另外分辨率也是关键。如果产品不需要 4K 输出把显示分辨率降到 1080p 甚至 720p图形缓冲区的内存占用会成倍下降。一个 4K 的 RGBA8888 缓冲区是 3840×2160×4 33MB三个就是 100MB降到 1080p 就只剩 25MB 左右。5.2 GPU 驱动内存优化Mali-G52 的驱动会预留一部分内存用于页表、命令队列等。在gralloc和mali驱动的配置里可以调整一些参数来压缩这部分开销减小 GPU 页表大小调整命令队列深度关闭不必要的 GPU 调试功能这些改动需要参考芯片厂商的文档不同版本的驱动参数名可能不一样。我一般会在vendor目录下的 gralloc 配置里找mali相关的设置项。5.3 硬件编解码器缓冲区RK3568 有硬件编解码器VPU这部分会通过 ion/dma-buf 分配内存。如果产品不需要硬件解码可以在内核配置里关掉 VPU 驱动能省 30-50MB 的保留内存。如果需要也要调整缓冲区数量和大小不要用默认的最大值。6. 内核与驱动层的内存回收6.1 调整内核内存参数内核层能做的优化其实不少只是很多人忽略了。在 RK3568 的 Android 14 上我通常会调整这些参数vm.swappiness控制换页倾向嵌入式设备上一般设低一些比如 60 改成 40vm.vfs_cache_pressure控制 inode/dentry 缓存的回收压力适当调高可以让内核更积极地回收缓存vm.min_free_kbytes保留的最小空闲内存设太大浪费设太小容易触发直接回收需要根据实际内存调整vm.dirty_ratio/vm.dirty_background_ratio控制脏页回写嵌入式设备上可以调低一些这些参数可以通过init.rc里的write /proc/sys/vm/xxx来设置也可以在sysctl.conf里配置。6.2 ZRAM 的合理配置ZRAM 是把一部分内存压缩后当作交换空间用。在 2GB 内存的设备上ZRAM 配置得当能显著提升可用内存。我的配置是ZRAM 大小设为物理内存的 25%-30%也就是 512-600MB压缩算法用lz4速度快压缩率也够用swappiness配合调整让系统在内存压力大时积极使用 ZRAM但 ZRAM 不是越大越好。设太大反而会占用过多 CPU 做压缩解压而且压缩后的数据还是要占物理内存。我实测下来 512MB 是个比较平衡的值。6.3 内核裁剪如果你们是自己编译内核那裁剪掉不需要的驱动和子系统能省不少内存。RK3568 的内核配置里很多驱动是默认编进去的但实际产品可能用不到。比如不需要的 USB 设备驱动不需要的文件系统不需要的网络协议调试相关的 trace、debugfs内核裁剪需要小心裁错了可能导致系统起不来。建议每次只裁一小部分编译后立即测试。7. 那些容易踩的坑和验证方法7.1 内存优化最常见的三个坑第一个坑是只看 MemFree 不看 MemAvailable。MemFree 是完全没被使用的内存但 Linux 会把大量内存用作缓存这些缓存在需要时是可以回收的。所以 MemFree 低不代表内存不够MemAvailable 才是真正反映可用内存的指标。第二个坑是改了没验证。内存优化最怕的就是改完之后某个功能悄悄挂了。我一般会准备一套回归测试用例覆盖启动、显示、音频、网络、USB、存储、输入等所有关键功能每次改动后都跑一遍。第三个坑是过度优化导致性能下降。比如把 ZRAM 设得太大CPU 占用飙升把缓冲区设得太小画面卡顿。内存优化永远是在内存和性能之间找平衡不是越省越好。7.2 怎么验证优化效果验证分两个层面定量和定性。定量就是看数据。我会在优化前后分别跑一套标准测试冷启动后 5 分钟的dumpsys meminfo连续打开 10 个应用后的内存变化长时间运行比如 24 小时后的内存泄漏情况压力测试下的 lowmemorykiller 触发次数定性就是实际体验。我会模拟真实使用场景看视频、浏览网页、切换应用、待机唤醒感受有没有卡顿、掉帧、应用被杀的情况。7.3 一个具体的排查案例有一次我改完一堆配置后发现系统启动变慢了而且开机后内存反而比之前高了 20MB。排查过程是这样的先用bootchart看启动流程发现system_server启动时间变长了。然后用dumpsys activity services看服务启动顺序发现有个服务在反复重启。最后定位到是我关掉了一个服务但另一个服务依赖它导致依赖方不断重试。这个问题的教训是关服务之前一定要理清依赖关系。Android 的服务依赖不是显式声明的很多时候是隐式的只能通过实际运行来发现。8. 211MB 的构成拆解与后续空间把上面所有优化项加起来我的实际收益大概是这样的优化类别节省内存系统服务裁剪75 MB预装应用精简45 MB图形缓冲区优化35 MB内核参数与 ZRAM30 MB驱动与保留内存26 MB合计211 MB这个数字是在特定产品配置下测出来的不同产品形态会有差异。比如带 GMS 的设备预装应用那块能省的可能更多不带显示的工业网关图形那块省的就少。后续还有没有空间我觉得还有但边际收益在递减。比如进一步裁剪内核能再省 10-20MB但风险很高把更多服务改成按需启动能再省 20-30MB但会影响响应速度。所以我的建议是先把这 211MB 里风险低、收益高的部分做扎实再根据产品实际需求决定要不要继续往下抠。最后分享一个我自己的习惯每次做完内存优化我都会把改动项、收益、风险、验证结果记录在一个表格里。这样下次做新项目的时候可以直接参考哪些改动是安全的、哪些需要谨慎。内存优化这件事经验积累比单次优化更重要。