【Bug记录】UnityEngine.Object与System.Object判空混用引起的问题记录

 # 问题

几个月前,也就是上一篇博客我做了关于优化将LZMA修改为LZ4的文章,将所有AB包通过

AssetBundle.LoadFromFileAsync

这个接口加载AB包的头文件,这样可以有效的提高加载速度。真实的资源在加载后通过弱引用进行管理。

最近出了一个偶现的bug,有时会存在加载不到资源的问题。

 public Object GetAssetCache(string assetName)
{
	_assetsCaching.TryGetValue(assetName, out var reference);
	if (reference == null || !reference.IsAlive)
	{
		var abName = _assetsPathMapping.GetAssetBundleName(assetName);
		var abAsset = GetAssetBundleCache(abName);
		if (abAsset != null && !abAsset.isStreamedSceneAssetBundle)
		{
			var assetPath = AssetBundleUtility.PackagePathToAssetsPath(assetName);
			var asset = abAsset.LoadAsset<Object>(assetPath);
			reference = WeakReferenceUtility.Get(asset);
			_assetsCaching[assetName] = reference;
		}
	}

	return reference?.Target as Object;
}

上面这段就是从缓存中获取资源的代码,如果查找不到引用或者弱引用不存在就会重新从AB包中加载资源。整个代码咋一看没有什么问题,一开始我也不知道问题出在了哪里,也是写了很多日志代码才最终定位问题。

//当引用不为空的时候加上下面的日志输出
if (reference.Target == null)
{
	Logger.LogError("weak reference is alive,but target is null???");
	return null;
}

if (reference.Target as Object == null)
{
	Logger.LogError("weak reference is alive,but target as Object is null???,type name=>{0}",
		reference.Target.GetType().Name);
}



//最终在测试时,第二个if走了进去

问题就出在,WeakReference是C#的一个对象,其中的Target是System.Object对象,但我们实际存储是一个Unity对象,虽然写法都是 xxx == null,但是由于以System.Object判空是判断是否为空指针,而UnityEngine.Object 判空是重载了方法。

下面是UnityEngine.Object判空的逻辑

private static bool CompareBaseObjects(Object lhs, Object rhs)
{
  bool flag1 = (object) lhs == null;
  bool flag2 = (object) rhs == null;
  if (flag2 && flag1)
	return true;
  if (flag2)
	return !Object.IsNativeObjectAlive(lhs);
  return flag1 ? !Object.IsNativeObjectAlive(rhs) : lhs.m_InstanceID == rhs.m_InstanceID;
}

private static bool IsNativeObjectAlive(Object o)
{
  if (o.GetCachedPtr() != IntPtr.Zero)
	return true;
  switch (o)
  {
	case MonoBehaviour _:
	case ScriptableObject _:
	  return false;
	default:
	  return Object.DoesObjectWithInstanceIDExist(o.GetInstanceID());
  }
}

通过源码看出,如果通过了System.Object判空后,对于一个Mono脚本永远都不为空,对于unity资源会去查找instance id是否存在而判断是否为空。

# 问题分析

上面之所以会出问题,就是在于我在资源卸载后会调用Resources.UnloadUnusedAssets对没用的资源进行卸载,这个过程是unity内部过程,而WeakReference是托管堆对象,需要gc后这个引用才会为空,而unity资源实际都是派生于UnityEngine.Object,这些实际上对应了c++对象,都是存在于非托管堆上。因此我推测(如有问题请指正),出现这个bug的时候是在,unity已经释放资源,通过UntiyEngine.Object == null已经为true,但是c#还没有触发gc,System.Object == null此时还是false。这个时候走进上面加载流程就会导致返回一个不存在的资源,导致后续逻辑的错误。

找到了问题也很好解决,只需要如下判断,将System.Object转UnityEngine.Object后再判空即可。


if (reference == null || reference.Target as Object == null)

出现这个问题也比较的特殊,因为我使用了弱引用,其中的Target实际上是System.Object,因此在判空的时候使用了System.Object的判空,强制使用UnityEngine.Object的判空方法即可。

又增加了一个小知识~

over

猜你喜欢

转载自blog.csdn.net/weixin_36719607/article/details/122611284