在bugly上看到不少android設(shè)備上報(bào)一個(gè)奇怪的NullReferenceException堆棧:
說它奇怪在于Transform既然能調(diào)用set_localPosition這個(gè)屬性設(shè)置器,說明Transform這個(gè)實(shí)例對(duì)象是存在的她紫,內(nèi)部只是設(shè)置Vector3這個(gè)結(jié)構(gòu)體判没,怎么會(huì)拋出NRE異常呢滥玷。github上找到了反編譯的Transform代碼:
[GeneratedByOldBindingsGenerator]
[MethodImpl(MethodImplOptions.InternalCall)]
private extern void INTERNAL_set_localPosition(ref Vector3 value);
......
public Vector3 localPosition
{
get
{
Vector3 result;
this.INTERNAL_get_localPosition(out result);
return result;
}
set
{
this.INTERNAL_set_localPosition(ref value);
}
}
INTERNAL_set_localPosition方法有MethodImpl(MethodImplOptions.InternalCall)
標(biāo)記,這是mono的一種調(diào)用native代碼的方法耐齐,比傳統(tǒng)的P/Invoke高效赵哲。看到這里隱約明白了為何set_localPosition內(nèi)部還會(huì)報(bào)NullReferenceException狈惫。Transform只是Mono托管堆里的一個(gè)實(shí)例,但它只是一個(gè)殼鹦马,具體的實(shí)現(xiàn)在native層胧谈,當(dāng)GameObject被銷毀時(shí),native層的對(duì)象已經(jīng)不存在了荸频,mono堆中的殼由于還存在引用菱肖,沒有被GC回收⌒翊樱可以寫如下代碼來驗(yàn)證:
using UnityEngine;
using System.Collections;
public class Test : MonoBehaviour {
// Use this for initialization
void Start () {
var o = new GameObject ("test");
var tr = o.transform;
GameObject.DestroyImmediate (o);
if (tr == null) {
Debug.Log ("tr is null");
Debug.Log ("tr type:" + tr.GetType());
} else {
Debug.Log ("tr is not null");
}
tr.localPosition = Vector3.one;
}
}
將腳本掛在一個(gè)GameObjet上運(yùn)行稳强,日志輸出如下:
輸出tr is null
,類型又是Transform。怎么會(huì)這樣呢和悦?原來UnityEngine.Object重載了==
操作符退疫。具體可以看UnityEngine.Object的反編譯代碼:
public override bool Equals(object other)
{
Object @object = other as Object;
return (!(@object == null) || other == null || other is Object) && Object.CompareBaseObjects(this, @object);
}
public static implicit operator bool(Object exists)
{
return !Object.CompareBaseObjects(exists, null);
}
當(dāng)native對(duì)象銷毀后,== null就會(huì)返true鸽素。
接著看log褒繁,在設(shè)置localPosition時(shí),拋出MissingReferenceException異常馍忽,注意這里不是我們預(yù)期的NullReferenceException棒坏。google了一下MissingReferenceException燕差,原來在Editor環(huán)境下,為了出現(xiàn)這種問題時(shí)能夠給出友好提示坝冕,UnityEditor保存了相關(guān)的上下文信息徒探,而編譯到目標(biāo)平臺(tái)后,舍棄了這些信息換來更快的運(yùn)行速度喂窟,就給出了NullReferenceException刹帕。見參考2。
回到最早的Bugly異常堆棧谎替,問題出現(xiàn)在lua中調(diào)用了transform.localPosition = xxx。搜索一下項(xiàng)目中的相應(yīng)代碼蹋辅,發(fā)現(xiàn)現(xiàn)有的邏輯加了如下判斷:
if trans ~= nil then
trans.localPosition = UnityEngine.Vector3.New(tempX,tempY,0)
end
由于這里lua的trans變量其實(shí)是一個(gè)UserData類型钱贯,指向mono中的對(duì)象,~=nil并不能判斷native對(duì)象是否銷毀了侦另。所以這里的保護(hù)代碼其實(shí)是無效的秩命。解決方法有兩個(gè):
1)在c#里寫一個(gè)判斷對(duì)象是否為null的方法導(dǎo)出給lua使用
2)在設(shè)置c#的TransformWrap設(shè)置localPosition值的時(shí)候,首先判斷當(dāng)前transform對(duì)象是否為null褒傅。我們使用的uLua弃锐,可以通過修改uLua生成Wrap類的腳本來統(tǒng)一實(shí)現(xiàn)。
參考: