當遊戲在手機/模擬器上卡死,logcat沒有日誌輸出,也沒有卡死堆疊資訊或者bugly也沒有捕獲到異常,你是否很焦急?本文介紹一下我們專案中檢測Unity卡死的方法,也許適合你使用。
實現原理
在絕大多數情況下我們可以認為Unity是單執行緒的,基於這點我們在Unity的系統函式FixedUpdate中統計遊戲執行期間的總幀數,如果Unity沒有卡死,那麼TotalFrame是會一直累加的,如果在某一段時間內TotalFrame都不會變化了,則可以認為Unity已經卡死了
既然Unity的主執行緒已經卡死了,我們就需要用另一個執行緒用來定時檢查unity主執行緒中的TotalFrame是否不會變化了
示例程式碼
using System;
using System.Threading;
using UnityEngine;
namespace KEngine
{
/// <summary>
/// 開另外一個執行緒檢測unity是否被卡死
/// </summary>
public static class UnityThreadDetect
{
public static Thread _MainThread = System.Threading.Thread.CurrentThread;//獲取unity執行緒
private static int check_interval = 3000;//檢測間隔
public static void Start()
{
new Thread(CheckMainThread).Start();
}
static void CheckMainThread()
{
long frame = 0;
while(!AppEngine.IsApplicationQuit)
{
frame = AppEngine.TotalFrame;
Thread.Sleep(check_interval);
if (frame == AppEngine.TotalFrame)
{
Log.LogToFile("unity thread dead,ThreadState:{0}",_MainThread.ThreadState);
if (AppEngine.IsApplicationFocus)
{
//todo report error
}
}
}
}
}
}
捕獲卡死的方法名
在我們的遊戲中一般出現卡死的情況都是在定時器裡面,我們的定時器是通過在Unity的Update驅動定時器列表,當卡死時,在另一個執行緒中列印出定時器中正在執行的函式就可以定位到卡死的函式了。定時器可參考:UnityTimer中的Timer.cs
同時在Unity的Update進行派發多個事件,比如PreUpdate,Update,以便出問題更容易定位到卡在那兒
舉例說明問題
下面舉例我們遇到的出現卡死的問題
死迴圈
下面這個死迴圈在Unity中會卡死,而在.NET中不會,.NET中當i超過byte的最大值255時i會從0開始
public static void TesBadCode()
{
byte i = 0;
while (true)
{
i++;
}
}
目前我們遇到的絕大多數情況都是邏輯程式碼中寫了where(true) do xxx
然後裡面某些情況不會break,導致迴圈永遠退不出來
遮蔽了事件系統
在某些系統中遮蔽掉了UGUI的事件系統,導致無法接受使用者輸入,這個問題不應該歸類為Unity卡死,但使用者反饋來看就是卡死了,無法操作。
重複新增定時器
起因是底層沒有對同名定時器進行限制,在某些邏輯中誤使用,出現每秒新增一個定時器,而定時器中的邏輯很大且長時間不退出的,當不斷新增重複定時器就導致遊戲執行越來越慢
重複註冊事件
在一些介面的重新整理函式和控制器函式,被頻繁重複註冊了事件,導致在丟擲事件時,同一個函式被呼叫了N次,這個問題在Unity的Profiler中可以清晰看到函式的呼叫次數
擴充套件
遞迴呼叫
遞迴呼叫,會報stack overflow,不會讓unity卡死
為什麼無限迴圈遞迴呼叫不會卡死Unity?
這是因為每個方法的方法呼叫棧容量
是有限的,當超出之後就會跳出報stack overflow,不會讓應用程式卡死
public static void TesBadCode()
{
while (true)
{
TesBadCode();
}
}