如何避免Android运行时崩溃,保证程序高可用性一_android try catch避免崩溃_mayundoyouknow的博客-程序员秘密

技术标签: Crash  ZynoteInit  Android异常处理  android  AWS  

1.Crash是什么?

Crash,即闪退,多指在移动设备(如IOS、Android设备)中,在打开应用程序时出现突然退出中断的情况(类似于Windows的应用程序崩溃)。

2.什么引起Crash?

异常可以简单分为Checked异常及Unchecked异常,那么这两种异常的区别是什么呢?其中派生于Error或者RuntimeException异常称为Unchecked异常,所有其他的Exception称为Checked异常。

那么为什么要这样区分他们呢?因为Java compiler(编译器)强制要求所有的Exception要么被catch,要么被throw对其进行处理,否则编译会不通过,除非这是一个RuntimeException(e instanceof RuntimeException)。也就是说,通常的Exception一定要被处理,也即我们所说的Checked Exception,而RuntimeException/Error,编译器是不强制要求处理的,所以被称为UnChecked exception。

RuntimeException(运行时异常),它属于Unchecked异常。如果出现RuntimeException,那么一定是程序员代码的问题,编译器并不会检查Unchecked异常。在开发过程中,我们遇到的crash绝大多数都属于RuntimeException(RuntimeException是引起Crash主要原因);

3.异常分类分哪些种?

Java层

Throwable

 从上图可以看出,他们都是从Throwable继承而来的,Throwable下层分为两个子类:Error和Exception。

3.1关于Error和Exception

其中Error类描述了Java运行时系统的内部错误或设备资源耗尽的错误。 这种错误一般没有别的解决办法,它用于报告给开发者程序无法恢复的异常情况。对于所有Error类型以及其子类都不要求程序进行处理,常见如栈溢出StackOverflowError等。

而Exception类则是值程序有可能恢复的异常情况,Exception包含了运行时异常(Runtime Exception)和受检查的异常(Checked Exception);

3.2关于Checked和Unchecked异常

上面一小节提到了Checked异常及Unchecked异常,那么这种异常的区别是什么呢?其中派生于Error或者RuntimeException的异常称为Unchecked异常,所有其他的Exception称为Checked异常。在上图所示中,浅蓝色方框所标注的为Unchecked异常。

那么为什么要这样区分他们呢?因为Java compiler(编译器)强制要求所有的Exception要么被catch,要么被throw对其进行处理,否则编译会不通过,除非这是一个RuntimeException(e instanceof RuntimeException)。也就是说,通常的Exception一定要被处理,也即我们所说的Checked Exception,而RuntimeException/Error,编译器是不强制要求处理的,所以被称为UnChecked exception。

3.3关于RuntimeException

Oracle 官网解释:
RuntimeException and its subclasses are unchecked exceptions. Unchecked exceptions do not need to be declared in a method or constructor’s throws clause if they can be thrown by the execution of the method or constructor and propagate outside the method or constructor boundary.
RuntimeException(运行时异常),它属于Unchecked异常。如果出现RuntimeException,那么一定是程序员代码的问题,编译器并不会检查Unchecked异常。在开发过程中,我们遇到的crash绝大多数都属于RuntimeException(RuntimeException是引起Crash主要原因);

4.Crash崩溃率的统计

影响用户体验因素:

App的应用性能稳定是良好用户体验中非常关键的一环,而现实情况却是应用崩溃、卡顿、加载缓慢、页面白屏等问题,频频出现在用户的真实体验之中,成为影响业务表现的直接杀手。为此,应用性能管理(APM)正在国内外蓬勃发展,被越来越多的企业所认可。

友盟崩溃率统计:

友盟+U-APM数据白皮书显示:APP 整体崩溃率为0.293%,其中Android 端崩溃率为0.32%, iOS 端崩溃率为0.1%,头部移动应用在治理崩溃表现方面表现更为优异,更加注重在应用质量治理方面的精细打磨。

参考:

移动应用性能体验报告-Android崩溃率达0.32%,OV、华为表现良好

app的崩溃率标准,优秀,合格,轻微隐患,严重隐患

5.线程如何处理一个未捕获异常导致的线程中断

5.1Thread定义了UncaughtExceptionHandler异常处理器接口

public interface UncaughtExceptionHandler {
        void uncaughtException(Thread t, Throwable e);
}

5.2Thread定义两个UncaughtExceptionHandler成员变量

//成员变量,线程独有的
// null unless explicitly set
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;

//静态变量用于所有线程
// null unless explicitly set
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

Thread中存在两个UncaughtExceptionHandler成员变量:

1)一个是静态的defaultUncaughtExceptionHandler:来自所有线程中的Exception在抛出并且未捕获的情况下,都会从此路过。进程fork的时候设置的就是这个静态defaultUncaughtExceptionHandler,管辖范围为整个进程。

2)另一个非静态uncaughtExceptionHandler:为单个线程设置一个属于线程自己的uncaughtExceptionHandler,管辖范围比较小。

当一个线程由于未捕获异常即将终止时,Java虚拟机将使用Thread的getDefaultUncaughtExceptionHandler()方法查询线程unCaughtException处理程序,并调用处理程序的uncaughtException方法,将线程和异常作为参数传递。一个线程如果没有设置uncaughtExceptionHandler,将使用线程所在的线程组来处理这个未捕获异常。线程组ThreadGroup实现UncaughtExceptionHandler,所以可以用来处理未捕获的异常。

ThreadGroup实现uncaughtException如下:

public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            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);
            }
        }
    }

1)首先将异常消息通知给父线程组处理;

2)否则,尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常;

3)如果没有默认的异常处理则将错误信息输出到System.err。

6.Android如何自定义添加全局接收Crash异常错误信息

在Android中,我们同样可以使用UncaughtExceptionHandler来添加运行时异常的回调,来监控Crash发生;

6.1创建一个自定义UncaughtExceptionHandler类

//实现UncaughtExceptionHandler接口
public class CrashHandler implements Thread.UncaughtExceptionHandler {
    
    private static CrashHandler mAppCrashHandler;
    //系统默认的处理器
    private Thread.UncaughtExceptionHandler mDefaultHandler;

    private CrashHandler(){}

    public void initCrashHandler() {
        // 获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //设置系统
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    public static CrashHandler getInstance() {
        if (mAppCrashHandler == null) {
            mAppCrashHandler = new CrashHandler();
        }
        return mAppCrashHandler;
    }

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        // 回调函数,处理异常
        // 在这里将崩溃日志读取出来,然后保存到SD卡,或者直接上传到日志服务器
        // 注意 保存或者上传到日志服务器需要将系统版本,手机唯一标识等系统信息和崩溃信息一同上传,方便分析crash问题
        // 如果用户没有处理则让系统默认的异常处理器来处理
        
    }
}

6.2在Application子类内设置全局监控

CrashHandler.getInstance().initCrashHandler()

完成以上2个步骤,我们就可以实现全局Crash监控了。这是所说的全局,是针对整个进程生效;

7.Android系统中Crash如何处理、分发逻辑

上面我们了解了怎么在Android中补货Crash,实现Crash的监控,上报。那么Android中系统是如何处理、分发Crash的呢?

7.1异常处理的注册

APP启动时,会通过zygote进程fork一个进程,然后创建VM虚拟机,然后会调用到zygoteInit进行初始化工作。

zygoteInit方法

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java的zygoteInit方法:

public static final Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
            String[] argv, ClassLoader classLoader) {
        ...
        RuntimeInit.commonInit();
        ...
    }

zygoteInit方法主要被zygote进程调用,zygoteInit调用了RuntimeInit.commonInit()方法;

RuntimeInit.commonInit()方法

 protected static final void commonInit() {
        if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");

        /*
         * set handlers; these apply to all threads in the VM. Apps can replace
         * the default handler, but not the pre handler.
         */
        LoggingHandler loggingHandler = new LoggingHandler();
        RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
        ...
    }

逻辑分析:

1)LoggingHandler主要处理打印日志,打印一个线程遇到的未捕获的异常信息;

2)Thead.setDefaultUncaughtExceptionHandler()用于注册系统默认异常处理的逻辑;

这里的RuntimeHooks.setUncaughtExceptionPreHandler()方法,其实是调用了Thread的setUncaughtExceptionPreHandler()方法;

Android 8.0中,Thread类增加了一个接口叫setUncaughtExceptionPreHandler,它会注册在分发异常处理时的回调,用于Android系统(平台)使用;

我们先来看异常的分发逻辑,后面再分析KillApplicationHandler中对异常的处理逻辑;

异常的分发

Thread的dispatchUncaughtException负责处理异常的分发逻辑;

Thread的dispatchUncaughtException

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);
    }

这里首先处理setUncaughtExceptionHandler注册的异常处理方法,然后调用通过setDefaultUncaughtExceptionHandler或者setUncaughtExceptionHandler方法注册的异常处理方法;

我们来看getUncaughtExceptionHandler()方法的返回值;

Thread的getUncaughtExceptionHandler()方法

    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

逻辑分析:

1)当uncaughtExceptionHandler不为空时,返回uncaughtExceptionHandler;

uncaughtExceptionHandler是通过setUncaughtExceptionHandler方法注册异常处理Handler;

2)否则,返回group

3)这里的group,其实就是当前线程所在的线程组;并且线程组ThreadGroup同样实现了UncaughtExceptionHandler接口;

TheadGroup的uncaughtException

在本文第一部分,我们其实已经介绍了,这里来回顾一下:

public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            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);
            }
        }
    }

逻辑分析:

1)首先将异常消息通知给父线程组处理;

2)否则,尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常;

3)如果没有默认的异常处理则将错误信息输出到System.err;

7.2异常的处理

系统默认异常处理逻辑在KillApplicationHandler类的uncaughtException方法中,我们来看代码;

KillApplicationHandler类的uncaughtException方法

  @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                //确保LoggingHandler
                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();
                }
                
                //调用AMS,展示弹框等逻辑
                // 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);
            }
        }

逻辑分析:

1)调用ensureLogging(t,e)确保LoggingHandler的执行(有去重逻辑,不用担心重复执行);

2)然后调用了ActivityManager.getService().handleApplicationCrash()方法来进行处理;

3)最后调用Process.killProcess(Process.myPid())来杀死进程,并且退出VM;

AMS的handleApplicationCrash方法

我们继续来看ActivityManagerService的handleApplicationCrash方法:
位置:/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public 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);
    }

这里通过Application的binder,取得进程的ProcessRecord对象,然后调用handleApplicationCrashInner方法;

AMS的handleApplicationCrashInner方法

 void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
            ApplicationErrorReport.CrashInfo crashInfo) {

        //处理一些错误日志的相关逻辑
        
        //调用
        mAppErrors.crashApplication(r, crashInfo);
    }

这里处理一些错误日志的相关逻辑,然后调用AppErrors的crashApplication方法。

AppErrors的crashApplication方法

/frameworks/base/services/core/java/com/android/server/am/AppErrors.java

void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
        final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();

        final long origId = Binder.clearCallingIdentity();
        try {
            crashApplicationInner(r, crashInfo, callingPid, callingUid);
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

crashApplication调用crashApplicationInner方法,处理系统的crash弹框等逻辑,这里我们就不再详细分析了,感兴趣的可以看源代码了解一下;

到这里,关于系统默认的异常捕获及处理逻辑我们也就分析完成了。

8.Crash优化建议

Crash是APP性能的一个非常重要的指标,我们要尽可能的减少Crash,增加APP的稳定性,一下是几点实际经验:

1)要有可靠的Crash日志收集方式:可以自己实现,也可以集成第三方SDK来采集分析;

2)当一个Crash发生了,我们不但需要针性的解决这一个Crash,而且要考虑这一类Crash怎么去解决和预防,只有这样才能使得该类Crash真正的解决,而不是反复出现。
3)不能随意的使用try-catch,这样只会隐蔽真正的问题,要从根本上了解Crash的原因,根据原因去解决。
4)增加代码检测,预防常规可检测的代码问题的产生,预防胜于治理。

9.总结 

1)Java异常可分为:可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。

2)Java中,可以通过设置Thread类的uncaughtExceptionHandler属性或静态属性defaultUncaughtExceptionHandler来设置不可查异常的回调处理。

3)在Android中,运行时异常(属于不可查的异常),如果没有在try-catch语句捕获并处理,就会产生Crash,导致程序崩溃。

4)在Android中,我们同样可以使用UncaughtExceptionHandler来添加运行时异常的回调,来监控Crash的发生。

5)通过Thread的静态方法setDefaultUncaughtExceptionHandler方法,可以注册全局的默认Crash监控。通过Thread的setUncaughtExceptionHandler方法来注册某个线程的异常监控。

6)setDefaultUncaughtExceptionHandler方法和setUncaughtExceptionHandler方法有注册顺序的问题,多次注册后,只有最后一次生效。

7)Android系统中,默认的Crash处理Handler,是在进程创建时,通过RuntimeInit.commonInit()方法进行注册的。

8)Android 8.0中,Thread类增加了一个接口叫setUncaughtExceptionPreHandler,它会注册在分发异常处理时的回调,用于Android系统(平台)使用。

9)Thread的dispatchUncaughtException负责处理异常的分发逻辑。

10)Android中,异常分发顺序为:
        a.首先处理setUncaughtExceptionPreHandler注册的异常处理方法;
        b.然后处理线程私有的(uncaughtExceptionHandler)Handler异常处理方法;
        c.如果私有Handler不存在,则处理ThreadGroup的Handler异常处理方法;
        d.ThreadGroup中,优先调用父线程组的处理逻辑,否则,调用通过setUncaughtExceptionHandler方法注册异常处理Handler。
11)系统默认异常处理逻辑在KillApplicationHandler类的uncaughtException方法中,系统默认Crash弹框等逻辑是通过AMS的handleApplicationCrash方法执行的。
Crash是App性能的一个非常重要的指标,我们要尽可能的减少Crash,增加App的稳定性。

4.1Crash解决思路

4.2Crash解决实例

参考:

Java、Android的异常处理原理&Android Crash捕获、分发及处理原理+实战详解

Android 崩溃优化之Java篇(二)

Android防止应用崩溃

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ahou2468/article/details/120022670

智能推荐

【华为云技术分享】干货!!卷积神经网络之LeNet-5迁移实践案例_华为云开发者联盟的博客-程序员秘密

摘要:LeNet-5是Yann LeCun在1998年设计的用于手写数字识别的卷积神经网络,当年美国大多数银行就是用它来识别支票上面的手写数字的,它是早期卷积神经网络中最有代表性的实验系统之一。可以说,LeNet-5就相当于编程语言入门中的“Hello world!”。华为的昇腾训练芯片一直是大家所期待的,目前已经开始提供公测,如何在昇腾训练芯片上运行一个训练任务,这是目前很多人都在采坑过程中,所以我写了一篇指导文章,附带上所有相关源代码。注意,本文并没有包含环境的安装,请查看另外相关文档。环境约束

python爬虫文件代码大全-23个Python爬虫开源项目代码_weixin_37988176的博客-程序员秘密

今天为大家整理了23个Python爬虫项目。整理的原因是,爬虫入门简单快速,也非常适合新入门的小伙伴培养信心。所有链接指向GitHub,祝大家玩的愉快1、WechatSogou [1]– 微信公众号爬虫。基于搜狗微信搜索的微信公众号爬虫接口,可以扩展成基于搜狗搜索的爬虫,返回结果是列表,每一项均是公众号具体信息字典。2、DouBanSpider [2]– 豆瓣读书爬虫。可以爬下豆瓣读书标签下的所有...

Red Hat Enterprise Linux 7 server reboots when Trend Micro DS agent is updated_linux trend micro_victoruu的博客-程序员秘密

环境Red Hat Enterprise Linux 7 Trend Micro Deep Security Agent 9.6.2-7888.el7 (Kernel modules gsch and redirfs) Imperva Agent 11.5.0.5030 (kernel module krg_11_5_0_5030_imRH7K1smp64)问题What is the...

spring zuul的日志配置_dream8062的博客-程序员秘密

设置zuul.debug.request=true  #如果设置了这个,默认所有的请求都会debugzuul.include-debug-header: true未设置zuul.debug.request=true,可以用zuul_host:zuul_port/路径?debug=true    debug你的指定请求 logging:      level:

SQL技术第十章使用行函数_车晋强的博客-程序员秘密

第十章使用行函数第十章 使用行函数第一节 其它行函数currentuser:当前会话使用的用户ID的名字currentuser()=`Admin`now() date() time() 指当前的日期和时间now()=`12-20-1999 10:30:25 am`date()=`12-20-1999`time()=`10:30:25 am`nz:将null转换为其他的值n

随便推点

FFMPEG音视频解码_weixin_30256505的博客-程序员秘密

文章转自:https://www.cnblogs.com/CoderTian/p/6791638.html1.播放多媒体文件步骤通常情况下,我们下载的视频文件如MP4,MKV、FLV等都属于封装格式,就是把音视频数据按照相应的规范,打包成一个文本文件。我们可以使用MediaInfo这个工具查看媒体文件的相关信息。所以当我们播放一个媒体文件时,通常需要经过以下几个步骤①解封装...

数据库基础知识汇总!_数据库基础知识入门_如鲲一般的博客-程序员秘密

主流的数据库软件都有哪些?开原可以跨平台的数据库都有哪些?甲骨文:orcaleIBM:DB2微软:SQL server开原的可以跨平台的:Mysql:开源切跨平台;           Orcale   DB2:跨平台不开源SQL server  :不跨平台,不开源;mysql的服务进程叫什么名字?端口是多少? 默认数据库的目录?进程名:mysqld        ...

C++ 语言实现1加到100(初学者)_c++1加到100_oo_vvL的博客-程序员秘密

//算出1加到100的值1+2+3+4+....+100    0+1=1 1+2 =3  3+3= 6  6+4 =10  .....3+3+4+....+100        假设a是 1到100,b是累积数值 6+4+...+100             b是不断变化的 ,这个变化是加a 导致的,所以 把b+a再赋予b。                           

akka-stream与actor系统集成以及如何处理随之而来的背压问题_weixin_30493321的博客-程序员秘密

这几天上海快下了五天的雨️️️️,淅淅沥沥,郁郁沉沉。一共存在四个api:Source.actorRef,返回actorRef,该actorRef接收到的消息,将被下游消费者所消费。Sink.actorRef,接收actorRef,做为数据流下游消费节点。Source.actorPublisher,返回actorRef,使用于reactive stream的Pub...

模板函数与函数模板 类模板和模板的说明_多模板函数模板会互相影响_rufeng18的博客-程序员秘密

一,模板的概念。引入模板的原因:我们已经学过重载,对重载函数而言,C++的检查机制能通过函数参数的不同及所属类的不同。正确的调用重载函数。例如,为求两个数的最大值,我们定义MAX()函数需要对不同的数据类型分别定义不同重载版本。Int max(int x,int y);{return(x>y)?x:y ;}float max( float x,float y){return (x>y)? x:y

69道HTML 面试知识点总结_普通网友的博客-程序员秘密

来源 |https://github.com/CavsZhouyou/Front-End-Interview-Notebook/blob/master/Html/Html.md1、DO...

推荐文章

热门文章

相关标签