1、设置开启崩溃捕获
- static
int s_fatal_signals[] = { -
SIGABRT, -
SIGBUS, -
SIGFPE, -
SIGILL, -
SIGSEGV, -
SIGTRAP, -
SIGTERM, -
SIGKILL, - };
-
- static
const char* s_fatal_signal_names[] = { -
"SIGABRT", -
"SIGBUS", -
"SIGFPE", -
"SIGILL", -
"SIGSEGV", -
"SIGTRAP", -
"SIGTERM", -
"SIGKILL", - };
-
- static
int s_fatal_signal_num = sizeof(s_fatal_signals) / sizeof(s_fatal_signals[0]); -
- void
InitCrashReport() - {
-
// 1 linux错误信号捕获 -
for (int i = 0; i < s_fatal_signal_num; ++i) { -
signal(s_fatal_signals[i], SignalHandler); -
} -
-
// 2 objective-c未捕获异常的捕获 -
NSSetUncaughtExceptionHa ndler(&HandleException); - }
static int s_fatal_signals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP, SIGTERM, SIGKILL, }; static const char* s_fatal_signal_names[] = { "SIGABRT", "SIGBUS", "SIGFPE", "SIGILL", "SIGSEGV", "SIGTRAP", "SIGTERM", "SIGKILL", }; static int s_fatal_signal_num = sizeof(s_fatal_signals) / sizeof(s_fatal_signals[0]); void InitCrashReport() { // 1 linux错误信号捕获 for (int i = 0; i < s_fatal_signal_num; ++i) { signal(s_fatal_signals[i], SignalHandler); } // 2 objective-c未捕获异常的捕获 NSSetUncaughtExceptionHandler(&HandleException); }
在游戏的最开始调用InitCrashReport()函数来开启崩溃捕获。
2、打印堆栈信息
- +
(NSArray *)backtrace - {
-
void* callstack[128]; -
int frames = backtrace(callstack, 128); -
char **strs = backtrace_symbols(callstack, frames); -
-
int i; -
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames]; -
for (i = kSkipAddressCount; -
i < __min(kSkipAddressCount + kReportAddressCount, frames); -
++i) { -
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]]; -
} -
free(strs); -
-
return backtrace; - }
+ (NSArray *)backtrace { void* callstack[128]; int frames = backtrace(callstack, 128); char **strs = backtrace_symbols(callstack, frames); int i; NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames]; for (i = kSkipAddressCount; i < __min(kSkipAddressCount + kReportAddressCount, frames); ++i) { [backtrace addObject:[NSString stringWithUTF8String:strs[i]]]; } free(strs); return backtrace; }
幸好,苹果的iOS系统支持backtrace,通过这个函数可以直接打印出程序崩溃的调用堆栈。优点是,什么符号函数表都不需要,也不需要保存发布出去的对应版本,直接查看崩溃堆栈。缺点是,不能打印出具体哪一行崩溃,很多问题知道了是哪个函数崩的,但是还是查不出是因为什么崩的

3、日志上传,这个需要看实际需求,比如我们公司就是把崩溃信息http post到一个php服务器。这里就不多做声明了。
4、技巧---崩溃后程序保持运行状态而不退出
- CFRunLoopRef
runLoop = CFRunLoopGetCurrent(); -
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop); -
-
while (!dismissed) -
{ -
for (NSString *mode in (__bridge NSArray *)allModes) -
{ -
CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false); -
} -
} -
-
CFRelease(allModes);
CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop); while (!dismissed) { for (NSString *mode in (__bridge NSArray *)allModes) { CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false); } } CFRelease(allModes);
在崩溃处理函数上传完日志信息后,调用上述代码,可以重新构建程序主循环。这样,程序即便崩溃了,依然可以正常运行(当然,这个时候是处于不稳定状态,但是由于手持游戏和应用大多是短期操作,不会有挂机这种说法,所以稳定与否就无关紧要了)。玩家甚至感受不到崩溃。
这里要在说明一个感念,那就是“可重入(reentrant)”。简单来说,当我们的崩溃回调函数是可重入的时候,那么再次发生崩溃的时候,依然可以正常运行这个新的函数;但是如果是不可重入的,则无法运行(这个时候就彻底死了)。要实现上面描述的效果,并且还要保证回调函数是可重入的几乎不可能。所以,我测试的结果是,objective-c的异常触发多少次都可以正常运行。但是如果多次触发错误信号,那么程序就会卡死。
二、android崩溃捕获和收集
1、android开启崩溃捕获
Thread.setDefaultUncaughtExcept
- void
InitCrashReport() - {
-
CCLOG("InitCrashReport"); -
-
// Try to catch crashes... -
struct sigaction handler; -
memset(&handler, 0, sizeof(struct sigaction)); -
-
handler.sa_sigaction = android_sigaction; -
handler.sa_flags = SA_RESETHAND; -
- #define
CATCHSIG(X) sigaction(X, &handler, &old_sa[X]) -
CATCHSIG(SIGILL); -
CATCHSIG(SIGABRT); -
CATCHSIG(SIGBUS); -
CATCHSIG(SIGFPE); -
CATCHSIG(SIGSEGV); -
CATCHSIG(SIGSTKFLT); -
CATCHSIG(SIGPIPE); - }
void InitCrashReport() { CCLOG("InitCrashReport"); // Try to catch crashes... struct sigaction handler; memset(&handler, 0, sizeof(struct sigaction)); handler.sa_sigaction = android_sigaction; handler.sa_flags = SA_RESETHAND; #define CATCHSIG(X) sigaction(X, &handler, &old_sa[X]) CATCHSIG(SIGILL); CATCHSIG(SIGABRT); CATCHSIG(SIGBUS); CATCHSIG(SIGFPE); CATCHSIG(SIGSEGV); CATCHSIG(SIGSTKFLT); CATCHSIG(SIGPIPE); }通过singal的设置,当崩溃发生的时候就会调用android_sigaction函数。这同样是linux的信号机制。 此处设置信号回调函数的代码跟iOS有点不同,这个只是同一个功能的两种不同写法,没有本质区别。有兴趣的可以google下两者的区别。
2、打印堆栈
- void
android_sigaction(int signal, siginfo_t *info, void *reserved) - {
-
if (!g_env) { -
return; -
} -
-
jclass classID = g_env->FindClass(CLASS_NAME); -
if (!classID) { -
return; -
} -
-
jmethodID methodID = g_env->GetStaticMethodID(classID, "onNativeCrashed", "()V"); -
if (!methodID) { -
return; -
} -
-
g_env->CallStaticVoidMethod(classID, methodID); -
-
old_sa[signal].sa_handler(signal); - }
void android_sigaction(int signal, siginfo_t *info, void *reserved) { if (!g_env) { return; } jclass classID = g_env->FindClass(CLASS_NAME); if (!classID) { return; } jmethodID methodID = g_env->GetStaticMethodID(classID, "onNativeCrashed", "()V"); if (!methodID) { return; } g_env->CallStaticVoidMethod(classID, methodID); old_sa[signal].sa_handler(signal); }
可以看到,我们仅仅是通过jni调用了java的一个函数,然后所有的处理都是在java层面完成。
java对应的函数实现如下:
- public
static void onNativeCrashed() { -
// http://stackoverflow.com/questions/1083154/how-can-i-catch-sigsegv-segmentation-fault-and-get-a-stack-trace-under-jni-on-a -
Log.e("handller", "handle"); -
new RuntimeException("crashed here (native trace should follow after the Java trace)").printStackTrace(); -
s_instance.startActivity(new Intent(s_instance, CrashHandler.class)); -
}
public static void onNativeCrashed() { // http://stackoverflow.com/questions/1083154/how-can-i-catch-sigsegv-segmentation-fault-and-get-a-stack-trace-under-jni-on-a Log.e("handller", "handle"); new RuntimeException("crashed here (native trace should follow after the Java trace)").printStackTrace(); s_instance.startActivity(new Intent(s_instance, CrashHandler.class)); }
我们开启了一个新的activity,因为当jni发生崩溃的时候,原始的activity可能已经结束掉了。
- public
class CrashHandler extends Activity - {
-
public static final String TAG = "CrashHandler"; -
protected void onCreate(Bundle state) -
{ -
super.onCreate(state); -
setTitle(R.string.crash_title); -
setContentView(R.layout.crashhandler); -
TextView v = (TextView)findViewById(R.id.crashText); -
v.setText(MessageFormat.format(getString(R.string.crashed), getString(R.string.app_name))); -
final Button b = (Button)findViewById(R.id.report), -
c = (Button)findViewById(R.id.close); -
b.setOnClickListener(new View.OnClickListener(){ -
public void onClick(View v){ -
final ProgressDialog progress = new ProgressDialog(CrashHandler.this); -
progress.setMessage(getString(R.string.getting_log)); -
progress.setIndeterminate(true); -
progress.setCancelable(false); -
progress.show(); -
final AsyncTask task = new LogTask(CrashHandler.this, progress).execute(); -
b.postDelayed(new Runnable(){ -
public void run(){ -
if (task.getStatus() == AsyncTask.Status.FINISHED) -
return; -
// It's probably one of these devices where some fool broke logcat. -
progress.dismiss(); -
task.cancel(true); -
new AlertDialog.Builder(CrashHandler.this) -
.setMessage(MessageFormat.format(getString(R.string.get_log_failed), getString(R.string.author_email))) -
.setCancelable(true) -
.setIcon(android.R.drawable.ic_dialog_alert) -
.show(); -
}}, 3000); -
}}); -
c.setOnClickListener(new View.OnClickListener(){ -
public void onClick(View v){ -
finish(); -
}}); -
} -
-
static String getVersion(Context c) -
{ -
try { -
return c.getPackageManager().getPackageInfo(c.getPackageName(),0).versionName; -
} catch(Exception e) { -
return c.getString(R.string.unknown_version); -
} -
} - }
-
- class
LogTask extends AsyncTask - {
-
Activity activity; -
String logText; -
Process process; -
ProgressDialog progress; -
-
LogTask(Activity a, ProgressDialog p) { -
activity = a; -
progress = p; -
} -
-
@Override -
protected Void doInBackground(Void... v) { -
try { -
Log.e("crash", "doInBackground begin"); -
process = Runtime.getRuntime().exec(new String[]{"logcat","-d","-t","500","-v","threadtime"}); -
logText = UncaughtExceptionHandler .readFromLogcat(process.getInputStream()); -
Log.e("crash", "doInBackground end"); -
} catch (IOException e) { -
e.printStackTrace(); -
Toast.makeText(activity, e.toString(), Toast.LENGTH_LONG).show(); -
} -
return null; -
} -
-
@Override -
protected void onCancelled() { -
Log.e("crash", "onCancelled"); -
process.destroy(); -
} -
-
@Override -
protected void onPostExecute(Void v) { -
Log.e("crash", "onPostExecute"); -
progress.setMessage(activity.getString(R.string.starting_email)); -
UncaughtExceptionHandler .sendLog(logText, activity); -
progress.dismiss(); -
activity.finish(); -
Log.e("crash", "onPostExecute over"); -
}
public class CrashHandler extends Activity { public static final String TAG = "CrashHandler"; protected void onCreate(Bundle state) { super.onCreate(state); setTitle(R.string.crash_title); setContentView(R.layout.crashhandler); TextView v = (TextView)findViewById(R.id.crashText); v.setText(MessageFormat.format(getString(R.string.crashed), getString(R.string.app_name))); final Button b = (Button)findViewById(R.id.report), c = (Button)findViewById(R.id.close); b.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ final ProgressDialog progress = new ProgressDialog(CrashHandler.this); progress.setMessage(getString(R.string.getting_log)); progress.setIndeterminate(true); progress.setCancelable(false); progress.show(); final AsyncTask task = new LogTask(CrashHandler.this, progress).execute(); b.postDelayed(new Runnable(){ public void run(){ if (task.getStatus() == AsyncTask.Status.FINISHED) return; // It's probably one of these devices where some fool broke logcat. progress.dismiss(); task.cancel(true); new AlertDialog.Builder(CrashHandler.this) .setMessage(MessageFormat.format(getString(R.string.get_log_failed), getString(R.string.author_email))) .setCancelable(true) .setIcon(android.R.drawable.ic_dialog_alert) .show(); }}, 3000); }}); c.setOnClickListener(new View.OnClickListener(){ public void onClick(View v){ finish(); }}); } static String getVersion(Context c) { try { return c.getPackageManager().getPackageInfo(c.getPackageName(),0).versionName; } catch(Exception e) { return c.getString(R.string.unknown_version); } } } class LogTask extends AsyncTask { Activity activity; String logText; Process process; ProgressDialog progress; LogTask(Activity a, ProgressDialog p) { activity = a; progress = p; } @Override protected Void doInBackground(Void... v) { try { Log.e("crash", "doInBackground begin"); process = Runtime.getRuntime().exec(new String[]{"logcat","-d","-t","500","-v","threadtime"}); logText = UncaughtExceptionHandler.readFromLogcat(process.getInputStream()); Log.e("crash", "doInBackground end"); } catch (IOException e) { e.printStackTrace(); Toast.makeText(activity, e.toString(), Toast.LENGTH_LONG).show(); } return null; } @Override protected void onCancelled() { Log.e("crash", "onCancelled"); process.destroy(); } @Override protected void onPostExecute(Void v) { Log.e("crash", "onPostExecute"); progress.setMessage(activity.getString(R.string.starting_email)); UncaughtExceptionHandler .sendLog(logText, activity); progress.dismiss(); activity.finish(); Log.e("crash", "onPostExecute over"); }
最主要的地方是doInBackground函数,这个函数通过logcat获取了崩溃信息。 不要忘记在AndroidManifest.xml添加读取LOG的权限
- <</span>uses-permission
android:name="android.permission.READ_LOGS" />
3、获取到错误日志后,就可以写到sd卡(同样不要忘记添加权限),或者是上传。
我们在获取到的错误日志中,可以截取到如下信息:
- 12-12
20:41:31.807 24206 24206 I DEBUG : - 12-12
20:41:31.847 24206 24206 I DEBUG : #00 pc 004931f8 /data/data/org.cocos2dx.wing/lib/libhelloworld.so - 12-12
20:41:31.847 24206 24206 I DEBUG : #01 pc 005b3a5e /data/data/org.cocos2dx.wing/lib/libhelloworld.so - 12-12
20:41:31.847 24206 24206 I DEBUG : #02 pc 005aab68 /data/data/org.cocos2dx.wing/lib/libhelloworld.so - 12-12
20:41:31.847 24206 24206 I DEBUG : #03 pc 005ad8aa /data/data/org.cocos2dx.wing/lib/libhelloworld.so - 12-12
20:41:31.847 24206 24206 I DEBUG : #04 pc 005924a4 /data/data/org.cocos2dx.wing/lib/libhelloworld.so - 12-12
20:41:31.847 24206 24206 I DEBUG : #05 pc 005929b6 /data/data/org.cocos2dx.wing/lib/libhelloworld.so
12-12 20:41:31.807 24206 24206 I DEBUG : 12-12 20:41:31.847 24206 24206 I DEBUG : #00 pc 004931f8 /data/data/org.cocos2dx.wing/lib/libhelloworld.so 12-12 20:41:31.847 24206 24206 I DEBUG : #01 pc 005b3a5e /data/data/org.cocos2dx.wing/lib/libhelloworld.so 12-12 20:41:31.847 24206 24206 I DEBUG : #02 pc 005aab68 /data/data/org.cocos2dx.wing/lib/libhelloworld.so 12-12 20:41:31.847 24206 24206 I DEBUG : #03 pc 005ad8aa /data/data/org.cocos2dx.wing/lib/libhelloworld.so 12-12 20:41:31.847 24206 24206 I DEBUG : #04 pc 005924a4 /data/data/org.cocos2dx.wing/lib/libhelloworld.so 12-12 20:41:31.847 24206 24206 I DEBUG : #05 pc 005929b6 /data/data/org.cocos2dx.wing/lib/libhelloworld.so
- 004931f8
004931f8这个就是我们崩溃函数的地址,
arm-linux-androideabi-addr2line.exe -e 动态库名称
例如:
- $
/cygdrive/d/devandroid/android-ndk-r8c-windows/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/arm-linux-androideabi-addr2line.exe -e obj/local/armeabi-v7a/libhelloworld.so 004931f8
$ /cygdrive/d/devandroid/android-ndk-r8c-windows/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/arm-linux-androideabi-addr2line.exe -e obj/local/armeabi-v7a/libhelloworld.so 004931f8得到的结果就是哪个cpp文件第几行崩溃。