【真机Bug】异步加载资源未完成访问单例导致资源创建失败

1.错误表现描述

抽卡时,10抽展示界面为A。抽取内容可能是整卡或者碎片,抽到整卡,会有立绘展示和点击详情的按钮。

点击详情后出现详情页B。【此时界面A预制体被销毁,卡片数据进入数据缓存池】

点击页面B的返回按钮,单例的HuanLingRewardController读取DataPool的内容加载界面。

Bug表现:10抽的抽卡展示界面没有出现。

【P.S. 测试发现,问题在游戏引擎平台上没有任何问题,在真机中稳定复现

2.错误排查阶段

因为界面B没有显示,那么就可能有下面几种情况

  1. 界面B的最大一级预制体没有创建/或者被隐藏
  2. Grid下的每个Item可能没有被创建成功/或者被隐藏

通过对指定卡片的GameObject进行addWatches 监视 和 对于SetActive位置进行断点。

首先排除了,物品未被激活导致不显示的问题。

然后从返回按钮的点击事件回调,追踪了一下上下文结构。在机器适配过程中发现,代码中并没有专门为webgl编写相关的宏分支。那么初步可以判断问题不是出现在机型适配导致的。

接下来,我在CreateItem方法和HuanLingRewardController脚本的Awake和OnEnable阶段断点。

查看堆栈。追踪一下上下文。

在绘制界面的必经方法中打印了日志(真机调试,用日志输出)

然后。

对比正常创建10抽界面(第一次十抽后显示)和 详情页返回10抽界面(Bug不显示)的日志

上图为正常加载界面,可以看到碎片都成功加载了。

上图为不显示界面的情况。发现HuanLingRewardController.awake 阶段在showDrawResult后执行的。

看看awake做了什么事

csharp 复制代码
 private void Awake()
    {
        Debug.LogWarning("HuanLingRewardController.Awake()执行了");
        m_Instance = this;
        if (!CheckUI()) return;
        m_LiHuiView.PlayFinished = OnPlayFinished;
        m_LiHuiView.UpdateActiveTips = OnUpdateActiveTips;
        m_AutoActiveTipsTweeners = m_AutoActiveTips.GetComponents<UITweener>();
        m_AutoActiveTips.gameObject.SetActive(false);
    }

他对Instance实例初始化了。

因为实际上awake在后面执行,所以此时m_Instace == null ,然后在if (!m_Instance) return; 返回了

csharp 复制代码
 public static void ShowDrawResult(GC_SPIRITS_LOTTERY packet, bool playTweenAnimation = true)
    {
        Debug.LogWarning("ShowDrawResult执行了");
        if (!m_Instance) return;
        m_Instance.InnerShowDrawResult(packet, playTweenAnimation);
    }

3.错误分析

那么为什么作为单例的Controller的awake阶段会在他的静态方法执行后才初始化。

之前的写法是

csharp 复制代码
 public static void ShowLastReward()
    {
        Debug.LogWarning("ShowLastReward()执行了");
        if (m_LastSpiritsLotteryPacket == null) return;
        ShowUI(SubPage.Reward,true);
        HuanLingRewardController.ShowDrawResult(m_LastSpiritsLotteryPacket);
    }

如果ShowUI是同步加载资源的话,是没有问题的但实际上showUI的基类会调用

csharp 复制代码
private static bool DoShowUI(bool bSync, UIPathData pathData, OnOpenUIDelegate delOpenUI = null, object param = null)

csharp 复制代码
 AssetManager.LoadUI(pathData.path, m_instance.LoadUIBundleFinish, pathData);
//主要是调用了LoadUI
pathData.onOpenUI = delOpenUI;
pathData.param = param;
AssetManager.LoadUI(pathData.path, m_instance.LoadUIBundleFinish, pathData);

而LoadAsset是一个异步加载的原型

csharp 复制代码
public static void LoadAsset(BundleType type, string name, Action<IAssetRef, object> callback, object param)
    {
        if (string.IsNullOrEmpty(name))
            return;
#if USE_AB
        BundleTask task = new BundleTask(OnLoadRemoteAssetFinished);
        task.AddParam(param);
        task.AddParam(callback);
        task.Add(type, name);
        LoadBundle(task, AssetLoader.LoadQueueType.KEYRES);
#else
        if (RemoteBundleManager.IsRemoteBundle(type, name))
        {
            BundleTask task = new BundleTask(OnLoadRemoteAssetFinished);
            task.AddParam(param);
            task.AddParam(callback);
            task.Add(type, name);
            LoadBundle(task, AssetLoader.LoadQueueType.KEYRES);
        }
        else if (Zeus.Framework.Asset.LocalAssetStatus.Ready == Zeus.Framework.Asset.AssetManager.GetAssetStatus(GetAssetPath(type, name), GetAssetType(type)))
        {
            if (callback != null)
                callback(Zeus.Framework.Asset.AssetManager.LoadAsset(GetAssetPath(type, name), GetAssetType(type)), param);
        }
        else
        {
            Zeus.Framework.Asset.AssetManager.LoadAssetAsync(GetAssetPath(type, name), GetAssetType(type), callback, param);
        }
#endif
    }

说明代码逻辑是先生成的窗口预制体然后,去缓存池中读上一次抽卡保存的结果,然后加载数据刷新面板。

所以现在是异步加载未完成然后就调用了后续的静态方法导致被迫中止。

4.问题解决

所以

csharp 复制代码
HuanLingRewardController.ShowDrawResult(m_LastSpiritsLotteryPacket);

刷数据的逻辑应该放在ShowUI 执行成功的回调函数里。

于是,给方法新增标记,判断是否是第二次加载

csharp 复制代码
HuanLingRewardController.ShowDrawResult(m_LastSpiritsLotteryPacket, false);

ShowUI.cs中

csharp 复制代码
public static void ShowUI(SubPage defaultPage,bool isSecondEnter = false)
    {
        // 针对绘卷UI进行特殊处理
        if (StoryScrollMainView.GetInstance())
        {
            StoryScrollMainView.GetInstance().NeedShowMenu = false;
        }

        if (StoryScrollSubView.GetInstance())
        {
            StoryScrollSubView.GetInstance().NeedShowMenu = false;
        }
        
        UIManager.ShowUI(UIInfo.HuanLingRoot, (isSuccess, _) =>
        {
            if (isSuccess && Instance)
            {
                Instance.OnShow(defaultPage);
            }
            if (isSecondEnter)
            {
                HuanLingRewardController.ShowDrawResult(m_LastSpiritsLotteryPacket, false);
            }
        });
    }

在UIManager.ShowUI 执行成功的回调中加入上述代码即可。

这样就保证了在界面预制体加载完成后,才会走刷数据的流程。

出现这样的问题也是因为,手机端异步加载资源的速度受到网络延迟,服务器结点的影响很大。用同步加载的逻辑去思考异步功能肯定是不行的。

相关推荐
mxwin16 小时前
unity shader中 ddx ddy是什么
unity·游戏引擎·shader
郝学胜-神的一滴18 小时前
[简化版 GAMES 101] 计算机图形学 08:三角形光栅化上
c++·unity·游戏引擎·godot·图形渲染·opengl·unreal
nnsix19 小时前
Unity ILRuntime 笔记
unity·游戏引擎
nnsix21 小时前
Unity API 兼容的 .NET Standard 2.1 和 .NET Framework 区别
unity·游戏引擎·.net
mxwin21 小时前
Unity Shader 制作半透明物体 使用多Pass提前写入深度的方式 避免穿模
unity·游戏引擎
nnsix1 天前
Unity HybridCLR 笔记
笔记·unity·游戏引擎
nnsix1 天前
Unity Addressables 笔记
unity·游戏引擎
RReality1 天前
【Unity Shader URP】视差贴图 实战教程
ui·平面·unity·游戏引擎·图形渲染·贴图
小清兔2 天前
Addressable的设置打包流程
笔记·游戏·unity·c#
3D霸霸2 天前
Sourcetree 拉取新工程
数据仓库·unity