Unity IL2CPP发布64位,以及代码裁剪Strip Engine Code

代码裁剪配置工具:https://blog.csdn.net/final5788/article/details/126451377

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游戏包体优化的一个重要环节.

看起来确实是一个非常牛掰又实用的功能, 然而还有不少问题,Unity貌似只能正确裁剪那些构建时自动打包进apk的预制体和场景中的脚本. 而AssetBundle上的脚本就就不会被处理到, 也就是说如果Prefab被打包成AssetBundle, 这个Prefab上的代码可能会被裁剪掉, 导致运行的时候报错闪退, 这无疑是致命的。所以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 FindAllScripts()
    {
        EditorUtility.DisplayProgressBar("Progress", "Find Class...", 0);
        string[] dirs = { "Assets/MainGame/Prefabs" };
        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") || tName.StartsWith("TMPro")))
                    {
                        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即可:

最终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>

这里需要注意的是,link.xml配置是根据程序集而不是根据名字空间,例如UnityEngine.Animator,在VisualStudio中跳转到Animator类会发现,它是属于UnityEngine.AnimationModule程序集,而不是UnityEngine程序集。如果配置到错误的程序集自然就不能正确保留该类防止被裁剪。

如保留Animator类,如下方式是无效的:

<assembly fullname="UnityEngine" preserve="all"/>

 正确方式应该是:

<assembly fullname="UnityEngine.AnimationModule" preserve="all"/>

Unity项目Build后会在项目Library的子目录,如Library\Bee\artifacts\Android\ManagedStripped下生成项目依赖的全部程序集(不同版本Unity或不同平台生成程序集位置不同),这样就可以确定需要保留哪些程序集。

管它三七二十四的, 打个包验证一下. 一步到位, 把裁剪等级为High, 得到apk大小39.7M,经测试运行正常. 比不做代码裁剪小了4.8M. 如果哪位路过的大佬知道更简单的有效的方式还请赐教?

  • 22
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值