Android 性能优化系列:崩溃原因及捕获
文章目录
崩溃的基本原因
抛出异常导致崩溃分析
在日常开发中崩溃是我们遇到的很常见的情况,可能是 NullPointerException、IllegalArgumentException 等等,当应用程序抛出这些我们未捕获的异常时,紧跟着的是应用的崩溃,进程被杀死并退出。
或许你到现在都一直认为是因为抛出了异常,所以才会导致的进程被杀死并退出,认为抛出未捕获的异常就是导致进程退出的根本原因。但事实真的如此吗?
主线程也是一个线程,所以我们从 Thread 源码找找是否有什么踪迹可以解释:
Thread.javaprivate void dispatchUncaughtException(Throwable e) { getUncaughtExceptionHandler().uncaughtException(this, e);}public UncaughtExceptionHandler getUncaughtExceptionHandler() {// 如果没有设置 uncaughtExceptionHandler,由 ThreadGroup group 线程组处理 return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group;}public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){ return defaultUncaughtExceptionHandler;}ThreadGroup.javapublic void uncaughtException(Thread t, Throwable e) { if (parent != null) { parent.uncaughtException(t, e); } else { Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); if (ueh != null) { // 如果有设置了 UncaughtExceptionHandler 处理异常捕获 ueh.uncaughtException(t, e); } else if (!(e instanceof ThreadDeath)) { System.err.print("Exception in thread \"" + t.getName() + "\" "); e.printStackTrace(System.err); } }}
上面的源码是 Thread 处理未捕获的异常时会执行的代码,其中 dispatchUncaughtException() 是当异常未被捕获时,它会由 JVM 发起调用。
当程序代码运行错误抛出异常时,如果没有设置 Thread.UncaughtExceptionHandler,从 Thread 中并没有看到有关进程退出的代码,那为什么我们的程序抛出异常会导致进程退出呢?
在 Android 创建 app 进程是由 zygote fork 进程,而在 zygote 进程创建之前则是由 init 进程启动的,每个应用程序的入口都是 main() 函数:
RuntimeInit.javapublic static void main(String[] argv) {...commonInit();...}protected static final void commonInit() {...// 在所在线程(即主线程)调用了 Thread.setDefaultUncaughtExceptionHandler// 提供 KillApplicationHandler 在应用抛出异常时退出 app 进程 LoggingHandler loggingHandler = new LoggingHandler(); Thread.setUncaughtExceptionPreHandler(loggingHandler); Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler)); ...}private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {@Overridepublic void uncaughtException(Thread t, Throwable e) {try {...ActivityManager.getService().handleApplicationCrash(mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));} catch (Throwable t2) {...} finally {// 捕获到应用程序抛出的异常,最后主动杀死退出进程Process.killProcess(Process.myPid());System.exit(10);}}}
可以发现应用程序抛出异常进程会退出,实际上是 RuntimeInit 在主线程设置了 KillApplicationHandler,捕获到异常时主动将应用进程杀死退出。
所以在这里我们可以得出结论:抛出未捕获的异常并不是导致进程杀死退出的根本原因而逝导火索,Android 默认提供了异常未捕获时主动将应用进程杀死退出的逻辑。
简单总结下 java crash 未捕获异常的处理流程:
-
Thread 中遇到未捕获异常时会由 JVM 调用 dispatchUncaughtException()
-
dispatchUncaughtException() 内部是运用 Thread.UncaughtExceptionHandler 进行处理
-
Android 默认提供一个 KillApplicationHandler 继承 Thread.UncaughtExceptionHandler,它提供进程退出功能
即我们程序中的代码抛出未捕获的异常时,异常会一路往上抛直到由 JVM 处理,JVM 就会调用 Thread 的 dispatchUncaughtException(),接着就是交给了 Android 在创建应用时设置的 KillApplicationHandler 将进程退出。
AMS 如何承接应用的异常信息上报
在上面分析 RuntimeInit 设置了 KillApplicationHandler 时,在杀死进程前将应用崩溃的信息交给了 AMS:
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {@Overridepublic void uncaughtException(Thread t, Throwable e) {try {...// AMS 处理进程退出前的信息处理ActivityManager.getService().handleApplicationCrash(mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));} catch (Throwable t2) {...} finally {Process.killProcess(Process.myPid());System.exit(10);}}}
那 AMS 在进程退出前具体做了哪些事情,接着看源码:
ActivityManagerService.javapublic void handleApplicationCrash(IBinder app, ApplicationErrorReport.ParcelableCrashInfo crashInfo) { // 拿到进程信息 ProcessRecord r = findAppProcess(app, "Crash"); final String processName = app == null ? "system_server" : (r == null ? "unknown" : r.processName); handleApplicationCrashInner("crash", r, processName, crashInfo);}// eventType 根据不同类型记录 logvoid handleApplicationCrashInner(String eventType, ProcessRecord r, String processName, ApplicationErrorReport.CrashInfo crashInfo) {...// java crash、native crash、anr 都会走这里去记录,通过 eventType 区分记录 logaddErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);...}
可以发现 AMS 是拿到了应用进程信息后,最终调用了 addErrorToDropBox() 将错误信息提供给 DropBox。
Android DropBox 是 Android 8 引入的用来持续化存储系统数据的机制。主要用于记录 Android 运行过程中,内核、系统进程、用户进程等出现严重问题时的 log,可以认为这是一个可持续存储的系统级别的 logcat。存储的 log 信息存放在 /data/system/dropbox/(该目录没有 root 无法访问),crash 和 anr 的日志都会存储在这里。
其中 eventType 参数表示的是记录的 log 信息类型,它有三种类型:
-
未捕捉的 java 异常:crash
-
ANR 异常:anr
-
native 异常:native crash
对于 native crash 系统如何做处理
上面讲到了 app 是如何捕捉 java crash,那 native crash 又是如何捕捉的?
ActivityManagerService.javapublic void startObservingNativeCrashes() {// NativeCrashListener 继承自 Thread final NativeCrashListener ncl = new NativeCrashListener(this); ncl.start();}
而 AMS 的 startObservingNativeCrashes() 是在 SystemServer 创建启动的时候调用:
SystemServer.javaprivate void startOtherServices() {...mActivityManagerService.startObservingNativeCrashes();...}
startOtherService() 是在一些核心服务启动后才会紧接着调用的方法用于开启其他服务,native crash 的监控就是在此时启动。
接下来看下 native crash 是怎么监听的。
NativeCrashListener.java// 通信地址,C 层是将数据推到这里,java 层也是从这个地址获取到数据static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket";@Overridepublic void run() {...// 这里走的是信号机制通信,当前接收建立管道绑定FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0);final UnixSocketAddress sockAddr = UnixSocketAddress.createFileSystem(DEBUGGERD_SOCKET_PATH);Os.bind(serverFd, sockAddr);Os.listen(serverFd, 1);Os.chmod(DEBUGGERD_SOCKET_PATH, 0777);while (true) {FileDescriptor peerFd = null;try {...// 挂载等待peerFd = Os.accept(serverFd, null );...if (peerFd != null) {// 处理 native crash 数据consumeNativeCrashData(peerFd);}...}}...}void consumeNativeCrashData(FileDescriptor fd) {// 根据 fd 拿到数据写到 ByteArrayOutputStream...final String reportString = new String(os.toByteArray(), "UTF-8");// 启动一个子线程将数据结果给到 AMS 调用 addErrorToDropBox()(new NativeCrashReport(pr, signal, reportString)).start();}class NativeCrashReport extends Thread {ProcessRecord mApp;int mSignal;String mCrashReport;NativeCrashReport(ProcessRecord app, int signal, String report) {super("NativeCrashReport");mApp = app;mSignal = signal;mCrashReport = report;}@Overridepublic void run() {try {// 封装数据,将数据交给 AMS CrashInfo ci = new CrashInfo(); ci.exceptionClassName = "Native crash"; ci.exceptionMessage = Os.strsignal(mSignal); ci.throwFileName = "unknown"; ci.throwClassName = "unknown"; ci.throwMethodName = "unknown"; ci.stackTrace = mCrashReport; if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()"); // eventType = native_crash,还是调用的 AMS.addErrorToDropBox() mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci); if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned");} catch (Exception e) {Slog.e(TAG, "Unable to report native crash", e);}...}}
通过源码可以知道,native crash 监听是通过信号量的方式监听一个 fd 文件描述符并挂载等待,当 fd 返回数据时说明出现 native crash,此时就可以通过 fd 拿到 native crash 的字节数据进行封装,最终还是将数据结果给到 AMS 调用 addErrorToDropBox(),只是 eventType 传的是 native_crash。
简单总结下 native crash 异常的处理流程:
-
通信机制建立,监听一个 fd 文件描述符
-
接收数据,挂载等待
-
fd 文件描述符有数据返回,处理数据
-
封装数据,将结果给 AMS 调用 addErrorToDropBox() 写入存储信息
系统如何处理 ANR 异常数据
因为这里我们只说 ANR 异常最终是如何处理的,所以关于 ANR 异常发生的原因和具体流程可以查看 ANR 触发原理和分析,这里不再赘述,我们直接定位到 ANR 已经发生的位置:
AppErrors.javafinal void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) { // ANR 数据处理封装...// 这里就是我们常用的 anr 日志存放在 /data/anr 的文件向外输出了一份 File tracesFile = ActivityManagerService.dumpStackTraces( true, firstPids, (isSilentANR) ? null : processCpuTracker, (isSilentANR) ? null : lastPids, nativePids);...// 调用 AMS 的 addErrorToDropBox() mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation, cpuInfo, tracesFile, null);...}
ANR 异常的信息是调用的 AppErrors 的 appNotResponding(),也是将数据处理封装后给到 AMS 调用 addErrorToDropBox(),eventType 传入的是 anr。
简单总结下 anr 异常的处理流程:
-
记录 anr 对应数据到 SLOG(framework 日志体系中)
-
记录 anr 数据到 LOG(主日志体系中)
-
dump 具体数据到指定文件中
-
调用 AMS 的 addErrorToDropBox() 写入存储信息
addErrorToDropBox()
经过上面的分析我们知道,无论是 java crash、native crash 还是 anr,最终都会交由 AMS 的 addErrorToDropBox() 处理。那它又是怎么处理的呢?
ActivityManagerService.javapublic void addErrorToDropBox(String eventType, ProcessRecord process, String processName, ActivityRecord activity, ActivityRecord parent, String subject, final String report, final File dataFile, final ApplicationErrorReport.CrashInfo crashInfo) {... final StringBuilder sb = new StringBuilder(1024); // 添加头信息 appendDropBoxProcessHeaders(process, processName, sb); // 添加一些头部 log 信息 if (process != null) { sb.append("Foreground: ") .append(process.isInterestingToUserLocked() ? "Yes" : "No") .append("\n"); } if (activity != null) { sb.append("Activity: ").append(activity.shortComponentName).append("\n"); } if (parent != null && parent.app != null && parent.app.pid != process.pid) { sb.append("Parent-Process: ").append(parent.app.processName).append("\n"); } if (parent != null && parent != activity) { sb.append("Parent-Activity: ").append(parent.shortComponentName).append("\n"); } if (subject != null) { sb.append("Subject: ").append(subject).append("\n"); } sb.append("Build: ").append(Build.FINGERPRINT).append("\n"); if (Debug.isDebuggerConnected()) { sb.append("Debugger: Connected\n"); } sb.append("\n");...// 读取对应异常数据拼装成字符数据 if (lines > 0) { sb.append("\n"); // Merge several logcat streams, and take the last N lines InputStreamReader input = null; try { java.lang.Process logcat = new ProcessBuilder( "/system/bin/timeout", "-k", "15s", "10s", "/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system", "-b", "main", "-b", "crash", "-t", String.valueOf(lines)).redirectErrorStream(true).start(); try { logcat.getOutputStream().close(); } catch (IOException e) {} try { logcat.getErrorStream().close(); } catch (IOException e) {} input = new InputStreamReader(logcat.getInputStream()); int num; char[] buf = new char[8192]; while ((num = input.read(buf)) > 0) sb.append(buf, 0, num); } catch (IOException e) { Slog.e(TAG, "Error running logcat", e); } finally { if (input != null) try { input.close(); } catch (IOException e) {} } }// 交给 DropBoxManager 录入 dbox.addText(dropboxTag, sb.toString()); ...}DropBoxManager.javapublic void addText(String tag, String data) { try { // mService 就是 DropBoxManagerService mService.add(new Entry(tag, 0, data)); } catch (RemoteException e) { if (e instanceof TransactionTooLargeException && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { Log.e(TAG, "App sent too much data, so it was ignored", e); return; } throw e.rethrowFromSystemServer(); }}
当 AMS 调用 addErrorToDropBox() 时,它会准备添加到 DropBoxManager 所需要的头信息、异常信息的字符数据,最终交由 DropBoxManager 录入信息。
DropBoxManager 在 Crash 方案中扮演的角色
DropBox 是 Android 在 API 8 引入的用来持续化存储系统数据的机制,主要用于记录 Android 运行过程中,内核、系统进程、用户进程等出现严重问题时的 log,可以认为 DropBox 就是一个可持续存储的系统级别的 logcat。
DropBoxManagerService.javapublic DropBoxManagerService(final Context context) {// DropBox 信息的存储日志目录是 /data/system/dropbox this(context, new File("/data/system/dropbox"), FgThread.get().getLooper());}public DropBoxManagerService(final Context context, File path, Looper looper) { super(context); mDropBoxDir = path; ...}// 最终其实就是 IO 写入public void add(DropBoxManager.Entry entry) { File temp = null; InputStream input = null; OutputStream output = null; final String tag = entry.getTag(); try { int flags = entry.getFlags(); if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException(); init(); if (!isTagEnabled(tag)) return; long max = trimToFit(); long lastTrim = System.currentTimeMillis(); byte[] buffer = new byte[mBlockSize]; input = entry.getInputStream(); // First, accumulate up to one block worth of data in memory before // deciding whether to compress the data or not. int read = 0; while (read < buffer.length) { int n = input.read(buffer, read, buffer.length - read); if (n <= 0) break; read += n; } // If we have at least one block, compress it -- otherwise, just write // the data in uncompressed form.// mDropBoxDir 就是 /data/system/dropbox 目录 temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp"); int bufferSize = mBlockSize; if (bufferSize > 4096) bufferSize = 4096; if (bufferSize < 512) bufferSize = 512; FileOutputStream foutput = new FileOutputStream(temp); output = new BufferedOutputStream(foutput, bufferSize); if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) { output = new GZIPOutputStream(output); flags = flags | DropBoxManager.IS_GZIPPED; } do { output.write(buffer, 0, read); long now = System.currentTimeMillis(); if (now - lastTrim > 30 * 1000) { max = trimToFit(); // In case data dribbles in slowly lastTrim = now; } read = input.read(buffer); if (read <= 0) { FileUtils.sync(foutput); output.close(); // Get a final size measurement output = null; } else { output.flush(); // So the size measurement is pseudo-reasonable } long len = temp.length(); if (len > max) { Slog.w(TAG, "Dropping: " + tag + " (" + temp.length() + " > " + max + " bytes)"); temp.delete(); temp = null; // Pass temp = null to createEntry() to leave a tombstone break; } } while (read > 0); long time = createEntry(temp, tag, flags); temp = null; final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag); dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time); if (!mBooted) { dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); } // Call sendBroadcast after returning from this call to avoid deadlock. In particular // the caller may be holding the WindowManagerService lock but sendBroadcast requires a // lock in ActivityManagerService. ActivityManagerService has been caught holding that // very lock while waiting for the WindowManagerService lock. mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent)); } catch (IOException e) { Slog.e(TAG, "Can't write: " + tag, e); } finally { IoUtils.closeQuietly(output); IoUtils.closeQuietly(input); entry.close(); if (temp != null) temp.delete(); }}
当发生异常信息时,最终的异常信息是交给 DropBoxManager(具体说是 DropBoxManagerService)通过 IO 将信息写入到指定目录文件。
总结
上面主要分析了当发生 java crash、native crash 和 anr 时,Android 为我们做了哪些事情,在这里在简单总结一下:
-
java crash 由 JVM 触发处理,最终走到 /data/system/dropbox 目录用文件保存
-
native crash 由管道通信建立 socket 接收通知,最终走到 /data/system/dropbox 目录用文件保存
-
anr 由多种情况(事件、前后台服务)触发器处理,最终走到 /data/system/dropbox 目录用文件保存
所有的 crash 处理在 Android 系统内部都会将对应的数据收集到 /data/system/dropbox 目录下。
同时我们也梳理了 Android 的 crash 处理机制:
- Java 没有捕获异常时会由 JVM 调用 dispatchUncaughtException 调用一个 UncaughtExceptionHandler 处理,默认 RuntimeInit 提供了一个 KillApplicationHandler 会直接退出进程。
如果我们要自己处理异常,可以自定义一个 UncaughtExceptionHandler 拦截处理。
来源地址:https://blog.csdn.net/qq_31339141/article/details/131420248
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341