【实战记录】手游内存优化(内存泄露检测)

故事背景:
        手上有一个完整的页游项目,线上运营数据还不错,所以打算把这个项目手游化。因为这个项目本来就是用cocos的creator写的,所以手游化成本比较低,在修改了大概6~7个只能在页游上运行的接口后,游戏就成功在手机上跑起来了。当然,虽然在手机上顺利跑起来了,但还有大量的工作是需要做得,例如:把鼠标点击,鼠标滑动经过,鼠标滚轮相关的操作改成手机的点击操作,又或者很多界面上能显示清晰的字体,放到手机上就看得很吃力很辛苦了(省略一万字)。而当中比较蛋痛的问题就是今天的主题了--内存泄露……
        这个泄露存在很多原因,也不能怪做页游版的朋友。首先,这游戏在页游上运行并没有出现闪退等现象,一切都能正常运行,其次,部分地方有可能是他们故意不释放的,不释放有可能资源会被反复利用,下次使用时加载资源速度就更快了,等于空间换时间~ 当然,解释一万句,其实最终项目上还是有不少地方确实存在泄露……只是他们页游运行正常,PC版顶得住……

使用的游戏引擎:CocosCreator 2.4.5

PS:嫌字多废话多的可以直接拉到文章末端,有精简版……

一.内存检测工具

要做内存相关的优化,首先肯定是要看到内存的变化情况,这里我使用了2款工具去查看游戏的内存。

1.android-studio的Profiler

【实战记录】手游内存优化(内存泄露检测)_第1张图片

 他的优点是可以实时,动态的看着内存的变化,缺点是当内存上升到一定程度,就会像上图一样显示的数据变成0.7GB了。刚开始时,他显示的是如:456M这样的显示,我可以看到的内存变化最少值是1M,但变成G之后,内存变化的最少值变成了100M,这就不符合我的需求了。

为了更精准的查看内存的准确数据,我使用了第二种内存检查工具。

2.adb的dumpsys指令
adb shell dumpsys meminfo 包名
【实战记录】手游内存优化(内存泄露检测)_第2张图片

他能显示当前内存的使用情况且精准的KB,但只能显示输入指令那一刻的内存情况(虽然我尝试写过bat一直循坏调用这个指令,一直刷新着内存的情况,但依然不够工具1的动态显示那么直观)

在检测工具这块,我只找了两个显示内存的工具,而没有去寻找更多的工具(之前是有听说过有些工具会显示内存快照之类的,但后面我们也在游戏内部代码实现了类似的功能)

二.检查加载资源的入口函数

        通过检查后发现,所有资源的加载都指向了同一个类【AssetManager】,接着寻找这个类的相关方法,发现有【releaseUnusedAssets】【releaseAll】接口,尝试过后……出现各种问题,此路不通……

        接着发现【loadAny】【loadRemote】【load】等方法确实是有内存泄露风险的,个人感觉官方意思,这些接口使用者加载回来的资源,自己加载自己管理,load回来后可以使用addRef进行引用计数+1,使用完后可以用decRef进行引用计数减1,引用计数为0则释放资源。
        然后查看项目代码,发现这些相关接口,加载完资源后,既没有addRef也没有decRef,再继续深入打断点,发现资源的引用计数为0,当时以为他会在主循环中自动检测,引用计数为0就会自动释放(走弯路了呗),结果这里显示没有释放,需要主动调用decRef或者cc.assetManager.releaseAsset等方法来主动释放资源。

三.通过项目资源寻找内存主要泄露点

        因为项目不是新项目了,是一个代码量比较庞大的成熟老项目,即使发现某个接口存在内存泄露,可如果项目里面有上百个地方调用到相关接口,改起来还是工作量巨大,而且不能保证绝对安全。所以在这个老项目上,第一波优化方案我选择了允许部分内存泄露,先优先处理那些接口统一而又存在大量内存泄露的地方【改最少得地方,减最多的内存】。我们一共寻找到4个资源量比较大的地方【各功能模块的资源】【人物、坐骑、模型等资源】【spine动画】【地图资源】

1.【人物、坐骑、模型等资源】
经过检查后,没发现泄露。

2.【地图资源】
确认存在泄露,在地图模块对load回来的资源addRef,decRef就解决了。
游戏内存也从2G降到了1.6G

3.【spine动画】
这个一开始我是排除了他泄露的可能性的,检查过他有对资源进行引用计数管理和释放。而且在原有网页版上测试也没发现这块有存在泄露。后来使用了方法【四】(文章下面会详细说),才最终定位到spine动画存在泄露。
处理后,游戏内存从1.6G降到了接近1.1G的地步。

4.【各功能模块的资源】
这里一开始我们就觉得他存在泄露,项目里各模块都处理成一个独立的bundle,但他loadbundle后,没看到他removeBundle,然后我们尝试了removeBundle,内存是降了,但不明显只降了几十M。后来使用了方法【五】(文章下面会详细说),最终定位到动态load的prefab没释放,内存又减少了200M+

至此,内存从一开始的2G降到了不到900M,而且为了快而稳的处理这个问题,我们初步只是找了一些修改起来比较统一而且泄露比较大的地方进去处理(目标是改起来快而且安全)。后续如果有需要的话可以更进一步对那些已经确定泄露的地方进行优化,只是那些优化可能改几十个地方也就降低几M,10几M的内存。

四.控制变量

        先寻找一个内存增加了很多的地方,然后逐渐减少那一块加载的东西,最终找出内存泄露的地方。
        在我们项目中,我发现新手第一场战斗后,增加了不少的内存,然后就开始屏蔽人物,屏蔽特效,屏蔽UI等等,最终发现了spine动画没释放。
        最简单的屏蔽肯定是把创建那些资源的代码直接屏蔽掉,但有些时候却并不好用,例如战斗的人物如果不创建出来,战斗逻辑会出问题,又例如当前只是加载一个texture,加载完成后返回到代码逻辑中创建spriteFrame,你把load texture的代码屏蔽了,后续代码会出问题,你想把创建sprite的代码屏蔽掉,他分散在战斗中的每一个角落,所以这里我建议,例如像texture,在他创建的地方我们写死一张固定资源来加载,那他永远都只会加载那一张texture,虽然界面看着会各种奇怪(都是那张图),但我们只需要达到屏蔽资源测试内存的目的不就可以了么? 人物角色也是一样,可以加载同一个,这样就会让原本加载几十个的资源变成加载一个,达到屏蔽的效果,依然可以看出最终泄露是出在哪一块上。

五.内存快照

        开篇时就有提到过有些内存检测工具有这类功能,一来懒得研究,二来在代码里写也不复杂,就自己做了个类似的东西了。

        打开界面前,记录下assetsManager里面当前的所有资源,然后打开界面关掉界面,再获取当前assetsManager里面的所有资源,并和打开界面前进行比较,在后台输出新增的资源。
        我们期待的必然是打开界面前和关闭界面后基本没有新增资源的,可结果显示是糟糕的,测试中尝试把游戏大部分的系统界面都点开一遍然后关闭掉,发现assetsManager里面新增了600多个资源,后来定位到一个统一的地方,把对应的perfab释放后,再测试时新增资源数已经减到剩下150个了。
剩下这些资源就没统一的地方可以改了,都是散布在各个模块代码里面load的资源,本着【改最少得地方,减最多的内存】的思想,剩下的也放后续优化里面了。例如这150个里面有近半都是icon资源,可能我要改20~30个地方最终减少不到1M的内存占用……

六.通过手动GC释放内存

        我是不太了解java的底层GC机制的,只知道他会间隔一段时间(具体什么规则也不太清楚=。=)才会GC一次,所以即使你的资源在游戏内释放掉了,其实他并没有真正的释放内存,要等到java的GC时才会真正把内存释放掉,这意味着我们游戏的平均内存会因为没GC而变得更高,当然GC也要消耗性能,我们也不能一直无限的去GC,这样我们就可以找一些游戏节点去进行GC了,例如切换场景的时候,或者某个界面打开时占用了大量的资源在他关闭的时候手动GC一下。

------------------------------------------------------精简版------------------------------------------------------

第一点:【内存检测工具】介绍了2款内存检测工具
第二点:【查内存泄露的方法】通过查找加载资源的接口来寻找资源是否有泄露
第三点:
【查内存泄露的方法】通过检查游戏内比较大的资源是否有释放,来寻找内存泄露比较多的地方(因为一般内存泄露比较明显的地方多数是加载了较大的资源没有释放)
第四点:【查内存泄露的方法】找到一个游戏内比较明显有泄露的地方,然后把那地方的东西,一块块屏蔽掉,通过控制变量最终找到泄露的点。
第五点:【查内存泄露的方法】通过两次内存快照之间新增的内存点,去寻找泄露
第六点:【内存优化】手动GC,及早清理没使用到的内存


 

你可能感兴趣的:(手游技术,内存检测,内存泄露,creator,cocos)