Unity建立全局异常处理机制

我们知道,Unity的异常处理做得非常好,源于他在框架底层会自动捕获异常,所以一般的异常(比如空引用、除0操作之类)均不会导致整个进程crash掉,原因很简单,代码在try段中发生了异常,在catch段处理以后,表现在Unity编辑器中便是在日志窗口打印红色错误日志,而在已经发布的项目中,由于异常依然被Unity捕获,所以进程并不会crash掉,但对于我们而言他是未知的,这个异常很明显会导致程序在功能表现上的不正确。

那么我们需要的便是在发生任何未知异常的时候,获取异常的相关信息,用户的反馈信息,同时希望结束掉整个程序,毕竟发生了未知的异常即便他无关紧要,但总会导致某些功能的不正常表现。

好在Unity对于这方面的封装非常完善,我们完全可以借助他自身的异常处理机制来完善我们的需求。

如下:

public static void RegisterLogCallback( Application.LogCallback handler );

RegisterLogCallback是Application中的静态方法,旨在将LogCallback类型的函数注册给Unity的日志回调委托,在Unity输出任何日志的时候都将会调用到方法handler。

在handler中我们就可以截获任何的日志类型(这里的日志包括Debug.Log输出的日志,以及Unity自身在捕获到异常时会打印的异常日志),判断其为我们未知的异常,同时就可以做出我们自己的处理,包括在这里结束掉整个程序并弹出一个友好的bug反馈窗口。

委托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本身的异常,这种异常通常都是致命的,会导致整个进程crash;

3、Warning为警告日志,Debug.LogWarning输出的日志便是此类型;

4、Log为普通日志,Debug.Log输出的日志便是此类型;

5、Exception为被Unity捕获的未知异常,也就是我们自己的代码产生的异常,这将是我们处理的重点对象。

如此,我们将代码完善一下:

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 OnDestory()

{

//清除注册

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.Error、LogType.Exception、LogType.Assert 类型的日志输出请求时,就将日志信息打印到本地文件,同时主程序退出,启动bug反馈程序,我这里的bug反馈程序是一个winform窗口程序,仿照了类似QQ的报错界面:

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里面的调用将会报空引用错。

我们将项目发布,同时将bug反馈程序拷入到exe的同级目录:

我们运行Test.exe,点击屏幕左上角的按钮:

这时引发异常,主程序会直接退出,bug反馈程序启动,我们可以查看错误的日志信息:

我们可以看到一个并未被我们手动用try catch处理的异常被捕获了,这样的机制很明显省去了我们苦心积虑的研究哪个地方该用try catch的苦恼,因为所有的异常都不会被测试遗落同时会展现出非常详细的错误日志,以便于我们修改,当然用户的反馈也是解决这些bug的重大保障。

不过针对平台的不同,比如移动端可能并不能提供一个外部bug反馈程序,但只要能够捕获到这些未知的异常,接下来怎么做都可以,比如可以在程序里直接弹出一个窗口提示报错啦,然后用户填写相关错误反馈,点击确定之后后台提交反馈,同时程序自动重启,如果认为界面或者内容上的一些由异常引发的功能表现不正确无关紧要的话,这里也不用重启,毕竟只要不是Assert类型的异常,Unity都不会crash。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容