# 记一次线上报错 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
///
///
public GObject AddItemFromPool()
{
GObject obj = GetFromPool(null);
return AddChild(obj);
}
///
/// Add a item to list, same as GetFromPool+AddChild
///
/// Item resource url
///
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
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。