写点什么

你知道 App 为什么会 Crash 吗?,Android 性能优化之 APK 优化

用户头像
Android架构
关注
发布于: 刚刚

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.


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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;


}


//派发发消息到对应的 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);


}


}


}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
你知道App为什么会Crash吗?,Android性能优化之APK优化