赞
踩
Crash(应用崩溃)是由于代码异常而导致 App 非正常退出,导致应用程序无法继续使用,所有工作都停止的现象。发生 Crash 后需要重新启动应用(有些情况会自动重启),而且不管应用在开发阶段做得多么优秀,也无法避免 Crash 发生,特别是在 Android 系统中,系统碎片化严重、各 ROM 之间的差异,甚至系统Bug,都可能会导致Crash的发生。
在 Android 应用中发生的 Crash 有两种类型,Java 层的 Crash 和 Native 层 Crash。这两种Crash 的监控和获取堆栈信息有所不同。
Java的Crash监控非常简单,Java中的Thread定义了一个接口: UncaughtExceptionHandler
;用于处理未捕获的异常导致线程的终止(注意:catch了的是捕获不到的),当我们的应用crash的时候,就会走 UncaughtExceptionHandler.uncaughtException ,在该方法中可以获取到异常的信息,我们通过 Thread.setDefaultUncaughtExceptionHandler 该方法来设置线程的默认异常处理器,我们可以将异常信息保存到本地或者是上传到服务器,方便我们快速的定位问题。
public class JavaCrashHandler implements Thread.UncaughtExceptionHandler { private static final String FILE_NAME_SUFFIX = ".log"; private static Thread.UncaughtExceptionHandler mDefaultCrashHandler; private static Context mContext; private JavaCrashHandler() { } public static void init(@NonNull Context context) { // 默认为:RuntimeInit#KillApplicationHandler mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler(); mContext = context.getApplicationContext(); Thread.setDefaultUncaughtExceptionHandler(new JavaCrashHandler()); } /*** 当程序中有未被捕获的异常,系统将会调用这个方法 ** @param t 出现未捕获异常的线程 * @param e 得到异常信息 */ @Override public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) { try { // 自行处理:保存本地 File file = dealException(thread, throwable); // 上传服务器 // ...... } catch (Exception e1) { e1.printStackTrace(); } finally { // 交给系统默认程序处理 if (mDefaultCrashHandler != null) { mDefaultCrashHandler.uncaughtException(thread, throwable); } } } /*** 导出异常信息到SD卡 ** @param e */ private File dealException(Thread thread, Throwable throwable) { String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); File crashFolder = new File(mContext.getExternalCacheDir().getAbsoluteFile(), CrashMonitor.DEFAULT_JAVA_CRASH_FOLDER_NAME); if (!crashFolder.exists()) { crashFolder.mkdirs(); } File crashFile = new File(crashFolder, time + FILE_NAME_SUFFIX); try { // 往文件中写入数据 PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(crashFile))); pw.println(time); pw.println(thread); pw.println(getPhoneInfo()); throwable.printStackTrace(pw); // 写入crash堆栈 pw.close(); } catch (IOException ex) { ex.printStackTrace(); } return crashFile; } private String getPhoneInfo() { PackageManager pm = mContext.getPackageManager(); PackageInfo pi = null; StringBuilder sb = new StringBuilder(); try { pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES); // App版本 sb.append("App Version: "); sb.append(pi.versionName); sb.append("_"); sb.append(pi.versionCode + "\n"); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } // Android版本号 sb.append("OS Version: "); sb.append(Build.VERSION.RELEASE); sb.append("_"); sb.append(Build.VERSION.SDK_INT + "\n"); // 手机制造商 sb.append("Vendor: "); sb.append(Build.MANUFACTURER + "\n"); // 手机型号 sb.append("Model: "); sb.append(Build.MODEL + "\n"); // CPU架构 sb.append("CPU: "); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { sb.append(Arrays.toString(Build.SUPPORTED_ABIS)); } else { sb.append(Build.CPU_ABI); } return sb.toString(); } }
系统默认设置的是RuntimeInit#KillApplicationHandler这个异常处理器,这个异常处理器的处理行为是打印crash日志并弹出一个crash对话框:
//RuntimeInit.java /** * Handle application death from an uncaught exception. The framework * catches these for the main threads, so this should only matter for * threads created by applications. Before this method runs, the given * instance of {@link LoggingHandler} should already have logged details * (and if not it is run first). */ private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler { private final LoggingHandler mLoggingHandler; /** * Create a new KillApplicationHandler that follows the given LoggingHandler. * If {@link #uncaughtException(Thread, Throwable) uncaughtException} is called * on the created instance without {@code loggingHandler} having been triggered, * {@link LoggingHandler#uncaughtException(Thread, Throwable) * loggingHandler.uncaughtException} will be called first. * * @param loggingHandler the {@link LoggingHandler} expected to have run before * this instance's {@link #uncaughtException(Thread, Throwable) uncaughtException} * is being called. */ public KillApplicationHandler(LoggingHandler loggingHandler) { this.mLoggingHandler = Objects.requireNonNull(loggingHandler); } @Override public void uncaughtException(Thread t, Throwable e) { try { ensureLogging(t, e); // Don't re-enter -- avoid infinite loops if crash-reporting crashes. if (mCrashing) return; mCrashing = true; // Try to end profiling. If a profiler is running at this point, and we kill the // process (below), the in-memory buffer will be lost. So try to stop, which will // flush the buffer. (This makes method trace profiling useful to debug crashes.) if (ActivityThread.currentActivityThread() != null) { ActivityThread.currentActivityThread().stopProfiling(); } // Bring up crash dialog, wait for it to be dismissed ActivityManager.getService().handleApplicationCrash( mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e)); } catch (Throwable t2) { if (t2 instanceof DeadObjectException) { // System process is dead; ignore } else { try { Clog_e(TAG, "Error reporting crash", t2); } catch (Throwable t3) { // Even Clog_e() fails! Oh well. } } } finally { // Try everything to make sure this process goes away. Process.killProcess(Process.myPid()); System.exit(10); } }
即默认的KillApplicationHandler异常处理器会调用Android系统的ActivityManager弹出一个crash对话框展示给用户。
Java 层的Crash很简单,关键就是一行代码: Thread.setDefaultUncaughtExceptionHandler(new JavaCrashHandler());
替换掉系统默认设置的异常处理器设置为自定义异常处理器即可。
相对于Java的Crash,NDK的错误无疑更加让人头疼,特别是对初学NDK的同学,不说监控,就算是错误堆栈都不知道怎么看。
信号机制是Linux进程间通信的一种重要方式,Linux信号一方面用于正常的进程间通信和同步,另一方面它还负责监控系统异常及中断。当应用程序运行异常时,Linux内核将产生错误信号并通知当前进程。当前进程在接收到该错误信号后,可以有三种不同的处理方式。
当Linux应用程序在执行时发生严重错误,一般会导致程序崩溃。其中,Linux专门提供了一类crash信号,在程序接收到此类信号时,缺省操作是将崩溃的现场信息记录到核心文件,然后终止进程。
常见崩溃信号列表:
信号 | 描述 |
---|---|
SIGSEGV | 内存引用无效。 |
SIGBUS | 访问内存对象的未定义部分。 |
SIGFPE | 算术运算错误,除以零。 |
SIGILL | 非法指令,如执行垃圾或特权指令。 |
SIGSYS | 糟糕的系统调用。 |
SIGXCPU | 超过CPU时间限制。 |
SIGXFSZ | 文件大小限制。 |
一般的出现崩溃信号,Android系统默认缺省操作是直接退出我们的程序。但是系统允许我们给某一个进程的某一个特定信号注册一个相应的处理函数(signal),即对该信号的默认处理动作进行修改。因此NDK Crash的监控可以采用这种信号机制,捕获崩溃信号执行我们自己的信号处理函数从而捕获NDK Crash。
此处了解即可,普通应用无权限读取墓碑文件,墓碑文件位于路径/data/tombstones/下。解析墓碑文件与后面的breakPad都可使用 addr2line 工具。
Android本机程序本质上就是一个Linux程序,当它在执行时发生严重错误,也会导致程序崩溃,然后产生一个记录崩溃的现场信息的文件,而这个文件在Android系统中就是 tombstones 墓碑文件。
Google breakpad是一个跨平台的崩溃转储和分析框架和工具集合,其开源地址是:https://github.com/google/breakpad。breakpad在Linux中的实现就是借助了Linux信号捕获机制实现的。因为其实现为C++,因此在Android中使用,必须借助NDK工具。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。