Android-Crash处理和拦截

日常开发过程中,我们肯定有遇到app崩溃的情况,很多少时候,会有系统弹出提示:
系统提示框
,那么系统是怎么捕获到App崩溃并弹出提示框的呢?更进一步,如果我想在子线程crash时,app不被杀死,要怎么实现呢?带着这些疑问,我去探究了一下Android的crash处理原理。

背景知识

做Android开发的小伙伴都知道,Android是基于Java线程+消息队列来实现的。而Java的Crash处理机制也是和线程息息相关的。在Java的Thread类源码中可以看到如下接口:

public interface UncaughtExceptionHandler {
        /**
         * 当虚拟机收到某个线程产生的未捕获的异常时,该方法会被调用,在此方法中扔出的异常将不会被虚拟机忽略
         */
        void uncaughtException(Thread t, Throwable e);
    }

UncaughtExceptionHandler接口定义了应用层处理异常的行为,那么应用层应该怎么使用呢?直接看Thread源码

public class Thread implements Runnable {
    // null unless explicitly set
    private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
    private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
    private static volatile UncaughtExceptionHandler uncaughtExceptionPreHandler;
 
    public static void setUncaughtExceptionPreHandler(UncaughtExceptionHandler eh) {
         uncaughtExceptionPreHandler = eh;
     }

    /** @hide */
    public static UncaughtExceptionHandler getUncaughtExceptionPreHandler() {
        return uncaughtExceptionPreHandler;
    }
    
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

    public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        checkAccess();
        uncaughtExceptionHandler = eh;
    }

    public final void dispatchUncaughtException(Throwable e) {
        // BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
        Thread.UncaughtExceptionHandler initialUeh =
                Thread.getUncaughtExceptionPreHandler();
        if (initialUeh != null) {
            try {
                initialUeh.uncaughtException(this, e);
            } catch (RuntimeException | Error ignored) {
                // Throwables thrown by the initial handler are ignored
            }
        }
        // END Android-added: uncaughtExceptionPreHandler for use by platform.
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
}

我精简了Thread中和Crash处理无关的代码,上述源码分为成员的设置和获取、异常处理,先来看成员相关:

  1. uncaughtExceptionHandler:对象成员,每个Thread对象对应一个
  2. defaultUncaughtExceptionHandler:类成员,Thread类仅一个,对于android来讲,就是一个应用只有一个,即应用级别
  3. uncaughtExceptionPreHandler:类成员,Thread类仅一个,对于android来讲,就是一个应用只有一个,即应用级别

接下来看下异常如何被分发和处理:

  1. 首先获取uncaughtExceptionPreHandler成员,将异常交给ta处理,和名字一样,果然是预处理。
  2. 获取uncaughtExceptionHandler,非空时,将异常交给uncaughtExceptionHandler处理。
  3. uncaughtExceptionHandler为空时,获取group,将异常交给group处理?那么这个group是什么呢
#Thread.ava
private ThreadGroup group;

#ThreadGroup.java
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
   
   @Override 
   public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
           //如果父处理器非Null,交由父处理器处理
            parent.uncaughtException(t, e);
        } else {
            //获取当前线程的默认处理器defaultUncaughtExceptionHandler,非Null则交由其处理。
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }}

通过上述源码,我们大概了解了Java线程在应用层的分发逻辑了,整理下可以得出如下结论:

  • 异常处理是具有优先级的,首先是应用级别预处理器uncaughtExceptionPreHandler,其次是当前线程处理器uncaughtExceptionHandler,之后时不断向上递交,最终交给应用级别默认处理器defaultUncaughtExceptionHandler
  • 异常处理具有线程隔离的能力,能将crash产生的线程捕获,不向其他线程或者进程扩散,前提是主业务能正常运行。

了解了Java中Thread线程异常的捕获机制,不禁让我疑惑,之前在开发时,也不曾看到有设置Thread异常处理对象啊?那么Android中的异常处理在哪里设置的呢??

Android中的异常处理

仔细想想,如果我们给Android中应用设置一个异常处理器,会在哪里设置呢?大概率会在进程启动的时候就设置,因为这样就不会遗漏捕获。其实Android的确就是这样干的。

#ZygoteInit.java
    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv,
            ClassLoader classLoader) {
        RuntimeInit.commonInit();
        ZygoteInit.nativeZygoteInit();
        return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
    }
    protected static final void commonInit() {
        /*
         * 设置默认异常处理器给虚拟机所有线程,允许替换默认处理器,但不允许更改预处理起
         */
        LoggingHandler loggingHandler = new LoggingHandler();
        RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
    }

上述是ZygoteInitl类的两个方法,我们知道,Android中每个应用是个进程,而且该进程由Zygote进程孵化而来,在Zygote创建新进程之后,便会调用上述zygoteInit对象zygoteInit方法来进程新进程的初始化,而我们关心的异常处理器则在其调用的commonInit方法中。看一看到其设置了预处理器LoggingHandler,又设置了默认处理器KillApplicationHandler。这里我们直接看KillApplicationHandler就行了。

# KillApplicationHandler.java
public void uncaughtException(Thread t, Throwable e) {
            try {
                //打印异常,内部主要是使用LoggingHandler
                ensureLogging(t, e);

                // 如果正在处理异常,则直接返回,这里是为了不重复上报异常
                if (mCrashing) return;
                mCrashing = true;

                // 停止系统数据采集
                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }

                // 通知AMS,当前进程出现了Crash
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
            } finally {
                // 杀掉当前进程并退出
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }

我们看到,系统设置的异常处理器主要就是做了三件事

  • 打印日志
  • 通知AMS,当前进程出现了crash,赶紧处理
  • 杀掉当前进程

那么AMS会怎么处理呢?简单跟一下代码:

#ActivityManagerService.java
 void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
            ApplicationErrorReport.CrashInfo crashInfo) {
        //省略打印日志和收集日志到drop box
        mAppErrors.crashApplication(r, crashInfo);
    }
# AppErrors.java    
 void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
        ....
        try {
            crashApplicationInner(r, crashInfo, callingPid, callingUid);
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

    void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,
            int callingPid, int callingUid) {
            AppErrorDialog.Data data = new AppErrorDialog.Data();
            data.result = result;
            data.proc = r;
            // 避免重复处理
            if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, data)) {
                return;
            }
            final Message msg = Message.obtain();
            msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;
            taskId = data.taskId;
            msg.obj = data;
            //这里是想UIHandler发送了SHOW_ERROR_UI_MSG消息,该消息主要是展示UI的
            mService.mUiHandler.sendMessage(msg);
        }
        // 后面是清理AMS进程内当前Crash进程的一些数据,有兴趣可以自己跟一下
    }
#UIHandler.java
     public void handleMessage(Message msg) {
            switch (msg.what) {
            case SHOW_ERROR_UI_MSG: {
                mAppErrors.handleShowAppErrorUi(msg);
                ensureBootCompleted();
            }
            ......
#AppErrors.java
void handleShowAppErrorUi(Message msg) {
        AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj;
        boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;

        AppErrorDialog dialogToShow = null;
        final String packageName;
        final int userId;
        synchronized (mService) {
            final ProcessRecord proc = data.proc;
            final AppErrorResult res = data.result;
            //...省略了一些拦截代码
           // 展示Dialog提示App Crash,这里的Crash是包含ANR导致的Crash的
        if (dialogToShow != null) {
            dialogToShow.show();
        }
    }             

上述思路大致可以看到,系统弹出了一个dialog去展示,就是文章开头的效果,当然,不同系统可能UI效果不一样的。到此,Android中的异常处理机制大致已经介绍一遍。那么加入我现在想实现一个功能,实现crash的拦截和包装上报呢,这个要怎么做呢?可以先想想,基础思路的 答案在文末。

public interface CrashHandleInterceptor {

  /**
   * @return false 为不捕获异常.
   */
  boolean handleException(Thread thread, Throwable throwable);
}
public interface CrashFacadeInterceptor {

  String facadeException(Throwable throwable);

}
public class CrashHandler implements Thread.UncaughtExceptionHandler {

  private static CrashHandler crashHandler;
  private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
  private boolean inited;
  private CrashHandleInterceptor crashInterceptor;
  private CrashFacadeInterceptor crashFacadeInterceptor;

  static synchronized CrashHandler getInstance() {
    CrashHandler myCrashHandler;
    synchronized (CrashHandler.class) {
      if (crashHandler == null) {
        crashHandler = new CrashHandler();
      }
      myCrashHandler = crashHandler;
    }
    return myCrashHandler;
  }

  void register(CrashHandleInterceptor crashInterceptor) {
    if (!inited) {
      inited = true;
      this.crashInterceptor = crashInterceptor;
      try {
        uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
      } catch (Exception e) {
        Log.d("CrashSDK", "Exception e = " + e.getMessage());
        inited = false;
        e.printStackTrace();
      }
    }
  }

  @Override public void uncaughtException(Thread thread, Throwable throwable) {
    //拦截
    if (crashInterceptor != null && crashInterceptor.handleException(thread, throwable)) {
      Log.d(CrashSDK.TAG, "crashInterceptor.handleException");
      return;
    }
    Throwable facadeThrowable = throwable;
    if (crashFacadeInterceptor != null) {
      String errorMsg = crashFacadeInterceptor.facadeException(throwable);
      facadeThrowable = CrashUtil.facadeThrowable(throwable, errorMsg);
    }
    uncaughtExceptionHandler.uncaughtException(thread, facadeThrowable);
  }

  void setCrashFacadeInterceptor(CrashFacadeInterceptor crashFacadeInterceptor) {
    this.crashFacadeInterceptor = crashFacadeInterceptor;
  }

  boolean isInited() {
    return inited;
  }
}

提几个问题

  1. 默认情况下,如果子线程出现了未捕获的Exception,主线程会crash吗
  2. 如何让指定子线程出现了未捕获异常时,主线程不受影响
  3. 如果覆盖了KillApplicationHandler,当异常产生时,不做任何处理,此时会出现什么情况?

对应答案

  1. 主线程会crash,因为子线程没有设置未处理异常的处理器,所以会委托给Threadgroup的uncatchExceptionHandle去处理,该方法会调用KillApplicationHandler处理,默认会crash。
  2. 可以调用子线程的setUncaughtExceptionHandler方法给子线程设置处理器,拦截掉异常
  3. 程序会没有反应,如果用户进行交互,会产生ANR
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值