5.IL2CPP跨平台模式可能存在的问题处理

5.Unity跨平台的基本原理-IL2CPP模式可能存在的问题处理


5.1 知识点

安装Unity IL2CPP打包工具

  • 去 ProjectSetting->Player->OtherSetting->Configuration->Scripting Backend 把脚本后端设置成IL2CPP

  • 假如没有下载对应平台的IL2CPP包 BuildSetting会报错
  • 在Unityhub中下载 IL2CPP打包相关工具 安装完成后重启工程 解决报错

IL2CPP打包存在的问题——类型裁剪

IL2CPP在打包时会自动对Unity工程的DLL进行裁剪,将代码中没有引用到的类型裁剪掉,以达到减小发布后包的尺寸的目的。然而在实际使用过程中,很多类型有可能会被意外剪裁掉,造成运行时抛出找不到某个类型的异常。特别是通过反射等方式在编译时无法得知的函数调用,在运行时都很有可能遇到问题。

解决方案:

  • IL2CPP处理模式时,将PlayerSetting->Other Setting->Managed Stripping Level(代码剥离)设置为Low

    • Disable:Mono模式下才能设置为不删除任何代码
    • Low:默认低级别,保守的删除代码,删除大多数无法访问的代码,同时也最大程度减少剥离实际使用的代码的可能性
    • Medium:中等级别,不如低级别剥离谨慎,也不会达到高级别的极端
    • High:高级别,尽可能多的删除无法访问的代码,有限优化尺寸减小。如果选择该模式一般需要配合link.xml使用
  • 通过Unity提供的link.xml方式来告诉Unity引擎,哪些类型是不能够被剪裁掉的

    • 在Unity工程的Assets目录中(或其任何子目录中)建立一个叫link.xml的XML文件,按一定格式告诉Unity哪些哪些程序集、类、成员等要保留。
    • 比较贪的办法是把代码剥离等级设置为高,打包出去,出现报错时,再在裁剪类link.xml中添加保留对应报错类的代码。但是这比较考验测试人员能力,假如没测出来有一定风险。
    • link.xml语法规则
<?xml version="1.0" encoding="UTF-8"?>
  
  <!--保存整个程序集-->
  <assembly fullname="UnityEngine" preserve="all"/>
  <!--没有“preserve”属性,也没有指定类型意味着保留所有-->
  <assembly fullname="UnityEngine"/>

  <!--完全限定程序集名称-->
  <assembly fullname="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
    <type fullname="Assembly-CSharp.Foo" preserve="all"/>
  </assembly>

  <!--在程序集中保留类型和成员-->
  <assembly fullname="Assembly-CSharp">
    <!--保留整个类型-->
    <type fullname="MyGame.A" preserve="all"/>
    <!--没有“保留”属性,也没有指定成员 意味着保留所有成员-->
    <type fullname="MyGame.B"/>
    <!--保留类型上的所有字段-->
    <type fullname="MyGame.C" preserve="fields"/>
    <!--保留类型上的所有方法-->
    <type fullname="MyGame.D" preserve="methods"/>
    <!--只保留类型-->
    <type fullname="MyGame.E" preserve="nothing"/>
    <!--仅保留类型的特定成员-->
    <type fullname="MyGame.F">
      <!--类型和名称保留-->
      <field signature="System.Int32 field1" />
      <!--按名称而不是签名保留字段-->
      <field name="field2" />
      <!--方法-->
      <method signature="System.Void Method1()" />
      <!--保留带有参数的方法-->
      <method signature="System.Void Method2(System.Int32,System.String)" />
      <!--按名称保留方法-->
      <method name="Method3" />

      <!--属性-->
      <!--保留属性-->
      <property signature="System.Int32 Property1" />
      <property signature="System.Int32 Property2" accessors="all" />
      <!--保留属性、其支持字段(如果存在)和getter方法-->
      <property signature="System.Int32 Property3" accessors="get" />
      <!--保留属性、其支持字段(如果存在)和setter方法-->
      <property signature="System.Int32 Property4" accessors="set" />
      <!--按名称保留属性-->
      <property name="Property5" />

      <!--事件-->
      <!--保存事件及其支持字段(如果存在),添加和删除方法-->
      <event signature="System.EventHandler Event1" />
      <!--根据名字保留事件-->
      <event name="Event2" />
    </type>

    <!--泛型相关保留-->
    <type fullname="MyGame.G`1">
      <!--保留带有泛型的字段-->
      <field signature="System.Collections.Generic.List`1&lt;System.Int32&gt; field1" />
      <field signature="System.Collections.Generic.List`1&lt;T&gt; field2" />

      <!--保留带有泛型的方法-->
      <method signature="System.Void Method1(System.Collections.Generic.List`1&lt;System.Int32&gt;)" />
      <!--保留带有泛型的事件-->
      <event signature="System.EventHandler`1&lt;System.EventArgs&gt; Event1" />
    </type>


    <!--如果使用类型,则保留该类型的所有字段。如果类型不是用过的话会被移除-->
    <type fullname="MyGame.I" preserve="fields" required="0"/>

    <!--如果使用某个类型,则保留该类型的所有方法。如果未使用该类型,则会将其删除-->
    <type fullname="MyGame.J" preserve="methods" required="0"/>

    <!--保留命名空间中的所有类型-->
    <type fullname="MyGame.SomeNamespace*" />

    <!--保留名称中带有公共前缀的所有类型-->
    <type fullname="Prefix*" />

  </assembly>
  
  

</linker>

IL2CPP打包存在的问题——泛型问题

我们上节课提到了IL2CPP和Mono最大的区别是不能在运行时动态生成代码和类型,就是说 泛型相关的内容,如果你在打包生成前没有把之后想要使用的泛型类型显示使用一次,那么之后如果使用没有被编译的类型,就会出现找不到类型的报错。

举例:List<A> 和List<B>中A和B是我们自定义的类,我能必须在代码中显示的调用过,IL2CPP才能保留List<A> 和List<B> 两个类型。如果在热更新时我们调用List < C >,但是它之前并没有在代码中显示调用过,那么这时就会出现报错等问题。主要就是因为JIT和AOT两个编译模式的不同造成的。

解决方案:

  • 泛型类: 声明一个类,然后在这个类中声明一些public的泛型类变量

    public class IL2CPP_Info
    {
        public List<A> list;
        public List<B> list2;
        public List<C> list3;
    
        public Dictionary<int, string> dic = new Dictionary<int, string>();
    }
    
  • 泛型方法: 随便写一个静态方法,在将这个泛型方法在其中调用一下。这个静态方法无需被调用,这样做的目的其实就是在预编译之前让IL2CPP知道我们需要使用这个内容

    public class IL2CPP_Info
    {
        public void Test<T>(T info)
        {
    
        }
    
        public static void StaticMethod()
        {
            IL2CPP_Info info = new IL2CPP_Info();
            info.Test<int>(1);
            info.Test<float>(1);
            info.Test<bool>(true);
        }
    
    }
    

总结

对于我们目前开发的新项目,都建议大家使用IL2CPP脚本后处理模式来进行打包。主要原因是因为它的效率相对Mono较高,同时由于它自带裁剪功能,包的大小也会小一些。但是如果在测试时出现类型无法识别等问题,需要用到我们这节课学习的知识点来解决这些问题。


5.2 知识点代码

Lesson05_Unity跨平台的基本原理__IL2CPP模式可能存在的问题处理

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MyGame
{
    public class Test
    {

    }

    public class A
    { }

}

public class A
{

}

public class B
{

}

public class C
{

}

public class IL2CPP_Info
{
    public List<A> list;
    public List<B> list2;
    public List<C> list3;

    public Dictionary<int, string> dic = new Dictionary<int, string>();

    public void Test<T>(T info)
    {

    }

    public static void StaticMethod()
    {
        IL2CPP_Info info = new IL2CPP_Info();
        info.Test<int>(1);
        info.Test<float>(1);
        info.Test<bool>(true);
    }

}



public class Lesson05_Unity跨平台的基本原理__IL2CPP模式可能存在的问题处理 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 安装Unity IL2CPP打包工具
        //在Unityhub中下载 IL2CPP打包相关工具
        #endregion

        #region 知识点二 IL2CPP打包存在的问题——类型裁剪
        //IL2CPP在打包时会自动对Unity工程的DLL进行裁剪,将代码中没有引用到的类型裁剪掉,
        //以达到减小发布后包的尺寸的目的。
        //然而在实际使用过程中,很多类型有可能会被意外剪裁掉,
        //造成运行时抛出找不到某个类型的异常。
        //特别是通过反射等方式在编译时无法得知的函数调用,在运行时都很有可能遇到问题

        //解决方案:
        //1.IL2CPP处理模式时,将PlayerSetting->Other Setting->Managed Stripping Level(代码剥离)设置为Low
        // Disable:Mono模式下才能设置为不删除任何代码
        // Low:默认低级别,保守的删除代码,删除大多数无法访问的代码,同时也最大程度减少剥离实际使用的代码的可能性
        // Medium:中等级别,不如低级别剥离谨慎,也不会达到高级别的极端
        // Hight:高级别,尽可能多的删除无法访问的代码,有限优化尺寸减小。如果选择该模式一般需要配合link.xml使用

        //2.通过Unity提供的link.xml方式来告诉Unity引擎,哪些类型是不能够被剪裁掉的
        //  在Unity工程的Assets目录中(或其任何子目录中)建立一个叫link.xml的XML文件
        #endregion

        #region 知识点三 IL2CPP打包存在的问题——泛型问题
        //我们上节课提到了IL2CPP和Mono最大的区别是 不能在运行时动态生成代码和类型
        //就是说 泛型相关的内容,如果你在打包生成前没有把之后想要使用的泛型类型显示使用一次
        //那么之后如果使用没有被编译的类型,就会出现找不到类型的报错

        //举例:List<A>和List<B>中A和B是我们自定义的类,
        //我能必须在代码中显示的调用过,IL2CPP才能保留List<A>和List<B>两个类型。
        //如果在热更新时我们调用List<C>,但是它之前并没有在代码中显示调用过,
        //那么这时就会出现报错等问题。主要就是因为JIT和AOT两个编译模式的不同造成的
        List<A> list = new List<A>();
        List<B> list2 = new List<B>();

        //解决方案:
        //泛型类:声明一个类,然后在这个类中声明一些public的泛型类变量
        //泛型方法:随便写一个静态方法,在将这个泛型方法在其中调用一下。这个静态方法无需被调用
        //这样做的目的其实就是在预编译之前让IL2CPP知道我们需要使用这个内容
        #endregion

        #region 总结
        //对于我们目前开发的新项目
        //都建议大家使用IL2CPP脚本后处理模式来进行打包
        //主要原因是因为它的效率相对Mono较高,同时由于它自带裁剪功能,包的大小也会小一些
        //但是如果在测试时出现 类型无法识别等问题
        //需要用到我们这节课学习的知识点来解决这些问题
        #endregion
    }
}
<?xml version="1.0" encoding="UTF-8"?>
  
  <!--保存整个程序集-->
  <assembly fullname="UnityEngine" preserve="all"/>
  <!--没有“preserve”属性,也没有指定类型意味着保留所有-->
  <assembly fullname="UnityEngine"/>

  <!--完全限定程序集名称-->
  <assembly fullname="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
    <type fullname="Assembly-CSharp.Foo" preserve="all"/>
  </assembly>

  <!--在程序集中保留类型和成员-->
  <assembly fullname="Assembly-CSharp">
    <!--保留整个类型-->
    <type fullname="MyGame.A" preserve="all"/>
    <!--没有“保留”属性,也没有指定成员 意味着保留所有成员-->
    <type fullname="MyGame.B"/>
    <!--保留类型上的所有字段-->
    <type fullname="MyGame.C" preserve="fields"/>
    <!--保留类型上的所有方法-->
    <type fullname="MyGame.D" preserve="methods"/>
    <!--只保留类型-->
    <type fullname="MyGame.E" preserve="nothing"/>
    <!--仅保留类型的特定成员-->
    <type fullname="MyGame.F">
      <!--类型和名称保留-->
      <field signature="System.Int32 field1" />
      <!--按名称而不是签名保留字段-->
      <field name="field2" />
      <!--方法-->
      <method signature="System.Void Method1()" />
      <!--保留带有参数的方法-->
      <method signature="System.Void Method2(System.Int32,System.String)" />
      <!--按名称保留方法-->
      <method name="Method3" />

      <!--属性-->
      <!--保留属性-->
      <property signature="System.Int32 Property1" />
      <property signature="System.Int32 Property2" accessors="all" />
      <!--保留属性、其支持字段(如果存在)和getter方法-->
      <property signature="System.Int32 Property3" accessors="get" />
      <!--保留属性、其支持字段(如果存在)和setter方法-->
      <property signature="System.Int32 Property4" accessors="set" />
      <!--按名称保留属性-->
      <property name="Property5" />

      <!--事件-->
      <!--保存事件及其支持字段(如果存在),添加和删除方法-->
      <event signature="System.EventHandler Event1" />
      <!--根据名字保留事件-->
      <event name="Event2" />
    </type>

    <!--泛型相关保留-->
    <type fullname="MyGame.G`1">
      <!--保留带有泛型的字段-->
      <field signature="System.Collections.Generic.List`1&lt;System.Int32&gt; field1" />
      <field signature="System.Collections.Generic.List`1&lt;T&gt; field2" />

      <!--保留带有泛型的方法-->
      <method signature="System.Void Method1(System.Collections.Generic.List`1&lt;System.Int32&gt;)" />
      <!--保留带有泛型的事件-->
      <event signature="System.EventHandler`1&lt;System.EventArgs&gt; Event1" />
    </type>


    <!--如果使用类型,则保留该类型的所有字段。如果类型不是用过的话会被移除-->
    <type fullname="MyGame.I" preserve="fields" required="0"/>

    <!--如果使用某个类型,则保留该类型的所有方法。如果未使用该类型,则会将其删除-->
    <type fullname="MyGame.J" preserve="methods" required="0"/>

    <!--保留命名空间中的所有类型-->
    <type fullname="MyGame.SomeNamespace*" />

    <!--保留名称中带有公共前缀的所有类型-->
    <type fullname="Prefix*" />

  </assembly>
  
  

</linker>

5.3 练习题

IL2CPP和Mono的执行效率谁高?为什么?

IL2CPP的代码执行效率是高于Mono的。

主要原因:

  • Mono是JIT即时编译,IL2CPP是AOT提前编译。
  • AOT的优势是在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗。
  • 可以在程序运行初期就达到最高性能,可以显著的加快程序的启动。
  • 再加上IL2CPP的原生C++代码加持,整体而言IL2CPP的效率在Unity下是高于Mono的。

IL2CPP模式打包是,代码剥离可能造成什么问题?如何解决?

代码剥离可能会造成,使用反射调用类或使用热更新时通过Lua创建C#对象时,出现类型不存在的问题。

解决方案:

  1. 使用link.xml配置文件,指明哪些类型不被裁剪剥离。
  2. 显示地调用泛型类或者泛型方法。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏