Unity3D :托管代码剥离

推荐:将NSDT场景编辑器加入你的3D工具链
3D工具集:NSDT简石数字孪生

托管代码剥离

在构建过程中,Unity 会通过称为托管代码剥离的过程删除未使用或无法访问的代码,这可以显著减小应用程序的最终大小。托管代码剥离从托管程序集中删除代码,包括从项目中的 C# 脚本生成的程序集、属于包和插件的程序集以及 .NET Framework 中的程序集。

Unity 使用名为 Unity 链接器的工具对项目程序集中的代码执行静态分析。静态分析标识在执行期间无法访问的任何类、类的一部分、函数或函数的一部分。此分析仅包括在构建时存在的代码,因为在 Unity 执行静态分析时运行时生成的代码不存在。

您可以使用托管剥离级别设置配置 Unity 为项目执行的代码剥离级别。要防止 Unity 删除代码的特定部分,请使用注释来指示 Unity 链接器应保留代码库的哪些部分。有关更多信息,请参阅 Unity 链接器。

配置托管代码剥离

“托管剥离级别”属性
“托管剥离级别”属性

托管剥离级别”属性确定 Unity 链接器在分析和剥离应用程序代码时遵循的规则集。将设置从“最小”增加到“高”时,这些规则使链接器能够在更多程序集中搜索无法访问的代码。Unity 链接器在较高的设置下删除更多代码,从而减小生成的最终大小,但扩展的搜索意味着生成每个构建需要更长的时间。

更改“托管剥离级别”属性:

  1. 转到编辑>项目设置>播放器
  2. 在其他设置中,导航到优化标题。
  3. “托管剥离级别”属性设置为所需的值。
属性:功能:
禁用Unity 不会删除任何代码。

此设置仅可见,如果使用 Mono 脚本后端,则为默认设置。
极小Unity 仅搜索 UnityEngine 和 .NET 类库以查找未使用的代码。Unity 不会删除任何用户编写的代码。此设置最不可能导致任何意外的运行时行为。

此设置对于可用性优先级高于生成大小的项目非常有用。如果使用 IL2CPP 脚本后端,这是默认设置。
Unity 会搜索一些用户编写的程序集以及所有 UnityEngine 和 .NET 类库以查找未使用的代码。此设置应用一组规则,用于删除一些未使用的代码,但最大程度地减少意外后果的可能性,例如使用反射的运行时代码的行为更改。
中等Unity 会部分搜索所有程序集以查找无法访问的代码。此设置应用一组规则,这些规则去除更多类型的代码模式以减小生成大小。尽管 Unity 不会去除所有可能的无法访问的代码,但此设置确实会增加意外或意外行为更改的风险。
Unity 对所有程序集执行广泛搜索,以查找无法访问的代码。在此设置下,Unity 优先考虑减小大小而不是代码稳定性,并尽可能多地删除代码。

与较低的剥离水平相比,此搜索可能需要更长的时间。仅对紧凑生成大小非常重要的项目使用此设置。彻底测试应用程序并谨慎使用 [保留] 属性和链接.xml文件,以确保 Unity 链接器不会剥离重要代码。

使用批注保留代码

您可以使用注释来防止 Unity 链接器剥离代码的特定部分。如果应用程序生成的运行时代码在 Unity 执行静态分析时不存在,这将非常有用;例如,通过反思。注释要么向 Unity 链接器提供一般指导,说明不应去除哪些代码模式,要么提供不剥离特定定义的代码部分的说明。

有两种广泛的方法可用于批注代码,以使其免受托管代码剥离过程的影响:

  • 根批注将代码的某些部分标识为根。Unity 链接器不会去除任何标记为根的代码。根注释的使用不太复杂,但也可能导致 Unity 链接器保留一些应剥离的代码。
  • 依赖关系批注定义代码元素之间的连接。与根批注相比,依赖项批注可以减少代码的过度保留量。

这些技术中的每一种都可以更好地控制 Unity 链接器在较高剥离级别提取的代码量,并减少重要代码被剥离的可能性。当代码通过反射引用其他代码时,注释特别有用,因为 Unity 链接器无法始终检测反射的使用情况。

保留使用反射或在运行时生成其他代码的代码,以显著降低执行应用程序时出现意外行为的可能性。有关 Unity 链接器可以识别的反射模式的示例,请参阅 Unity 中间语言链接器反射测试套件。

根批注

根注释强制 Unity 链接器将代码元素视为根,这些根元素不会在代码剥离过程中剥离。可以使用两种类型的根批注,具体取决于是否需要保留单个类型及其构造函数或程序集:

  • 保留属性:将单个类型注释为根以保留它们。
  • Link.xml:将程序集以及这些程序集中的任何类型或其他代码实体注释为根以保留它们。

使用“保留”属性批注根

使用“保留”属性可从 Unity 链接器的静态分析中单独排除代码的特定部分。若要使用此属性批注一段代码,请在要保留的代码的第一部分之前添加。以下列表描述了当您使用该属性注释不同的代码元素时,Unity 链接器会保留哪些实体:[Preserve][Preserve]

  • 程序集:保留程序集中使用和定义的所有类型。若要将属性分配给程序集,请将属性声明放在程序集中包含的任何 C# 文件中,放在任何命名空间声明之前。[Preserve]
  • 类型:保留类或类型及其默认构造函数。
  • 方法:保留方法、声明方法的类型、方法返回的类型及其所有参数的类型。
  • 属性:保留属性、声明属性的类型、属性的值类型以及获取和设置属性值的方法。
  • 字段:保留字段、字段类型和声明字段的类型。
  • 事件:保留事件、声明事件的类型、类型、事件返回的类型、访问器和访问器。[add][remove]
  • 委托:保留委托类型和委托调用的所有方法。

如果要同时保留类型及其默认构造函数,请使用该特性。如果要保留其中一个,但不能同时保留两个,请使用链接.xml文件。[Preserve]

可以在任何程序集和任何命名空间中定义属性。您可以使用 UnityEngine.Scripting.PreserveAttribute 类,创建 UnityEngine.Scripting.PreserveAttribute 的子类,或创建自己的 PreserveAttribute 类。例如:[Preserve]

class Foo
{
    [Preserve]
    public void PreservedMethod(){}
}

使用链接 XML 文件注释根目录

可以在项目中包括标题为 link.xml 的 .xml 文件,以保留特定程序集或程序集部件的列表。link.xml 文件必须存在于项目中文件夹或文件夹的子目录中,并且必须在文件中包含标记。Unity 链接器将链接文件中保留的任何程序集、类型.xml成员视为根类型。AssetsAssets<linker>

您可以在项目中使用任意数量的 link.xml 文件。因此,您可以为每个插件提供单独的保存声明。不能在包中包含链接.xml文件,但可以从非包链接.xml文件中引用包程序集。

下面的示例演示了使用 link.xml 文件声明项目程序集的根类型的不同方法:

<linker>
  <!--Preserve types and members in an assembly-->
  <assembly fullname="AssemblyName">
    <!--Preserve an entire type-->
    <type fullname="AssemblyName.MethodName" preserve="all"/>

    <!--No "preserve" attribute and no members specified means preserve all members-->
    <type fullname="AssemblyName.MethodName"/>

    <!--Preserve all fields on a type-->
    <type fullname="AssemblyName.MethodName" preserve="fields"/>

    <!--Preserve all fields on a type-->
    <type fullname="AssemblyName.MethodName" preserve="methods"/>

    <!--Preserve the type only-->
    <type fullname="AssemblyName.MethodName" preserve="nothing"/>

    <!--Preserving only specific members of a type-->
    <type fullname="AssemblyName.MethodName">
        
      <!--Fields-->
      <field signature="System.Int32 FieldName" />

      <!--Preserve a field by name rather than signature-->
      <field name="FieldName" />
      
      <!--Methods-->
      <method signature="System.Void MethodName()" />

      <!--Preserve a method with parameters-->
      <method signature="System.Void MethodName(System.Int32,System.String)" />

      <!--Preserve a method by name rather than signature-->
      <method name="MethodName" />

      <!--Properties-->

      <!--Preserve a property, it's backing field (if present), 
          getter, and setter methods-->
      <property signature="System.Int32 PropertyName" />

      <property signature="System.Int32 PropertyName" accessors="all" />

      <!--Preserve a property, it's backing field (if present), and getter method-->
      <property signature="System.Int32 PropertyName" accessors="get" />

      <!--Preserve a property, it's backing field (if present), and setter method-->
      <property signature="System.Int32 PropertyName" accessors="set" />

      <!--Preserve a property by name rather than signature-->
      <property name="PropertyName" />

      <!--Events-->

      <!--Preserve an event, it's backing field (if present), add, and remove methods-->
      <event signature="System.EventHandler EventName" />

      <!--Preserve an event by name rather than signature-->
      <event name="EventName" />

    </type>
  </assembly>
</linker>

下一个示例演示如何声明整个程序集:

<!--Preserve an entire assembly-->
  <assembly fullname="AssemblyName" preserve="all"/>

  <!--No "preserve" attribute and no types specified means preserve all-->
  <assembly fullname="AssemblyName"/>

  <!--Fully qualified assembly name-->
  <assembly fullname="AssemblyName, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
    <type fullname="AssemblyName.Foo" preserve="all"/>
  </assembly>

  <!--Force an assembly to be processed for roots but don’t explicitly preserve anything in particular. Useful when the assembly isn't referenced.-->
  <assembly fullname="AssemblyName" preserve="nothing"/>

此示例演示如何保留嵌套类型或泛型类型:

<!--Examples with generics-->
    <type fullname="AssemblyName.G`1">

      <!--Preserve a field with generics in the signature-->
      <field signature="System.Collections.Generic.List`1&lt;System.Int32&gt; FieldName" />

      <field signature="System.Collections.Generic.List`1&lt;T&gt; FieldName" />

      <!--Preserve a method with generics in the signature-->
      <method signature="System.Void MethodName(System.Collections.Generic.List`1&lt;System.Int32&gt;)" />

      <!--Preserve an event with generics in the signature-->
      <event signature="System.EventHandler`1&lt;System.EventArgs&gt; EventName" />

    </type>

    <!--Preserve a nested type-->
    <type fullname="AssemblyName.H/Nested" preserve="all"/>

    <!--Preserve all fields of a type if the type is used.  If the type isn't used, it will be removed-->
    <type fullname="AssemblyName.I" preserve="fields" required="0"/>

    <!--Preserve all methods of a type if the type is used. If the type isn't used, it will be removed-->
    <type fullname="AssemblyName.J" preserve="methods" required="0"/>

    <!--Preserve all types in a namespace-->
    <type fullname="AssemblyName.SomeNamespace*" />

    <!--Preserve all types with a common prefix in their name-->
    <type fullname="Prefix*" />

其他程序集 XML 属性

link.xml 文件的元素具有三个特殊用途属性,您可以启用这些属性以更好地控制批注。<assembly>

  • ignoreIfMissing:如果需要声明对所有播放器生成期间不存在的程序集的保留,请使用此属性。
<linker>
  <assembly fullname="Foo" ignoreIfMissing="1">
    <type name="TypeName"/>
  </assembly>
</linker>
  • ignoreIfUnreferenced:在某些情况下,您可能希望仅在程序集被另一个程序集引用时才保留该程序集中的实体。仅当程序集中至少引用了一种类型时,使用此属性才能保留程序集中的实体。
<linker>
  <assembly fullname="Bar" ignoreIfUnreferenced="1">
    <type name="TypeName"/>
  </assembly>
</linker>
  • windowsruntime:定义 Windows 运行时元数据 (.winmd) 程序集的保留时,必须将属性添加到 link.xml 文件中的元素:windowsruntime<assembly>
<linker>
  <assembly fullname="Windows" windowsruntime="true">
    <type name="TypeName"/>
 </assembly>
</linker>

依赖关系注释

依赖项批注定义各种代码元素之间的依赖项。这些注释可用于保留 Unity 链接器无法静态分析的代码模式,例如反射。这些批注还可确保在没有根元素使用它们时不会错误地保留这些代码元素。有两种方法可用于更改 Unity 链接器处理代码元素的方式:

  • 批注属性:这些属性指示 Unity 链接器应保留特定的代码模式,例如派生自批注类型的任何类型。
  • AlwaysLinkAssemblyAttribute:使用此属性指示 Unity 链接器应处理程序集,即使项目中的任何其他程序集未引用该程序集也是如此。

批注属性

该属性对于始终需要 API 的情况很有用。其他属性可用于更一般的保存。例如,可以通过使用 RequireImplementorsAttribute 批注接口来保留实现特定接口的所有类型。[Preserve]

若要批注特定的编码模式,请使用以下一个或多个属性:

  • RequireImplementorsAttribute:将实现此接口的所有类型标记为依赖项。
  • RequireDerivedAttribute:将从此类型派生的所有类型标记为依赖项。
  • RequiredInterfaceAttribute:将类型的接口实现标记为依赖项。
  • RequiredMemberAttribute:将某个类型的所有成员标记为依赖项。
  • RequireAttributeUsagesAttribute:将自定义属性标记为依赖项。

您可以通过各种方式组合这些属性,以更精确地控制 Unity 链接器保留代码的方式。

AlwaysLinkAssembly 属性

该属性强制 Unity 链接器搜索程序集,而不管该程序集是否被生成中包含的另一个程序集引用。只能将 AlwaysLinkAssembly 属性应用于程序集。[assembly: UnityEngine.Scripting.AlwaysLinkAssembly]

该特性不直接保留程序集中的代码。相反,此属性指示 Unity 链接器将根标记规则应用于程序集。如果没有代码元素与程序集的根标记规则匹配,Unity 链接器仍会从生成中删除程序集。

在预编译程序集或包程序集上使用此属性,这些程序集包含一个或多个具有该属性的方法,但可能不包含在项目中的任何场景中直接或间接使用的类型。[RuntimeInitializeOnLoadMethod]

如果某个程序集定义并被生成中包含的另一个程序集引用,则该属性对输出没有影响。[assembly: AlwaysLinkAssembly]


3D建模学习工作室整理翻译,转载请注明出处!

上一篇:Unity3D :脚本限制 (mvrlink.com)

下一篇:Unity3D :Unity链接器 (mvrlink.com)

NSDT场景编辑器 | NSDT 数字孪生 | GLTF在线编辑器 | 3D模型在线转换 | UnrealSynth虚幻合成数据生成器 | 3D模型自动纹理化工具
2023 power by nsdt©鄂ICP备2023000829号