移动游戏客户端内存水位:不要等系统杀进程才开始优化

围绕移动端内存水位、资源常驻、场景峰值、后台恢复和低端机策略,讨论客户端内存治理。

内存问题通常来得很安静

帧率问题会马上被看见,内存问题常常在上线后才爆。玩家玩了半小时,切几次场景,打开几次活动界面,突然闪退;或者切到后台回微信,再回来游戏被系统杀掉。崩溃日志里不一定有漂亮的堆栈,只看到低内存或进程被回收。

水位要按设备分档

同样 1.5GB 内存占用,在高端平板上可能没事,在低端手机上就是危险。客户端应该按设备内存和系统版本设定不同水位,比如安全、警告、危险三个区间。达到警告区间时减少缓存,达到危险区间时主动释放可恢复资源并降低画质。

常驻资源要审计

很多内存浪费来自“这个资源可能会用到,所以常驻”。通用字体、基础 UI 图集、主角骨架、常用音效可以常驻,但活动大图、剧情角色、节日特效、一次性引导资源不应该长期留在内存。常驻资源需要清单和理由。

场景峰值比稳定值更危险

玩家最容易崩溃的时刻往往是场景切换。旧场景还没释放,新场景已经加载,内存短时间翻倍。稳定进入后看起来没问题,但峰值已经超过低端机承受范围。优化时不能只看切换后的占用,要看切换过程最高点。

对象池也会泄漏

对象池常被用来减少频繁创建,但对象池没有上限就会变成内存泄漏。战斗高峰创建了 500 个飘字,战斗结束后池子仍然保留 500 个;活动界面打开一次缓存 200 个条目,关闭后不清。

小结

内存水位是移动端客户端的健康指标。它要求团队按设备看占用、按场景看峰值、按资源看常驻、按对象池看增长。真正稳定的游戏不是从不占内存,而是在接近危险线时知道该释放什么、降级什么、暂停什么。

上线前的复盘清单

移动端内存水位最后容易输在细节。团队可以在提测前做一次十五分钟复盘:入口是否只有一个,失败路径是否能被重复触发,日志里是否能看到关键上下文,弱网、低内存、切后台、热更新后首次进入这些场景是否有人真正跑过。清单不需要很长,但要能挡住最常见的事故。

第一项是边界。哪些状态属于客户端暂存,哪些必须等服务端确认,哪些只是表现层效果,要写在需求文档或接口说明里。第二项是恢复。玩家断网、杀进程、锁屏、切换账号、更新资源后回来,客户端应该回到哪个画面,是否会重复扣道具或重复弹奖励。第三项是可观测。没有日志、没有埋点、没有版本号和配置号,线上问题只能靠猜。第四项是降级。低端机、老资源包、灰度配置错误时,系统能否退到朴素但可用的路径。

内存治理不是为了把代码写得保守,而是为了让客户端在真实环境里少一点脆弱。玩家不会按测试用例玩游戏,他会在地铁里切网络,在战斗结算前接电话,在更新到一半时锁屏,也会在礼包倒计时最后几秒连续点击。能承受这些动作的系统,通常不是靠某个聪明函数撑起来的,而是靠清楚的状态、稳定的数据、可回放的日志和足够朴素的失败处理撑起来的。

和策划、美术、服务端对齐

很多移动端内存水位问题表面看是客户端实现,根上却是协作边界没有说清楚。策划需要知道哪些反馈可以立即出现,哪些反馈必须等待权威结果;美术需要知道资源尺寸、动画事件、特效峰值和加载时机的预算;服务端需要知道客户端会缓存什么、重试什么、放弃什么。只要这些假设没有写下来,后续迭代就会靠口头记忆运转。

比较有效的做法是把一页协作说明放在需求旁边,列出输入、输出、失败处理和验收方式。比如资源类需求要写明包体归属、依赖关系、是否允许边玩边下;战斗类需求要写明本地预演和服务端确认的差异;UI 类需求要写明列表规模、刷新频率和关闭后的状态保留。说明越具体,返工越少。

上线后也要保留一条反馈通道。客服截图、玩家录像、崩溃堆栈、埋点漏斗和灰度数据都能帮助团队判断问题在哪一层。客户端工程师不应该只等 bug 单,而要主动把现象翻译成可定位的问题:是资源缺失、状态跳转错误、请求重复、表现未降级,还是需求本身给了互相冲突的规则。

一个容易忽略的成本

移动端内存水位还有一个成本是新人理解成本。项目越到中后期,真正危险的不是某个类多了两百行,而是没人能说清一次完整流程经过哪些模块。新同事接手时,如果只能靠全局搜索和断点追踪,很容易在修一个小问题时改坏另一条路径。

因此我更偏向把关键流程画出来,并在代码里保留少量稳定的命名:状态名、事件名、错误码、资源阶段名尽量和文档一致。这样排查问题时,日志、配置、代码和运营后台看到的是同一套语言。语言统一以后,团队讨论会短很多,也更少出现“我以为你说的是另一个状态”的误会。

这类维护成本不会在第一周显现,但会在每次版本合入、每次活动复用、每次紧急修复里持续计息。早一点把结构讲清楚,后面就少一点靠资深同学记忆救火的依赖。

上线前的复盘清单

移动端内存水位最后容易输在细节。团队可以在提测前做一次十五分钟复盘:入口是否只有一个,失败路径是否能被重复触发,日志里是否能看到关键上下文,弱网、低内存、切后台、热更新后首次进入这些场景是否有人真正跑过。清单不需要很长,但要能挡住最常见的事故。

第一项是边界。哪些状态属于客户端暂存,哪些必须等服务端确认,哪些只是表现层效果,要写在需求文档或接口说明里。第二项是恢复。玩家断网、杀进程、锁屏、切换账号、更新资源后回来,客户端应该回到哪个画面,是否会重复扣道具或重复弹奖励。第三项是可观测。没有日志、没有埋点、没有版本号和配置号,线上问题只能靠猜。第四项是降级。低端机、老资源包、灰度配置错误时,系统能否退到朴素但可用的路径。

内存治理不是为了把代码写得保守,而是为了让客户端在真实环境里少一点脆弱。玩家不会按测试用例玩游戏,他会在地铁里切网络,在战斗结算前接电话,在更新到一半时锁屏,也会在礼包倒计时最后几秒连续点击。能承受这些动作的系统,通常不是靠某个聪明函数撑起来的,而是靠清楚的状态、稳定的数据、可回放的日志和足够朴素的失败处理撑起来的。

和策划、美术、服务端对齐

很多移动端内存水位问题表面看是客户端实现,根上却是协作边界没有说清楚。策划需要知道哪些反馈可以立即出现,哪些反馈必须等待权威结果;美术需要知道资源尺寸、动画事件、特效峰值和加载时机的预算;服务端需要知道客户端会缓存什么、重试什么、放弃什么。只要这些假设没有写下来,后续迭代就会靠口头记忆运转。

比较有效的做法是把一页协作说明放在需求旁边,列出输入、输出、失败处理和验收方式。比如资源类需求要写明包体归属、依赖关系、是否允许边玩边下;战斗类需求要写明本地预演和服务端确认的差异;UI 类需求要写明列表规模、刷新频率和关闭后的状态保留。说明越具体,返工越少。

上线后也要保留一条反馈通道。客服截图、玩家录像、崩溃堆栈、埋点漏斗和灰度数据都能帮助团队判断问题在哪一层。客户端工程师不应该只等 bug 单,而要主动把现象翻译成可定位的问题:是资源缺失、状态跳转错误、请求重复、表现未降级,还是需求本身给了互相冲突的规则。

一个容易忽略的成本

移动端内存水位还有一个成本是新人理解成本。项目越到中后期,真正危险的不是某个类多了两百行,而是没人能说清一次完整流程经过哪些模块。新同事接手时,如果只能靠全局搜索和断点追踪,很容易在修一个小问题时改坏另一条路径。

因此我更偏向把关键流程画出来,并在代码里保留少量稳定的命名:状态名、事件名、错误码、资源阶段名尽量和文档一致。这样排查问题时,日志、配置、代码和运营后台看到的是同一套语言。语言统一以后,团队讨论会短很多,也更少出现“我以为你说的是另一个状态”的误会。

这类维护成本不会在第一周显现,但会在每次版本合入、每次活动复用、每次紧急修复里持续计息。早一点把结构讲清楚,后面就少一点靠资深同学记忆救火的依赖。

上线前的复盘清单

移动端内存水位最后容易输在细节。团队可以在提测前做一次十五分钟复盘:入口是否只有一个,失败路径是否能被重复触发,日志里是否能看到关键上下文,弱网、低内存、切后台、热更新后首次进入这些场景是否有人真正跑过。清单不需要很长,但要能挡住最常见的事故。

第一项是边界。哪些状态属于客户端暂存,哪些必须等服务端确认,哪些只是表现层效果,要写在需求文档或接口说明里。第二项是恢复。玩家断网、杀进程、锁屏、切换账号、更新资源后回来,客户端应该回到哪个画面,是否会重复扣道具或重复弹奖励。第三项是可观测。没有日志、没有埋点、没有版本号和配置号,线上问题只能靠猜。第四项是降级。低端机、老资源包、灰度配置错误时,系统能否退到朴素但可用的路径。

内存治理不是为了把代码写得保守,而是为了让客户端在真实环境里少一点脆弱。玩家不会按测试用例玩游戏,他会在地铁里切网络,在战斗结算前接电话,在更新到一半时锁屏,也会在礼包倒计时最后几秒连续点击。能承受这些动作的系统,通常不是靠某个聪明函数撑起来的,而是靠清楚的状态、稳定的数据、可回放的日志和足够朴素的失败处理撑起来的。

和策划、美术、服务端对齐

很多移动端内存水位问题表面看是客户端实现,根上却是协作边界没有说清楚。策划需要知道哪些反馈可以立即出现,哪些反馈必须等待权威结果;美术需要知道资源尺寸、动画事件、特效峰值和加载时机的预算;服务端需要知道客户端会缓存什么、重试什么、放弃什么。只要这些假设没有写下来,后续迭代就会靠口头记忆运转。

比较有效的做法是把一页协作说明放在需求旁边,列出输入、输出、失败处理和验收方式。比如资源类需求要写明包体归属、依赖关系、是否允许边玩边下;战斗类需求要写明本地预演和服务端确认的差异;UI 类需求要写明列表规模、刷新频率和关闭后的状态保留。说明越具体,返工越少。

上线后也要保留一条反馈通道。客服截图、玩家录像、崩溃堆栈、埋点漏斗和灰度数据都能帮助团队判断问题在哪一层。客户端工程师不应该只等 bug 单,而要主动把现象翻译成可定位的问题:是资源缺失、状态跳转错误、请求重复、表现未降级,还是需求本身给了互相冲突的规则。

一个容易忽略的成本

移动端内存水位还有一个成本是新人理解成本。项目越到中后期,真正危险的不是某个类多了两百行,而是没人能说清一次完整流程经过哪些模块。新同事接手时,如果只能靠全局搜索和断点追踪,很容易在修一个小问题时改坏另一条路径。

因此我更偏向把关键流程画出来,并在代码里保留少量稳定的命名:状态名、事件名、错误码、资源阶段名尽量和文档一致。这样排查问题时,日志、配置、代码和运营后台看到的是同一套语言。语言统一以后,团队讨论会短很多,也更少出现“我以为你说的是另一个状态”的误会。

这类维护成本不会在第一周显现,但会在每次版本合入、每次活动复用、每次紧急修复里持续计息。早一点把结构讲清楚,后面就少一点靠资深同学记忆救火的依赖。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页