关于此方面我也是最近遇到问题才刚刚接触,有理解有误的地方还请路过的看官大佬不吝赐教.
Google Play要求从2019年8月1日起apk必须支持64位cpu, 否则就下架或不让上. 使apk支持ARM64就需要把Scripting Backend由Mono切换为IL2CPP
那么问题来了, 通过IL2CPP打出的包往往不能正常运行(闪退,报错).
其原因就是, BuildSetting里默认勾选了代码裁剪, 取消勾选打出的apk就能正常运行,但是包体也会很大:
什么是代码裁剪?
关于代码裁剪的说明: Managed code stripping
勾选代码裁剪,构建时Unity代码裁剪工具会分析项目中的程序集,查找和删除未使用的代码. 裁剪掉没有使用到的代码.比如,一款2D游戏只用到了Sprite, 2D物理组件, 就可以把没有用到的3D物理代码部分裁剪掉. 使用裁剪功能可以显著减小包体大小, 也是目前Unity游戏包体优化的一个重要环节.
看起来确实是一个非常牛掰又实用的功能, 然而还有不少问题. UnityLinker无法始终检测项目中的代码通过反射引用其他代码的实例,也就是说有可能误裁掉用到的代码, 运行的时候会造成报错闪退, 这无疑是致命的。所以Unity提供了裁剪等级的设置, 以及通过配置link.xml告诉Unity裁剪规则.
① Managed Stripping Level:
有三个等级, 当然, 等级越高裁剪掉的代码越多, 包体也就越小, 但是对应的风险也就更大:
② 配置link.xml:
在项目的Assets目录下创建个link.xml
首先, 我自己的写的游戏逻辑代码肯定需要全部保留, 项目用到了DOTween和Newtonsoft.Json插件也不需要裁剪掉, 就在link.xml添加如下:
<?xml version="1.0" encoding="UTF-8"?>
<linker>
<assembly fullname="DOTween" preserve="all" />
<assembly fullname="Newtonsoft.Json" preserve="all" />
<assembly fullname="Assembly-CSharp" preserve="all" />
</linker>
上面的配置表示保留整个dll, 也可以指定保留某个库下的某个类, 例如:
<assembly fullname="UnityEngine">
<type fullname="UnityEngine.SpriteRenderer" preserve="all"/>
<type fullname="UnityEngine.Rigidbody2D" preserve="all"/>
</assembly>
还有很多其它配置规则请参考官方文档.
Ok, 已经了解到代码裁剪的一点皮毛, 那就开始往坑里跳吧.
0.不进行代码裁剪, apk能正常运行, apk大小为44.5M
1.设置IL2CPP,并勾选代码裁剪,设置裁剪等级为Low, 先不做link.xml配置. 打包apk大小为39.4M. 运行, 完美闪退. Logcat:
很明显, 有的类在项目中使用过,但被裁剪掉了. 并且还给了贴心提示, 让尝试禁用代码裁剪.
闪退问题找到了, 理论上我们把使用到的类配置到link.xml, 防止被裁剪掉就行了. 但是, 怎么找到这些使用到的类呢?
可以看到上图圈出部分, 可以知道这个被裁剪掉的类ID是233, OK, 我们从YAML Class ID Reference找找看:
项目Prefab中用到了HingeJoint2D组件, 但是被Unity裁剪掉了, 所以当游戏运行到实例化这个Prefab时就报错闪退了.
嗯, 感觉有戏. 再找下一个被裁掉的类ID为253, 翻啊翻啊,竟然没找到! 好吧, 应该不是Unity的类. 但是一个一个去找类名感觉有点傻. 这时候就需要上篇博文介绍到的编辑器扩展知识让程序把那些用到过的Unity类给提取出来, 然后配置到link.xml里, 防止被裁剪掉.
同样, 原理也是超简单, 遍历查找所有Prefab上的节点, 获取节点上的组件把类名记录到文件里:
public static void FindAllUnityClass()
{
EditorUtility.displayProgressBar("Progress", "Find Class...", 0);
string[] dirs = { "Assets/MainGame/Prefabs/Entity/Levels" };
var asstIds = AssetDatabase.FindAssets("t:Prefab", dirs);
int count = 0;
List<string> classList = new List<string>();
for (int i = 0; i < asstIds.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(asstIds[i]);
var pfb = AssetDatabase.LoadAssetAtPath<GameObject>(path);
foreach (Transform item in pfb.transform)
{
var coms = item.GetComponentsInChildren<Component>();
foreach (var com in coms)
{
string tName = com.GetType().FullName;
if (!classList.Contains(tName) && tName.StartsWith("UnityEngine"))
{
classList.Add(tName);
}
}
}
count++;
EditorUtility.displayProgressBar("Find Class", pfb.name, count / (float)asstIds.Length);
}
for (int i = 0; i < classList.Count; i++)
{
classList[i] = string.Format("<type fullname=\"{0}\" preserve=\"all\"/>", classList[i]);
}
System.IO.File.WriteallLines(Application.dataPath+"/Classtypes.txt", classList);
EditorUtility.ClearProgressBar();
}
最终link.xml配置好了:
<?xml version="1.0" encoding="UTF-8"?>
<linker>
<assembly fullname="DOTween" preserve="all" />
<assembly fullname="Newtonsoft.Json" preserve="all" />
<assembly fullname="Assembly-CSharp" preserve="all" />
<assembly fullname="UnityEngine">
<type fullname="UnityEngine.Transform" preserve="all"/>
<type fullname="UnityEngine.SpriteRenderer" preserve="all"/>
<type fullname="UnityEngine.Rigidbody2D" preserve="all"/>
<type fullname="UnityEngine.CapsuleCollider2D" preserve="all"/>
<type fullname="UnityEngine.CircleCollider2D" preserve="all"/>
<type fullname="UnityEngine.HingeJoint2D" preserve="all"/>
<type fullname="UnityEngine.FixedJoint2D" preserve="all"/>
<type fullname="UnityEngine.BoxCollider2D" preserve="all"/>
<type fullname="UnityEngine.ParticleSystem" preserve="all"/>
<type fullname="UnityEngine.ParticleSystemRenderer" preserve="all"/>
<type fullname="UnityEngine.distanceJoint2D" preserve="all"/>
<type fullname="UnityEngine.MeshFilter" preserve="all"/>
<type fullname="UnityEngine.MeshRenderer" preserve="all"/>
<type fullname="UnityEngine.Buoyancyeffector2D" preserve="all"/>
<type fullname="UnityEngine.polygonCollider2D" preserve="all"/>
<type fullname="UnityEngine.Experimental.U2D.SpriteShapeRenderer" preserve="all"/>
<type fullname="UnityEngine.U2D.SpriteShapeController" preserve="all"/>
<type fullname="UnityEngine.EdgeCollider2D" preserve="all"/>
<type fullname="UnityEngine.SpringJoint2D" preserve="all"/>
<type fullname="UnityEngine.Areaeffector2D" preserve="all"/>
<type fullname="UnityEngine.Surfaceeffector2D" preserve="all"/>
</assembly>
</linker>
管它三七二十四的, 打个包验证一下. 一步到位, 把裁剪等级为High, 得到apk大小39.7M,经测试运行正常. 比不做代码裁剪小了4.8M. 如果哪位路过的大佬知道更简单的有效的方式还请赐教