【Unity记录】使用Preserve特性防止反射调用代码在build时被裁剪

本文内容

本文介绍:

  • 构建时代码裁剪
  • 裁剪存在的问题
  • Preserve特性(UnityEngine.Scripting.PreserveAttribute)

自动代码裁剪(Managed code stripping)

在Edit->Project Settings->Player->(特定平台)Optimization->Managed Stripping Level中可以选择构建时代码裁剪的程度。

代码裁剪将在构建过程中删除未使用或不可访问的代码,以显著减少程序的最终大小。
该过程会尝试从:你脚本所生成的程序集、插件或包的程序集、.Net Framework程序集中裁剪不可达代码。

裁剪的问题

  • 一般情况:代码引用都可在编译时确定,不会存在任何问题。
  • 反射调用:但反射(Reflection)调用(借助字符串等动态读取数据生成对象或调用方法)无法在编译时追踪。

因此在提升裁剪程度时,被反射调用的代码可能会在裁剪过程中被移除。当尝试调用到这些不存在代码时,会出现各种各样令人意外的错误。

比如我所遇到的问题:
我使用某个JSON库进行序列化与反序列化保存游戏,当在Unity编辑器中,一切正常;当裁剪等级设置为High时,构建后加载存档产生如下错误:

可以归纳出:

  • 这是一个反射相关的错误
  • 涉及Linq.Where()函数的调用

当存在以下要素:

  • 构建后运行存在反射错误
  • 编辑器中运行一切正常
  • 使用了构建裁剪

基本上可以断定是代码裁剪把某些重要代码给意外裁剪掉了。一般在Print出错误堆栈时,你可以在里面找到一些端倪(比如调用过程涉及的函数名或类名)。但我这个错误实属有点阴间,它的错误堆栈一共就只有两行。

但好歹给了些提示,涉及LInq.Where()。仔细思考,JSON序列化库应该不会使用到Linq(经过代码查找也确实如此),因此我把重心放在了我的脚本中。最后发现:

在我存档的序列化对象中,存在一处Linq.Where()的调用。
但是下面的构造函数LevelData(LevelManager levelInfo)是用于保存的;上面的无参构造函数LevelData()才是用于加载的。
为什么在加载时,反射生成存档对象时没有匹配上面的无参构造函数,而是匹配了下面的有参函数?原因就是……无参构造函数压根就不存在于构建后的代码中。
可以看到,LevelData()的引用个数是0,结合裁剪等级设置为High,基本可以断定,这个函数将在构建时移除。

Preserve特性

[Preserve]可以显式声明在构建中保留的代码。可作用于:类、方法、字段或属性。
以下是使用该特性解决上述问题的例子:

using UnityEngine.Scripting;
public class LevelData
{
	......
	[Preserve]
	public LevelData() //构造函数将一定保留
	{
	}
}

受这个特性修饰的所有单位将在裁剪时无条件保留。我的问题也在相关代码加入了[Preserve]修饰后解决。

总结

吾日三省吾身:

  • 有使用代码裁剪乎?
  • 代码存在反射调用乎?
  • Preserve乎?
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值