(插播)unity的 異常捕捉和 ios Android 崩潰資訊的捕捉。
最近 做些準備性得工作和有意思的事情。所以最近做了一個適合ios和android 錯誤資訊捕捉的unity外掛。
兩個功能,app崩潰也就是閃退 是開發者 很頭疼的一件事,還有就是一些莫名得錯誤 有時候也會困擾著我們。現在,unity已經封裝得挺好了,及時出現數組越界,和空物件這樣嚴重得錯誤也不會崩潰,聽著挺好,但是這給開發者帶了很多煩惱啊。因為有時候可能出錯了 你要跟就不知道 ,在什麼地方出得錯誤啊。所以我們要想辦法去解決這個問題。
我們都知道及時app崩潰,其實後臺還是在執行得 只不過是 到了另一個執行緒去處理崩潰得一些問題。那好我們就可以去捕捉到錯誤,供我們去理解這個問題。
首先 我們先針對 ios閃退得錯誤得問題。我們知道 現在ios還是才去得OC程式設計,身為一個編譯性得語言,一般很小得一個錯誤都會造成整個app得崩潰。
接下來 我們說下原理。 對於 IOS的機制是,它專門有一個類來處理異常錯誤。那就是NSException類,來處理各種錯誤得一個類。我們要的就是這個類中得一個通知,就是 在app出現異常崩潰的事後會通知得一個方法C語言的NSSetUncaughtExceptionHandler。我們就是用這個方法來進行 異常註冊。
NSSetUncaughtExceptionHandler(&caughtExceptionHandler);
其中 caughtExceptionHandler是自己寫得類中得一個C語言得方法。
可以看到 在這個方法中我們就可以拿到NSException得錯誤得指標。就可以得到所有得錯誤得資訊。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;
}
}
工程再次哦