0


Android 性能优化系列:崩溃原因及捕获

文章目录

崩溃的基本原因

抛出异常导致崩溃分析

在日常开发中崩溃是我们遇到的很常见的情况,可能是 NullPointerException、IllegalArgumentException 等等,当应用程序抛出这些我们未捕获的异常时,紧跟着的是应用的崩溃,进程被杀死并退出。

或许你到现在都一直认为是因为抛出了异常,所以才会导致的进程被杀死并退出,认为抛出未捕获的异常就是导致进程退出的根本原因。但事实真的如此吗?

主线程也是一个线程,所以我们从 Thread 源码找找是否有什么踪迹可以解释:

Thread.java

/**
 * Dispatch an uncaught exception to the handler. This method is
 * intended to be called only by the JVM.
 */privatevoiddispatchUncaughtException(Throwable e){getUncaughtExceptionHandler().uncaughtException(this, e);}publicUncaughtExceptionHandlergetUncaughtExceptionHandler(){// 如果没有设置 uncaughtExceptionHandler,由 ThreadGroup group 线程组处理return uncaughtExceptionHandler !=null?
        uncaughtExceptionHandler : group;}publicstaticUncaughtExceptionHandlergetDefaultUncaughtExceptionHandler(){return defaultUncaughtExceptionHandler;}ThreadGroup.java

publicvoiduncaughtException(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);}elseif(!(e instanceofThreadDeath)){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.java

publicstaticvoidmain(String[] argv){...commonInit();...}protectedstaticfinalvoidcommonInit(){...// 在所在线程(即主线程)调用了 Thread.setDefaultUncaughtExceptionHandler// 提供 KillApplicationHandler 在应用抛出异常时退出 app 进程LoggingHandler loggingHandler =newLoggingHandler();Thread.setUncaughtExceptionPreHandler(loggingHandler);Thread.setDefaultUncaughtExceptionHandler(newKillApplicationHandler(loggingHandler));...}privatestaticclassKillApplicationHandlerimplementsThread.UncaughtExceptionHandler{@OverridepublicvoiduncaughtException(Thread t,Throwable e){try{...ActivityManager.getService().handleApplicationCrash(
                    mApplicationObject,newApplicationErrorReport.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:

privatestaticclassKillApplicationHandlerimplementsThread.UncaughtExceptionHandler{@OverridepublicvoiduncaughtException(Thread t,Throwable e){try{...// AMS 处理进程退出前的信息处理ActivityManager.getService().handleApplicationCrash(
                    mApplicationObject,newApplicationErrorReport.ParcelableCrashInfo(e));}catch(Throwable t2){...}finally{Process.killProcess(Process.myPid());System.exit(10);}}}

那 AMS 在进程退出前具体做了哪些事情,接着看源码:

ActivityManagerService.java

publicvoidhandleApplicationCrash(IBinder app,ApplicationErrorReport.ParcelableCrashInfo crashInfo){// 拿到进程信息ProcessRecord r =findAppProcess(app,"Crash");finalString processName = app ==null?"system_server":(r ==null?"unknown": r.processName);handleApplicationCrashInner("crash", r, processName, crashInfo);}// eventType 根据不同类型记录 logvoidhandleApplicationCrashInner(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.java

publicvoidstartObservingNativeCrashes(){// NativeCrashListener 继承自 ThreadfinalNativeCrashListener ncl =newNativeCrashListener(this);
    ncl.start();}

而 AMS 的 startObservingNativeCrashes() 是在 SystemServer 创建启动的时候调用:

SystemServer.java

privatevoidstartOtherServices(){...
    mActivityManagerService.startObservingNativeCrashes();...}

startOtherService() 是在一些核心服务启动后才会紧接着调用的方法用于开启其他服务,native crash 的监控就是在此时启动。

接下来看下 native crash 是怎么监听的。

NativeCrashListener.java

// 通信地址,C 层是将数据推到这里,java 层也是从这个地址获取到数据staticfinalString DEBUGGERD_SOCKET_PATH ="/data/system/ndebugsocket";@Overridepublicvoidrun(){...// 这里走的是信号机制通信,当前接收建立管道绑定FileDescriptor serverFd =Os.socket(AF_UNIX, SOCK_STREAM,0);finalUnixSocketAddress 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/* peerAddress */);...if(peerFd !=null){// 处理 native crash 数据consumeNativeCrashData(peerFd);}...}}...}voidconsumeNativeCrashData(FileDescriptor fd){// 根据 fd 拿到数据写到 ByteArrayOutputStream...finalString reportString =newString(os.toByteArray(),"UTF-8");// 启动一个子线程将数据结果给到 AMS 调用 addErrorToDropBox()(newNativeCrashReport(pr, signal, reportString)).start();}classNativeCrashReportextendsThread{ProcessRecord mApp;int mSignal;String mCrashReport;NativeCrashReport(ProcessRecord app,int signal,String report){super("NativeCrashReport");
        mApp = app;
        mSignal = signal;
        mCrashReport = report;}@Overridepublicvoidrun(){try{// 封装数据,将数据交给 AMSCrashInfo ci =newCrashInfo();
            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.java

finalvoidappNotResponding(ProcessRecord app,ActivityRecord activity,ActivityRecord parent,boolean aboveSystem,finalString 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.java

publicvoidaddErrorToDropBox(String eventType,ProcessRecord process,String processName,ActivityRecord activity,ActivityRecord parent,String subject,finalString report,finalFile dataFile,finalApplicationErrorReport.CrashInfo crashInfo){...finalStringBuilder sb =newStringBuilder(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 linesInputStreamReader input =null;try{java.lang.Process logcat =newProcessBuilder("/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 =newInputStreamReader(logcat.getInputStream());int num;char[] buf =newchar[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.java

publicvoidaddText(String tag,String data){try{// mService 就是 DropBoxManagerService
        mService.add(newEntry(tag,0, data));}catch(RemoteException e){if(e instanceofTransactionTooLargeException&& 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.java

publicDropBoxManagerService(finalContext context){// DropBox 信息的存储日志目录是 /data/system/dropboxthis(context,newFile("/data/system/dropbox"),FgThread.get().getLooper());}publicDropBoxManagerService(finalContext context,File path,Looper looper){super(context);
    mDropBoxDir = path;...}// 最终其实就是 IO 写入publicvoidadd(DropBoxManager.Entry entry){File temp =null;InputStream input =null;OutputStream output =null;finalString tag = entry.getTag();try{int flags = entry.getFlags();if((flags &DropBoxManager.IS_EMPTY)!=0)thrownewIllegalArgumentException();init();if(!isTagEnabled(tag))return;long max =trimToFit();long lastTrim =System.currentTimeMillis();byte[] buffer =newbyte[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 =newFile(mDropBoxDir,"drop"+Thread.currentThread().getId()+".tmp");int bufferSize = mBlockSize;if(bufferSize >4096) bufferSize =4096;if(bufferSize <512) bufferSize =512;FileOutputStream foutput =newFileOutputStream(temp);
        output =newBufferedOutputStream(foutput, bufferSize);if(read == buffer.length &&((flags &DropBoxManager.IS_GZIPPED)==0)){
            output =newGZIPOutputStream(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 tombstonebreak;}}while(read >0);long time =createEntry(temp, tag, flags);
        temp =null;finalIntent dropboxIntent =newIntent(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
版权归原作者 VincentWei95 所有, 如有侵权,请联系我们删除。

“Android 性能优化系列:崩溃原因及捕获”的评论:

还没有评论