首先,上线各大渠道要看应用的总内存量。总内存包含了我们在Unity Profiler的Memory面板上看到的Reserved Total数字的值,再加上除Unity以外的内存。但是很多时候,这个总内存要比Unity Profiler多出来一个量级。以我们的项目为例,iOS总内存为408MB,Unity Reserved Total为279.4MB,多出来408-279.4=128.6MB。
天了噜!这些暗箱里的东西都是什么?我打算从完整项目开始做减法,分别测试一探究竟。
先上我测试用的代码,下面分别是iOS和Android的获取总内存的代码。
iOS的c代码:
//#import <mach/mach.h>
long getResidentMemory
{
struct task_basic_info t_info;
mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
int r = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
if (r == KERN_SUCCESS)
{
return t_info.resident_size;
}
else
{
return -1;
}
}
Android的java代码:
package com.melody.memorytest;
import android.app.ActivityManager;
import android.content.Context;
import android.os.Debug;
import com.unity3d.player.UnityPlayer;
/**
* Created by melody on 2017/1/17.
*/
public class MemoryUtil {
/**
* get the memory of process with certain pid.
*
* @param pid
* pid of process
* @param context
* context of certain activity
* @return memory usage of certain process
*/
public static int getPidMemorySize(int pid,Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int[] myMempid = new int[]{pid};
Debug.MemoryInfo[] memoryInfo = am.getProcessMemoryInfo(myMempid);
memoryInfo[0].getTotalSharedDirty();
int memSize = memoryInfo[0].getTotalPss();
return memSize;
}
public static int getUsedMemory(){
int pid = android.os.Process.myPid();
Context context = UnityPlayer.currentActivity;
return getPidMemorySize(pid,context);
}
}
c#代码:
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
public class ProfilerGUI : MonoBehaviour {
[DllImport("__Internal")]
public extern static long getResidentMemory();
float interval = 1f;
string totalMemory = "totalMemory:";
GUIStyle style;
void Start(){
style = new GUIStyle();
style.normal.textColor = Color.green;
style.fontSize = 30;
StartCoroutine(GetMemoryInterval());
}
IEnumerator GetMemoryInterval()
{
yield return new WaitForSeconds(interval);
#if UNITY_EDITOR
totalMemory = "totalMemory:" + Random.Range(100f, 200f).ToString(".00");//print fake number in UnityEditor
#elif UNITY_IOS
totalMemory = "totalMemory:" + ((float)getResidentMemory() / 1048576).ToString(".00");
#elif UNITY_ANDROID
AndroidJavaClass jc = new AndroidJavaClass("com.melody.memorytest.MemoryUtil");
totalMemory = "totalMemory:" + (float)jc.CallStatic<int>("getUsedMemory") / 1024;
#endif
StartCoroutine(GetMemoryInterval());
}
Rect r = new Rect(0,Screen.height - 80 ,200,80);
void OnGUI(){
GUI.Label (r,totalMemory,style);
}
}
然后分别测试的结果如下:
结论 | |||||||||||||||||||||||
游戏项目|完整 | 屏蔽oc的代码就是指替换UnityAppController.mm,UnityView.mm文件,使在objective-c层面的代码屏蔽 | ||||||||||||||||||||||
Xcode Run | |||||||||||||||||||||||
内存值 | |||||||||||||||||||||||
带有sdk启动的代码|Development Build | 104.81 | ||||||||||||||||||||||
屏蔽oc的代码|Development Build | 86.14 | ||||||||||||||||||||||
Xcode Profile|Instrument | |||||||||||||||||||||||
带有sdk启动的代码|Development Build | 101 | ||||||||||||||||||||||
屏蔽oc的代码|Development Build | 82.5 | ||||||||||||||||||||||
游戏项目|去掉了其他场景 | 内存减少了18.5MB,初步结论是各路sdk的native代码项就占用了18.5MB的app内存 | ||||||||||||||||||||||
Xcode Run | |||||||||||||||||||||||
内存值 | |||||||||||||||||||||||
带有sdk启动的代码|Development Build | |||||||||||||||||||||||
屏蔽oc的代码|Development Build | |||||||||||||||||||||||
Xcode Profile|Instrument | |||||||||||||||||||||||
内存值 | |||||||||||||||||||||||
带有sdk启动的代码|Development Build | 101 | ||||||||||||||||||||||
屏蔽oc的代码|Development Build | |||||||||||||||||||||||
游戏项目|去掉了其他场景|去掉了所有代码和引用库 | 内存减少了47.4MB,结论是il2cpp的代码的初始化内存占用了很大的一部分 | ||||||||||||||||||||||
如果可以用减法的话,47.4-18.5=28.9MB,这是il2cpp所占用的内存 | |||||||||||||||||||||||
Xcode Run | |||||||||||||||||||||||
内存值 | |||||||||||||||||||||||
Development Build | 55.2 | ||||||||||||||||||||||
Xcode Profile|Instrument | |||||||||||||||||||||||
内存值 | |||||||||||||||||||||||
Development Build | 53.6 | ||||||||||||||||||||||
游戏项目|去掉了其他场景|去掉了所有代码和引用库|去掉了StreamingAssets | 无明显变化,结论是StreamingAssets在iOS下有绝对文件路径,所以不需要单独索引 | ||||||||||||||||||||||
而且项目的StreamingAssets文件数量不多 | |||||||||||||||||||||||
Xcode Profile|Instrument | |||||||||||||||||||||||
内存值 | |||||||||||||||||||||||
Development Build | 53.5 | ||||||||||||||||||||||
游戏项目|去掉了其他场景|去掉了所有代码和引用库|去掉了StreamingAssets|去掉了所有Resources文件夹 | 内存减少了20MB,结论是Unity在native层面进行的资源索引,这里和安卓的dalvik堆相似 | ||||||||||||||||||||||
Xcode Run | |||||||||||||||||||||||
内存值 | |||||||||||||||||||||||
Development Build | |||||||||||||||||||||||
Xcode Profile|Instrument | |||||||||||||||||||||||
内存值 | |||||||||||||||||||||||
Development Build | 35.4 | ||||||||||||||||||||||
无其他代码的项目 | |||||||||||||||||||||||
Xcode Run | |||||||||||||||||||||||
内存值 | |||||||||||||||||||||||
Development Build | 31.8 | ||||||||||||||||||||||
Xcode Profile|Instrument | |||||||||||||||||||||||
内存值 | |||||||||||||||||||||||
Development Build | 31 | ||||||||||||||||||||||
Xcode Profile|Instrument|Release | |||||||||||||||||||||||
No Development Build | 30 | ||||||||||||||||||||||
总结:iOS内存测试报表里的128MB内存,包含了:18MB的sdk相关oc代码,28.9MB的代码相关代码(猜测为il2cpp的初始化内存分配),20MB的Resources文件夹分配索引的代码。
在这个层面可优化的点不多。针对以上三项:
- sdk的代码层面的内存优化,我们接入的是MSDK,这里相信腾讯,问题先放置了。
- il2cpp的代码占用,只能通过减少总代码量来进行,及时清理无用代码,特别是第三方的库。
- Resources文件夹的内存:这个问题比较复杂,参考:https://unity3d.com/cn/learn/tutorials/topics/best-practices/guide-assetbundles-and-resources
总的来说就是,Resources文件夹下面的所有文件,无论使用与否都会被打包出来。Unity引擎会在打包的时候,判断项目文件夹下面的需要打包的资源文件。
这些资源将要被打包:
- 所有Resources文件夹下的文件,及其引用到的所有资源。 例如我们把图集资源放到Resources文件夹外面,但是把引用到了这个图集的UIPrefab放到Resources文件夹里面,那么图集也会被打包。
- 获取在BuildSetting面板里面Scenes In Build窗口,获取所有已激活场景文件,并且找到这些场景文件引用到的所有资源。
- 所有的代码文件
- 所有的Plugins/当前平台 的文件
- StreamingAssets文件,这部分文件比较特殊,在iOS下有绝对文件路径,在Android下没有,具体可以参考手册。
而Unity引擎在程序启动时会对所有的资源文件弄一个映射,这个映射的内存就是多出来的这一块内存,内存大小和打包的所有资源文件的数量(非大小)成正比。在Android下同样也可以看见,随着增/减Resources文件夹的文件数量,dalvik堆的内存也随之增加/减少,也是这个原理。还有一份内存是可以在Unity Profiler/Memory/Detailed里面看见,显示在Assets/ResourceManager的内存。这部分内存也是常驻,并且也随打包的所有资源文件的数量(非大小)成正比。
关于Resources文件夹,有的Resources文件夹都不在Assets/Resources这里,而是随意放置,不能统一管理。而且有些也是无用资源,清理打包后的无用资源可以用这个插件 Build Report Tool:https://www.assetstore.unity3d.com/en/#!/content/8162
先总结到这里。。2017年的第一篇博客总算写出来了,Markdown还不熟悉很蛋疼啊。万事开头难,加油。