Unity建立全局异常处理机制

2017年04月13日 15:17 0 点赞 0 评论 更新于 2025-11-21 13:38

在开发Unity项目时,我们会发现Unity本身的异常处理机制较为出色。其框架底层会自动捕获异常,所以常见的异常(如空引用、除零操作等)一般不会导致整个进程崩溃。当代码在try段中发生异常,在catch段处理后,在Unity编辑器的日志窗口会打印红色错误日志;在已发布的项目中,由于异常仍被Unity捕获,进程也不会崩溃。然而,这些未被我们察觉的异常很可能会导致程序在功能表现上出现问题。

因此,我们需要在发生任何未知异常时,获取异常的相关信息和用户的反馈信息,并且在必要时结束整个程序。毕竟,即使某些未知异常看似无关紧要,但仍可能影响部分功能的正常运行。

好在Unity对这方面的封装十分完善,我们可以借助其自身的异常处理机制来满足需求。

Unity的日志回调机制

注册日志回调函数

RegisterLogCallbackApplication类中的静态方法,用于将LogCallback类型的函数注册给Unity的日志回调委托。当Unity输出任何日志时,都会调用该方法。其函数签名如下:

public static void RegisterLogCallback( Application.LogCallback handler );

handler函数中,我们可以截获各种类型的日志,包括Debug.Log输出的日志以及Unity捕获异常时打印的异常日志。通过判断日志类型,我们可以对未知异常进行处理,例如结束程序并弹出友好的错误反馈窗口。

日志回调委托的形式

LogCallback委托的形式如下:

public delegate void LogCallback( string condition, string stackTrace, LogType type );

其中:

  • condition:日志的具体内容。
  • stackTrace:相关的堆栈调用信息。
  • type:日志的类型,日志类型分为以下五种:
    public enum LogType
    {
    Error = 0,
    Assert = 1,
    Warning = 2,
    Log = 3,
    Exception = 4,
    }
    

    下面对这五种日志类型进行详细说明:

    1. Error:错误日志,Debug.LogError输出的日志属于此类型。
    2. Assert:Unity本身的异常,通常是致命的,会导致整个进程崩溃。
    3. Warning:警告日志,Debug.LogWarning输出的日志属于此类型。
    4. Log:普通日志,Debug.Log输出的日志属于此类型。
    5. Exception:被Unity捕获的未知异常,也就是我们自己代码产生的异常,是我们处理的重点对象。

完善异常处理代码

以下是一个完整的异常处理类ExceptionHandler的代码示例:

using UnityEngine;
using System;
using System.IO;
using System.Diagnostics;
using System.Collections;

public class ExceptionHandler : MonoBehaviour
{
// 是否作为异常处理者
public bool IsHandler = false;
// 是否在异常发生时退出程序
public bool IsQuitWhenException = true;
// 异常日志保存路径(文件夹)
private string LogPath;
// Bug反馈程序的启动路径
private string BugExePath;

void Awake()
{
LogPath = Application.dataPath.Substring( 0, Application.dataPath.LastIndexOf( "/" ) );
BugExePath = Application.dataPath.Substring( 0, Application.dataPath.LastIndexOf( "/" ) ) + "\\Bug.exe";

// 注册异常处理委托
if ( IsHandler )
{
Application.RegisterLogCallback( Handler );
}
}

void OnDestroy()
{
// 清除注册
Application.RegisterLogCallback( null );
}

void Handler( string logString, string stackTrace, LogType type )
{
if ( type == LogType.Error || type == LogType.Exception || type == LogType.Assert )
{
string logPath = LogPath + "\\" + DateTime.Now.ToString( "yyyy_MM_dd HH_mm_ss" ) + ".log";
// 打印日志
if ( Directory.Exists( LogPath ) )
{
File.AppendAllText( logPath, "[time]:" + DateTime.Now.ToString() + "\r\n" );
File.AppendAllText( logPath, "[type]:" + type.ToString() + "\r\n" );
File.AppendAllText( logPath, "[exception message]:" + logString + "\r\n" );
File.AppendAllText( logPath, "[stack trace]:" + stackTrace + "\r\n" );
}
// 启动bug反馈程序
if ( File.Exists( BugExePath ) )
{
ProcessStartInfo pros = new ProcessStartInfo();
pros.FileName = BugExePath;
pros.Arguments = "\"" + logPath + "\"";
Process pro = new Process();
pro.StartInfo = pros;
pro.Start();
}
// 退出程序,bug反馈程序重启主程序
if ( IsQuitWhenException )
{
Application.Quit();
}
}
}
}

处理机制说明

当接收到LogType.ErrorLogType.ExceptionLogType.Assert类型的日志输出请求时,程序会将日志信息打印到本地文件,同时主程序退出,并启动bug反馈程序。这里的bug反馈程序是一个WinForm窗口程序,仿照了类似QQ的报错界面。

Bug反馈程序

Bug反馈程序会接收一个参数,即日志文件的路径。这样,在反馈界面中,用户输入的引起异常的原因可以添加到错误日志中。如果用户选择发送错误报告,该错误日志将被上传到服务器。需要注意的是,如果不提供参数,直接运行bug反馈程序将无法成功。

测试异常处理机制

编写测试脚本

在Unity中新建一个测试脚本Test.cs,内容如下:

using UnityEngine;
using System.Collections;

public class Test : MonoBehaviour
{
public GameObject TestObj;

void OnGUI()
{
if ( GUILayout.Button( "点我就会抛出一个异常" ) )
{
TestObj.transform.position = Vector3.one;
}
}
}

这里的TestObj不进行赋值,因此在OnGUI方法中的调用会引发空引用异常。

测试步骤

  1. 将项目发布。
  2. 将bug反馈程序拷贝到exe的同级目录。
  3. 运行Test.exe,点击屏幕左上角的按钮。

此时,程序会捕获到异常,主程序退出,bug反馈程序启动。我们可以查看错误日志信息,这样的机制避免了手动在每个可能出现异常的地方使用try-catch语句,所有异常都能被捕获并记录详细信息,方便我们进行修改。同时,用户的反馈也是解决这些bug的重要依据。

不同平台的处理

对于不同平台,处理方式可能有所不同。例如,在移动端可能无法提供外部的bug反馈程序,但只要能够捕获到未知异常,我们可以在程序中直接弹出窗口提示报错,让用户填写相关错误反馈,点击确定后将反馈信息提交到后台,同时程序可以选择自动重启。如果某些由异常引发的界面或内容上的功能表现问题无关紧要,也可以不进行重启操作,因为只要不是Assert类型的异常,Unity一般不会崩溃。