记一次线上报错 GList AddChildAt NullReferenceException

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

///

/// 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如何拿到

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

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