04.Android崩溃Crash库之Loop拦截崩溃和ANR
时间:2022-07-24
本文章向大家介绍04.Android崩溃Crash库之Loop拦截崩溃和ANR,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
目录总结
- 01.能否利用Looper拦截崩溃
- 02.思考几个问题分析
- 03.App启动时自动开启Looper
- 04.拦截主进程崩溃
前沿
- 上一篇整体介绍了crash崩溃库崩溃重启,崩溃记录记录,查看以及分享日志等功能。
- 项目地址:https://github.com/yangchong211/YCAndroidTool
- 欢迎star
01.能否利用Looper拦截崩溃
- 问题思考一下
- 能否基于 Handler 和 Looper 拦截全局崩溃(主线程),避免 APP 退出。
- 能否基于 Handler 和 Looper 实现 ANR 监控。
- 测试代码如下所示public class App extends Application { @Override public void onCreate() { super.onCreate(); CrashTestDemo.test(); } }
//测试代码
public class CrashTestDemo {
private static long startWorkTimeMillis = 0L;
public static void test(){
Looper.getMainLooper().setMessageLogging(new Printer() {
@Override
public void println(String it) {
if (it.startsWith(">>>>> Dispatching to Handler")) {
startWorkTimeMillis = System.currentTimeMillis();
} else if (it.startsWith("<<<<< Finished to Handler")) {
long duration = System.currentTimeMillis() - startWorkTimeMillis;
if (duration > 100) {
Log.e("Application---主线程执行耗时过长","$duration 毫秒,$it");
}
}
}
});
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
while (true){
try {
Looper.loop();
} catch (Throwable e){
if (e.getMessage()!=null && e.getMessage().startsWith("Unable to start activity")){
android.os.Process.killProcess(android.os.Process.myPid());
break;
}
e.printStackTrace();
Log.e("Application---Looper---",e.getMessage());
}
}
}
});
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
e.printStackTrace();
Log.e("Application-----","uncaughtException---异步线程崩溃,自行上报崩溃信息");
}
});
}
}
```
- 通过上面的代码就可以就可以实现拦截UI线程的崩溃,耗时性能监控。但是也并不能够拦截所有的异常,如果在Activity的onCreate出现崩溃,导致Activity创建失败,那么就会显示黑屏。
02.思考几个问题分析
- 通过上面简单的代码,我们就实现崩溃和ANR的拦截和监控,但是我们可能并不知道是为何实现的,包括我们知道出现了ANR,但是我们还需要进一步分析为何处出现ANR,如何解决。
- 今天分析的问题有:
- 如何拦截全局崩溃,避免APP退出。如何实现 ANR 监控。拦截到了之后可以做什么处理,如何优化?
03.App启动时自动开启Looper
- 先从APP启动开始分析,APP的启动方法是在ActivityThread中,在main方法中创建了主线程的Looper,也就是当前进程创建。
- 在main方法的最后调用了 Looper.loop(),在这个方法中处理主线程的任务调度,一旦执行完这个方法就意味着APP被退出了。
- 如果我们要避免APP被退出,就必须让APP持续执行Looper.loop()。注意这句话非常重要!!!public final class ActivityThread extends ClientTransactionHandler { ... public static void main(String[] args) { ... Looper.prepareMainLooper(); ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } }
- 那进一步分析Looper.loop()方法
- 在这个方法中写了一个循环,只有当 queue.next() == null 的时候才退出,看到这里我们心里可能会有一个疑问,如果没有主线程任务,是不是Looper.loop()方法就退出了呢?
- 实际上queue.next()其实就是一个阻塞的方法,如果没有任务或没有主动退出,会一直在阻塞,一直等待主线程任务添加进来。
- 当队列有任务,就会打印信息 Dispatching to ...,然后就调用 msg.target.dispatchMessage(msg);执行任务,执行完毕就会打印信息 Finished to ...,我们就可以通过打印的信息来分析 ANR,一旦执行任务超过5秒就会触发系统提示ANR,但是我们对自己的APP肯定要更加严格,我们可以给我们设定一个目标,超过指定的时长就上报统计,帮助我们进行优化。public final class Looper { final MessageQueue mQueue; 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 msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } try { msg.target.dispatchMessage(msg); } finally {} if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } msg.recycleUnchecked(); } } public void quit() { mQueue.quit(false); } }
- 如何让app崩溃后不会退出
- 如果主线程发生了异常,就会退出循环,意味着APP崩溃,所以我们我们需要进行try-catch,避免APP退出,我们可以在主线程再启动一个 Looper.loop() 去执行主线程任务,然后try-catch这个Looper.loop()方法,就不会退出。
04.拦截主进程崩溃
- 拦截主进程崩溃其实也有一定的弊端,因为给用户的感觉是点击没有反应,因为崩溃已经被拦截了。如果是Activity.create崩溃,会出现黑屏问题,所以如果Activity.create崩溃,必须杀死进程,让APP重启,避免出现改问题。public class MyApplication extends Application { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); new Handler(getMainLooper()).post(new Runnable() { @Override public void run() { while (true) { try { Looper.loop(); } catch (Throwable e) { e.printStackTrace(); // TODO 需要手动上报错误到异常管理平台,比如bugly,及时追踪问题所在。 if (e.getMessage() != null && e.getMessage().startsWith("Unable to start activity")) { // 如果打开Activity崩溃,就杀死进程,让APP重启。 android.os.Process.killProcess(android.os.Process.myPid()); break; } } } } }); } }
项目地址:https://github.com/yangchong211/YCAndroidTool
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- python基础 -- 内建函数
- virtualenv 安装及使用
- Git 分布式版本控制 -- (1、基本使用)
- python爬虫----(scrapy框架提高(1),自定义Request爬取)
- python基础 -- 异常处理try的使用及一些思考
- 记一次关于MySQL与PHP的蠢事
- 编写一个简单的JQuery插件
- Spring AOP异常:Error creating bean with name ‘org.springframework.aop.aspectj.
- Tomcat7 Redis Session 共享
- CodeIgniter (CI) 框架学习 -- load_class
- Laravel框架学习 -- php artisan down/up
- Spring中基于注解@AspectJ的AOP实现
- python提高--running-python-code-contained-in-a-strin
- linux shell 监控脚本 及 邮件发送
- Laravel框架学习 -- 安装