版本:Unity 6 (6000.0)
语言:English
脚本限制
Unity 链接器

托管代码剥离

在构建过程中,Unity 通过称为托管代码剥离的过程删除未使用的或无法访问的代码,这可以显著减小应用程序的最终大小。托管代码剥离会从托管程序集中删除代码,包括从 C# 脚本一段代码,允许您创建自己的组件、触发游戏事件、随时间推移修改组件属性并以任何您喜欢的方式响应用户输入。 更多信息
参见 术语表
构建的程序集、包和插件的一部分的程序集以及 .NET Framework 中的程序集。

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

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

配置托管代码剥离

The Managed Stripping Level property
托管剥离级别属性

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

要更改 **托管剥离级别** 属性

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

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

此设置适用于可用性优先于构建大小的项目。如果您使用 IL2CPP 脚本后端,则为默认设置。
Unity 搜索一些用户编写的程序集以及所有 UnityEngine 和 .NET 类库以查找未使用的代码。此设置应用一组规则,这些规则会删除一些未使用的代码,但最大程度地减少了意外后果的可能性,例如使用反射的运行时代码的行为变化。
Unity 部分搜索所有程序集以查找无法访问的代码。此设置应用一组规则,这些规则会剥离更多类型的代码模式以减小构建大小。虽然 Unity 不会剥离所有可能的无法访问的代码,但此设置确实增加了出现不良或意外行为变化的风险。
Unity 对所有程序集执行广泛的搜索以查找无法访问的代码。在此设置下,Unity 将大小减小优先于代码稳定性,并尽可能多地删除代码。

此搜索可能比较低的剥离级别花费更长的时间。仅对构建大小极其重要的项目使用此设置。彻底测试您的应用程序,并谨慎使用 [Preserve] 属性和 link.xml 文件,以确保 Unity 链接器不会剥离重要的代码。

使用注释保留代码

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

您可以使用两种主要方法来注释代码以将其保留在托管代码剥离过程中

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

这些技术中的每一种都提供了更多控制在较高剥离级别下 Unity 链接器剥离的代码量,并减少了剥离重要代码的可能性。当您的代码通过反射引用其他代码时,注释特别有用,因为 Unity 链接器并不总是能够检测到反射的使用。

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

根注释

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

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

使用 Preserve 属性注释根

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

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

当您想要保留类型及其默认构造函数时,请使用 [Preserve] 属性。如果您想保留其中一个而不是两个,请使用 link.xml 文件。

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

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

使用 Link XML 文件注释根

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

您可以在项目中使用任意数量的 link.xml 文件。因此,您可以为每个 插件在 Unity 之外创建的一组代码,用于在 Unity 中创建功能。您可以在 Unity 中使用两种插件:托管插件(使用 Visual Studio 等工具创建的托管 .NET 程序集)和原生插件(特定于平台的原生代码库)。 更多信息
参见 术语表
提供单独的保留声明。您不能在包中包含 link.xml 文件,但您可以从非包 link.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, its backing field (if present), 
          getter, and setter methods-->
      <property signature="System.Int32 PropertyName" />

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

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

      <!--Preserve a property, its 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, its 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) 程序集定义保留时,必须将 windowsruntime 属性添加到 link.xml 文件中的 <assembly> 元素中
<linker>
  <assembly fullname="Windows" windowsruntime="true">
    <type name="TypeName"/>
 </assembly>
</linker>

依赖项注释

依赖项注释定义各种代码元素之间的依赖关系。这些注释对于保留 Unity 链接器无法静态分析的代码模式(例如反射)很有用。这些注释还确保当没有根元素使用这些代码元素时,不会错误地保留这些代码元素。您可以使用两种方法来更改 Unity 链接器处理代码元素的方式

  • 注释属性:这些属性指示 Unity 链接器应保留特定的代码模式,例如从注释类型派生的任何类型。

  • AlwaysLinkAssemblyAttribute:使用此属性指示 Unity 链接器即使程序集未被项目中的任何其他程序集引用,也应处理该程序集。

注释属性

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

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

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

AlwaysLinkAssembly 属性

[assembly: UnityEngine.Scripting.AlwaysLinkAssembly] 属性强制 Unity 链接器搜索程序集,无论该程序集是否被包含在构建中的另一个程序集引用。您只能将 AlwaysLinkAssembly 属性应用于程序集。

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

将此属性用于包含一个或多个具有 [RuntimeInitializeOnLoadMethod] 属性但可能不包含项目中任何场景场景包含游戏环境和菜单。可以将每个唯一的场景文件视为一个唯一的关卡。在每个场景中,您可以放置环境、障碍物和装饰,本质上是分段设计和构建游戏。 更多信息
参见 术语表
中直接或间接使用的类型的预编译或包程序集。

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

其他资源

脚本限制
Unity 链接器