最近 做些准备性得工作和有意思的事情。所以最近做了一个适合ios和android 错误信息捕捉的unity插件。
两个功能,app崩溃也就是闪退 是开发者 很头疼的一件事,还有就是一些莫名得错误 有时候也会困扰着我们。现在,unity已经封装得挺好了,及时出现数组越界,和空对象这样严重得错误也不会崩溃,听着挺好,但是这给开发者带了很多烦恼啊。因为有时候可能出错了 你要跟就不知道 ,在什么地方出得错误啊。所以我们要想办法去解决这个问题。
我们都知道及时app崩溃,其实后台还是在运行得 只不过是 到了另一个线程去处理崩溃得一些问题。那好我们就可以去捕捉到错误,供我们去理解这个问题。
首先 我们先针对 ios闪退得错误得问题。我们知道 现在ios还是才去得OC编程,身为一个编译性得语言,一般很小得一个错误都会造成整个app得崩溃。
接下来 我们说下原理。 对于 IOS的机制是,它专门有一个类来处理异常错误。那就是NSException类,来处理各种错误得一个类。我们要的就是这个类中得一个通知,就是 在app出现异常崩溃的事后会通知得一个方法C语言的NSSetUncaughtExceptionHandler。我们就是用这个方法来进行 异常注册。
NSSetUncaughtExceptionHandler(&caughtExceptionHandler);其中 caughtExceptionHandler是自己写得类中得一个C语言得方法。
void caughtExceptionHandler(NSException *e){}
这里知道了一半,信息我们是得到了,但是app现在是处于崩溃中,我们想让我们的错误信息传到服务器或者返回给开发者得处理平台。那我们得线程必须等待才可以,这样才能给我们时间把错误信息上传到处理平台。那怎么才能把线程阻塞调那 正好 ios有线程进入等待得方法CFRunLoopRunInMode()
好了想好这个多 我们开始 进行实施了。
这是 异常获取得方法,把此方法 进行注册 也就是
-(void) _registerException{ NSSetUncaughtExceptionHandler(&caughtExceptionHandler); }
void caughtExceptionHandler(NSException *e){ NSString *currentTime = [[VKCatchCrash shareCatchCrash] getCurrentTime]; CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop); NSArray *arr = [e callStackSymbols]; NSString *reason = [e reason]; NSString *name = [e name]; NSMutableDictionary *dic = [[NSMutableDictionary alloc]init]; [dic setValue:[[VKCatchCrash shareCatchCrash] _getDeviceInfo] forKey:@"device"]; [dic setValue:arr forKey:@"callStackSymbols"]; [dic setValue:reason forKey:@"reason"]; [dic setValue:name forKey:@"name"]; [dic setObject:currentTime forKey:@"time"]; NSError *error = nil; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&error]; if ([jsonData length] > 0 && error == nil){ NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; NSString *path = [[VKCatchCrash shareCatchCrash] fullScreenshots:currentTime]; //if([[VKCatchCrash shareCatchCrash] checkNetwork]){ [Upload startUpload:[Upload initUpBackBlock:^(NSData *data, NSURLResponse *response, NSError *error) { NSFileManager *manager = [NSFileManager defaultManager]; if([manager fileExistsAtPath:path]){ [manager removeItemAtPath:path error:nil]; } } upUrl:HOST upDelegate:nil formName:@"fileField" filePath:path contentKey:[[NSArray alloc] initWithObjects:@"bug", nil] contentValue:[[NSArray alloc] initWithObjects:jsonString, nil]]]; // } NSLog(@"%@",jsonString); [[VKCatchCrash shareCatchCrash] performSelectorOnMainThread:@selector(alertUploadBug) withObject:nil waitUntilDone:YES]; while (!dismiss) { for (NSString *mode in (__bridge NSArray *)allModes) { CFRunLoopRunInMode((__bridge CFStringRef)mode, 0, false); } } CFRelease(allModes); NSSetUncaughtExceptionHandler(NULL); }else{ NSLog(@"nil"); } }警告框提示app闪退。
-(void)alertUploadBug{ UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"产生莫名得崩溃,报告正在发送服务器!" message:nil delegate:self cancelButtonTitle:@"确定" otherButtonTitles:nil, nil]; [alert show]; }
当前时间
-(NSString *)getCurrentTime{ NSDate * senddate=[NSDate date]; NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init]; [dateformatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"]; NSString * locationString=[dateformatter stringFromDate:senddate]; return locationString; }
这里还用到了一个 自己写的额一个上传得类VKHttpManager,回来我会上传 源码。
基本信息就这么多。详情可以看下 源码。
下面我们 我们来说下Android得闪退得异常捕获。
Android相对于ios比较简单,只要使用一个类来接口UncaughtExceptionHandler就可以了
然后有借口得方法,会自动调用该回调。
public class MyCrashHandler implements UncaughtExceptionHandler{ @Override public void uncaughtException(Thread thread, Throwable ex) { } }
其中 Throwable得异常对象就是我们需要得异常对象。
其中这里也有 和ios相似得地方 那就是 线程阻塞
Looper.prepare();
Looper.loop();
这里有一个地方要注意得就是 在安卓中 unity调用 AndroidUI得东西 要使用 UI线程不然是无法显示。详情见下面得源码
UnityPlayer.currentActivity.runOnUiThread(new Runnable() { @Override public void run() { } };
然后 unity里我们还要做些东西来捕获unity得异常和对ios和Android进行对接得一些东西。
using UnityEngine; using System.Collections; using System.Runtime.InteropServices; public class CatchCrash : MonoBehaviour { private static string HOST ="http://192.168.1.240/game/"; private static string currentTime = ""; #if UNITY_IPHONE [DllImport ("__Internal")] private static extern void registerException (string host); [DllImport ("__Internal")] private static extern void createBug (); [DllImport ("__Internal")] private static extern void unityBugAlert (string msg); [DllImport ("__Internal")] private static extern string getDeviceInfo (); #elif UNITY_ANDROID AndroidJavaClass jc = null; AndroidJavaObject jo = null; #endif // Use this for initialization void Awake () { #if UNITY_EDITOR #elif UNITY_IPHONE || UNITY_ANDROID DontDestroyOnLoad (gameObject); Application.RegisterLogCallback(OnLog); #endif #if UNITY_IPHONE registerException (HOST); #elif UNITY_ANDROID jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); jo = jc.GetStatic<AndroidJavaObject>("currentActivity"); jo.Call("registerException",HOST); #endif } void OnLog (string message, string stacktrace, LogType type) { if (type == LogType.Exception) { currentTime = getcurrentTime().ToString(); string butMsg = "{\n"+ "\"message\":" +"\""+message.Replace("\n","")+"\""+ ",\n\"stacktrace\":"+"\""+stacktrace.Replace("\n","")+"\""+ ",\n\"time\":"+"\""+currentTime+"\"" +"\n" + "\"device\":" +"\""+ #if UNITY_IPHONE getDeviceInfo().Replace("\n","")+"\""+ #elif UNITY_ANDROID jo.CallStatic<string>("getDeviceInfo").Replace("\n","")+"\""+ #endif "\n}"; StartCoroutine(uploadBug(butMsg)); #if UNITY_IPHONE unityBugAlert (butMsg); #elif UNITY_ANDROID jo.CallStatic("unityBugAlert",butMsg); #endif } } IEnumerator uploadBug(string butMsg){ yield return new WaitForEndOfFrame(); int width = Screen.width; int height = Screen.height; Texture2D tex = new Texture2D (width,height,TextureFormat.RGB24, false); tex.ReadPixels(new Rect(0, 0, width, height), 0, 0 ); tex.Apply(); byte [] bytes = tex.EncodeToPNG (); WWWForm form = new WWWForm(); form.AddField ("bug",butMsg); form.AddBinaryData ("fileField",bytes,currentTime+".png","image/png"); WWW w = new WWW (HOST,form); yield return w; if (w.error != null) { Debug.Log (" upload bug erro"); } else { Debug.Log (" upload bug finish"); } } public static string getcurrentTime() { System.DateTime now = System.DateTime.Now; return now.Year + "-" + now.Month + "-" + now.Day + " " + now.Hour + ":" + now.Minute + ":" + now.Second; } }
工程再次哦