【真机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 执行成功的回调中加入上述代码即可。

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

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

相关推荐
Sitarrrr6 分钟前
【Unity】ScriptableObject的应用和3D物体跟随鼠标移动:鼠标放置物体在场景中
3d·unity
极梦网络无忧9 分钟前
Unity中IK动画与布偶死亡动画切换的实现
unity·游戏引擎·lucene
逐·風8 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
_oP_i10 小时前
Unity Addressables 系统处理 WebGL 打包本地资源的一种高效方式
unity·游戏引擎·webgl
Leoysq19 小时前
【UGUI】实现点击注册按钮跳转游戏场景
游戏·unity·游戏引擎·ugui
_oP_i1 天前
unity中 骨骼、纹理和材质关系
unity·游戏引擎·材质
Padid1 天前
Unity SRP学习笔记(二)
笔记·学习·unity·游戏引擎·图形渲染·着色器
Tp_jh1 天前
推荐一款非常好用的C/C++在线编译器
linux·c语言·c++·ide·单片机·unity·云原生
dangoxiba2 天前
[Unity Demo]从零开始制作空洞骑士Hollow Knight第十八集补充:制作空洞骑士独有的EventSystem和InputModule
游戏·unity·c#·游戏引擎·playmaker
无敌最俊朗@2 天前
unity3d————屏幕坐标,GUI坐标,世界坐标的基础注意点
开发语言·学习·unity·c#·游戏引擎