你知道App为什么会Crash吗?,vue数据双向绑定

本文深入探讨了Android应用中App崩溃的原因,重点讲解了KillApplicationHandler和LoggingHandler在处理异常时的角色。通过分析Looper.loop()的工作原理,解释了主线程为何不会因死循环而卡死,并提出通过自定义CrashHandler来捕获和处理主线程及子线程的Uncaught异常,从而避免应用崩溃。
摘要由CSDN通过智能技术生成

}

}

}

private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {

private final LoggingHandler mLoggingHandler;

public KillApplicationHandler(LoggingHandler loggingHandler) {

this.mLoggingHandler = Objects.requireNonNull(loggingHandler);

}

@Override

public void uncaughtException(Thread t, Throwable e) {

try {

ensureLogging(t, e);

if (mCrashing) return;

mCrashing = true;

if (ActivityThread.currentActivityThread() != null) {

ActivityThread.currentActivityThread().stopProfiling();

}

ActivityManager.getService().handleApplicationCrash(

mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));

} catch (Throwable t2) {

if (t2 instanceof DeadObjectException) {

} else {

try {

Clog_e(TAG, “Error reporting crash”, t2);

} catch (Throwable t3) {

}

}

} finally {

//杀死进程

Process.killProcess(Process.myPid());

System.exit(10);

}

}

private void ensureLogging(Thread t, Throwable e) {

if (!mLoggingHandler.mTriggered) {

try {

mLoggingHandler.uncaughtException(t, e);

} catch (Throwable loggingThrowable) {

}

}

}

}

protected static final void commonInit() {

//设置异常处理回调

LoggingHandler loggingHandler = new LoggingHandler();

Thread.setUncaughtExceptionPreHandler(loggingHandler);

Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));

}

RuntimeInit有两个的内部类,LoggingHandler和KillApplicationHandler。

很显然,LoggingHandler的作用是打印异常日志,而KillApplicationHandler就是App发生Crash的真正原因,其内部调用了Process.killProcess(Process.myPid())来杀死发生Uncaught异常的进程。

我们还发现,这两个内部类都实现了Thread.UncaughtExceptionHandler接口。

分别通过Thread.setUncaughtExceptionPreHandler和Thread.setDefaultUncaughtExceptionHandler方法进行注册。

  • Thread.setUncaughtExceptionPreHandler,覆盖所有线程,会在回调DefaultUncaughtExceptionHandler之前调用,只能在Android Framework内部调用该方法

  • Thread.setDefaultUncaughtExceptionHandler,如果在任意线程中调用即可覆盖所有线程的异常,可以在应用层调用,每次调用传入的Thread.UncaughtExceptionHandler都会覆盖上一次的,即我们可以手动覆盖系统实现的KillApplicationHandler

  • new Thread().setUncaughtExceptionHandler(),只可以覆盖当前线程的异常,如果某个Thread有定义UncaughtExceptionHandler,则忽略全局DefaultUncaughtExceptionHandler

小结:Uncaught异常发生时会终止线程,此时,系统便会通知UncaughtExceptionHandler,告诉它被终止的线程以及对应的异常, 然后便会调用uncaughtException函数。

如果该handler没有被显式设置,则会调用对应线程组的默认handler。如果我们要捕获该异常,必须实现我们自己的handler。

3我们能让应用不发生Crash吗?


上面说到了我们可以在应用层调用Thread.setDefaultUncaughtExceptionHandler来实现所有线程的Uncaught异常的监听,并且会覆盖系统的默认实现的KillApplicationHandler,这样我们就可以做到让线程发生Uncaught异常的时候只是当前杀死线程,而不会杀死整个进程。

这适用于我们的子线程发生Uncaught异常,如果我们的主线程发生Uncaught异常呢?

主线程都被销毁了,这和Crash似乎就没什么区别的。

那么我们有办法让主线程发生Uncaught异常也不会发生Crash吗?

答案是有的,但在讲如何实现之前我们先来介绍一些知识点。

我们知道Java程序开始于一个Main函数,如果只是顺序执行有限任务很快这个Main函数所在的线程就结束了。

如何来保持Main函数一直存活并不断的处理已知或未知的任务呢?

  • 采用死循环。但是死循环的一次循环需要处理什么任务。如果任务暂时没有,也要程序保持活跃的等待状态怎么办?

  • 如果有两个线程或者多个线程如何来协作以完成一个微型系统任务?

如果熟悉Android Handler机制的话,我们会了解到整个Android系统其实是消息驱动的。

Looper内部是一个死循环,不断地MessageQueue内部取出消息,由消息来通知做什么任务。

比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;

再比如收到msg=H.PAUSE_ACTIVITY,则调用ActivityThread.handlePauseActivity()方法,最终会执行Activity.onPause()等方法。

public static void loop() {

final Looper me = myLooper();

if (me == null) {

throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);

}

final MessageQueue queue = me.mQueue;

for (;😉 {

//从消息队列中取出Message

Message msg = queue.next(); // might block

if (msg == null) {

// No message indicates that the message queue is quitting.

return;

}

//派发

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

发消息到对应的Handler,target就是Handler的实例

msg.target.dispatchMessage(msg);

//释放消息占据的资源

msg.recycleUnchecked();

}

}

那么我们有没有想过一个问题,Looper.loop是在ActiivtyThread被调用的,也就是主线程中,那么主线程中死循环为什么不会导致应用卡死呢?

这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在Looper.loop()的queue.next()中的nativePollOnce()方法,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

当收到不同Message时则采用相应措施:一旦退出消息循环,那么你的程序也就可以退出了。从消息队列中取消息可能会阻塞,取到消息会做出相应的处理。如果某个消息处理时间过长,就可能会影响UI线程的刷新速率,造成卡顿的现象

在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit()方法来终止消息循环,否则这个子线程就会一直处于等待(阻塞)状态,而如果退出Looper以后,这个线程就会立刻(执行所有方法并)终止,因此建议不需要的时候终止Looper

简单总结一下就是当没有消息时,native层的方法做了阻塞处理,所以Looper.loop()死循环不会卡死应用。

我们整个系统都是基于消息机制,再回过头去看一眼上面的主线程异常日志堆栈信息,是不是会经过Looper.loop(),所以其实我们只需要try catch Looper.loop()即可捕获主线程异常。

代码如下所示

public class CrashCatch {

private CrashHandler mCrashHandler;

private static CrashCatch mInstance;

private CrashCatch(){

}

private static CrashCatch getInstance(){

if(mInstance == null){

synchronized (CrashCatch.class){

if(mInstance == null){

mInstance = new CrashCatch();

}

}

}

return mInstance;

}

public static void init(CrashHandler crashHandler){

getInstance().setCrashHandler(crashHandler);

}

private void setCrashHandler(CrashHandler crashHandler){

mCrashHandler = crashHandler;

//主线程异常拦截

new Handler(Looper.getMainLooper()).post(new Runnable() {

@Override

public void run() {

for (;😉 {

try {

Looper.loop();

} catch (Throwable e) {

if (mCrashHandler != null) {

//处理异常

mCrashHandler.handlerException(Looper.getMainLooper().getThread(), e);

}

}

}

}

});

//所有线程异常拦截,由于主线程的异常都被我们catch住了,所以下面的代码拦截到的都是子线程的异常

Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {

@Override

public void uncaughtException(Thread t, Throwable e) {

if(mCrashHandler!=null){

//处理异常

mCrashHandler.handlerException(t,e);

}

}

});

}

public interface CrashHandler{

void handlerException(Thread t,Throwable e);

}

}

原理很简单,就是通过Handler往主线程的MessageQueue中添加一个Runnable,当主线程执行到该Runnable时,会进入我们的while死循环。

如果while内部是空的就会导致代码卡在这里,最终导致ANR,但我们在while死循环中又调用了Looper.loop(),这就导致主线程又开始不断的读取queue中的Message并执行,这样就可以保证以后主线程的所有异常都会从我们手动调用的Looper.loop()处抛出,一旦抛出就会被try{}catch捕获,这样主线程就不会crash了。

如果没有这个while的话那么主线程下次抛出异常时我们就又捕获不到了,这样App就又crash了,所以我们要通过while让每次crash发生后都再次进入消息循环,while的作用仅限于每次主线程抛出异常后迫使主线程再次进入消息循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值