记一次线上报错 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的代码,

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 中

/// 
/// Add a item to list, same as GetFromPool+AddChild
/// 
/// Item object
public GObject AddItemFromPool()
{
    GObject obj = GetFromPool(null);

    return AddChild(obj);
}

/// 
/// Add a item to list, same as GetFromPool+AddChild
/// 
/// Item resource url
/// Item object
public GObject AddItemFromPool(string url)
{
    GObject obj = GetFromPool(url);

    return AddChild(obj);
}

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

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,对象是从对象池池子中取出的,没取到则创建一个

        public GObject GetObject(string url)
        {
            url = UIPackage.NormalizeURL(url);
            if (url == null)
                return null;

            Queue 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代码报错的地方加入以下定位代码

    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)
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。

你可能感兴趣的:(unity,ui,lua)