记一次线上报错 GList AddChildAt NullReferenceException

文章目录

问题描述

后台日志大量报错,去主干看无法复现

c# exception:System.NullReferenceException: Object reference not set to an instance of an object. at FairyGUI.GCompone

nt.AddChildAt (FairyGUI.GObject child, System.Int32 index) [0x00000] in <00000000000000000000000000000000>:0 at Fairy

GUI.GList.AddChildAt (FairyGUI.GObject child, System.Int32 index) [0x00000] in <00000000000000000000000000000000>:0 a

t FairyGUI.GComponent.AddChild (FairyGUI.GObject child) [0x00000] in <00000000000000000000000000000000>:0 at FairyGUI

.GList.set_numItems...

分析

看逻辑是没有问题的,但是在调用numItems时c#层报错。看FairyGUI的代码,

C# 复制代码
public int numItems
{
    get
    {
        if (_virtual)
            return _numItems;
        else
            return _children.Count;
    }
    set
    {
        if (_virtual)
        {
            if (itemRenderer == null)
                throw new Exception("FairyGUI: Set itemRenderer first!");

            _numItems = value;
            if (_loop)
                _realNumItems = _numItems * 6;//设置6倍数量,用于循环滚动
            else
                _realNumItems = _numItems;

            //_virtualItems的设计是只增不减的
            int oldCount = _virtualItems.Count;
            if (_realNumItems > oldCount)
            {
                for (int i = oldCount; i < _realNumItems; i++)
                {
                    ItemInfo ii = new ItemInfo();
                    ii.size = _itemSize;

                    _virtualItems.Add(ii);
                }
            }
            else
            {
                for (int i = _realNumItems; i < oldCount; i++)
                    _virtualItems[i].selected = false;
            }

            if (_virtualListChanged != 0)
                Timers.inst.Remove(this.RefreshVirtualList);
            //立即刷新
            this.RefreshVirtualList(null);
        }
        else
        {
            int cnt = _children.Count;
            if (value > cnt)
            {
                for (int i = cnt; i < value; i++)
                {
                    if (itemProvider == null)
                        AddItemFromPool();
                    else
                        AddItemFromPool(itemProvider(i));
                }
            }
            else
            {
                RemoveChildrenToPool(value, cnt);
            }

            if (itemRenderer != null)
            {
                for (int i = 0; i < value; i++)
                    itemRenderer(i, GetChildAt(i));
            }
        }
    }
}

AddChild 发生在 AddItemFromPool 中

C# 复制代码
/// <summary>
/// Add a item to list, same as GetFromPool+AddChild
/// </summary>
/// <returns>Item object</returns>
public GObject AddItemFromPool()
{
    GObject obj = GetFromPool(null);

    return AddChild(obj);
}

/// <summary>
/// Add a item to list, same as GetFromPool+AddChild
/// </summary>
/// <param name="url">Item resource url</param>
/// <returns>Item object</returns>
public GObject AddItemFromPool(string url)
{
    GObject obj = GetFromPool(url);

    return AddChild(obj);
}

AddChild 的参数为空引用, 看GetFromPool如何拿到

c# 复制代码
public GObject GetFromPool(string url)
{
    if (string.IsNullOrEmpty(url))
        url = _defaultItem;

    GObject ret = _pool.GetObject(url);
    if (ret != null)
        ret.visible = true;
    return ret;
}

最终定位到GObjectPool.GetObject,对象是从对象池池子中取出的,没取到则创建一个

c# 复制代码
        public GObject GetObject(string url)
        {
            url = UIPackage.NormalizeURL(url);
            if (url == null)
                return null;

            Queue<GObject> arr;
            if (_pool.TryGetValue(url, out arr)
                && arr.Count > 0)
                return arr.Dequeue();

            GObject obj = UIPackage.CreateObjectFromURL(url);
            if (obj != null)
            {
                if (initCallback != null)
                    initCallback(obj);
            }

            return obj;
        }

从上面的代码可以得出报错的原因

  1. 可能池子里拿出来的对象在lua层已经被销毁了
  2. 可能创建对象没有成功,资源存在问题

在lua代码报错的地方加入以下定位代码

lua 复制代码
    local list = self._listview
    local obj, nullNum, lossNum
    for i = list.numChildren + 1, infoLen do -- 先处理已经销毁了的
        obj, nullNum, lossNum = self:getItmeFromPool(list)
        if obj then
            list:AddChild(obj)
        else
            if SDKCtrl then
                SDKCtrl:reportError(" warning 上报定位  get null tiems:" .. nullNum .. "  get Disposed times: " .. lossNum)
            end
        end
    end

    list.numItems = infoLen
    list:ResizeToFit(infoLen)
lua 复制代码
function mod:getItmeFromPool(list)
    local obj
    local lossNum, nullNum = 0, 0
    while lossNum < 100 and nullNum < 100 do
        obj = list:GetFromPool()

        if not obj then
            nullNum = nullNum + 1
        elseif obj.isDisposed then
            lossNum = lossNum + 1
        else
            break
        end
    end

    return obj, nullNum, lossNum
end

结果

定位的代码更新出去后,日志中出现了 warning 上报定位 get null tiems:100 get Disposed times: 0。

item对象没有创建成功,是分支的列表item资源存在问题,分支找到对应的包重新发布下,问题解决了。应该是主干同步分支的时候没有发布该包。

总结

GList添加item时候,会从对象池中取一个已存在对象复用或者创建新的对象,如果没有找到对应的资源或者对象池中的对象已经销毁过,会导致创建失败,报错 AddChildAt NullReferenceException。

相关推荐
南無忘码至尊2 小时前
Unity学习90天 - 第 6 天 -学习物理 Material + 重力与阻力并实现弹跳球和冰面滑动效果
学习·unity·游戏引擎
for_ever_love__4 小时前
UI 学习 Appearance 外观管理
学习·ui·ios·objective-c
小陈的进阶之路4 小时前
postman-mcp-server
测试工具·lua·postman
mxwin5 小时前
Unity 单通道立体渲染(Single Pass Instanced)对 Shader 顶点布局的特殊要求
unity·游戏引擎·shader
魔士于安7 小时前
unity 低多边形 无人小村 木质建筑 晾衣架 盆子手推车,桌子椅子,罐子,水井
游戏·unity·游戏引擎·贴图·模型
RReality7 小时前
【Unity Shader URP】简易卡通着色(Simple Toon)实战教程
ui·unity·游戏引擎·图形渲染·材质
UXbot8 小时前
如何用 AI 快速生成完整的移动端 UI 界面:从描述到交付的实操教程
前端·ui·交互·ai编程·原型模式
魔士于安8 小时前
unity 骷髅人 连招 武器 刀光 扭曲空气
游戏·unity·游戏引擎·贴图·模型
ai_coder_ai8 小时前
自动化脚本ui编程之下拉列表框控件
ui·autojs·自动化脚本·冰狐智能辅助·easyclick
@Demi9 小时前
Cursor 配置 MasterGo MCP 还原UI设计稿
ui·cursor·mastergo·mcp