记一次线上报错 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。

相关推荐
浅陌sss19 分钟前
物体网格弹性变形---Unity中实现
unity·游戏引擎
疯狂的沙粒1 小时前
Vue项目开发 element-UI 前端实现 1到10排列选择的按钮
前端·vue.js·ui
lzhdim7 小时前
3、.Net UI库:EASkins - 开源项目研究文章
ui·开源·.net
异次元的归来8 小时前
Unity DOTS中的Entity
unity·游戏引擎
向宇it9 小时前
【unity小技巧】Unity 四叉树算法实现空间分割、物体存储并进行查询和碰撞检测
开发语言·算法·游戏·unity·游戏引擎
小春熙子9 小时前
Unity图形学之灯光的原理
unity·游戏引擎·技术美术
鸿蒙自习室10 小时前
鸿蒙多线程开发——线程间数据通信对象01
ui·华为·harmonyos·鸿蒙
dangoxiba12 小时前
[Unity Demo]从零开始制作空洞骑士Hollow Knight第二十集:制作专门渲染HUD的相机HUD Camera和画布HUD Canvas
游戏·unity·c#·游戏引擎·playmaker
找藉口是失败者的习惯16 小时前
Jetpack Compose 如何布局解析
android·xml·ui
天涯倦客的美丽人生1 天前
2024年11月最新版Adobe PhotoShop(26.0)中文版下载
ui·adobe·photoshop