写点什么

有赞 Android 崩溃保护的探索与实践

  • 2020-03-11
  • 本文字数:3582 字

    阅读完需:约 12 分钟

有赞 Android 崩溃保护的探索与实践

概述

Android 的 Crash 是件让人头疼的事,测试阶段好好的代码一上线就各种崩溃,即使是一个微不足道的 bug 也得发个 hotfix。很多时候我们更希望即使个别功能没法使用也不要崩溃,比如点击图片想看大图时,由于 onClick 回调中没做判空处理等导致 APP 崩溃了,这时我们更希望即使不能看大图也不要崩溃,这时你可以考虑使用 Bandage,当然 Bandage的强大之处远不止这些。

Bandage 是什么

Bandage:绷带,通用的止血工具。 Bandage可以最大程度保证 APP 可用,任何 Java 异常都不会导致 APP 崩溃。 Bandage试图在 APP 即将崩溃时尽量去挽救,不至于情况更糟糕(医生,我觉得我还可以再抢救一下)。当然有些异常是一定要终止 APP 的,不然可能会给公司造成更大的损失,对于这种异常,可以通过黑白名单决定要不要终止 APP。

Bandage 是如何实现的

拦截 Activity 生命周期的异常

Activity 生命周期(比如 onCreateonResume等)抛出异常时,如果不 finish掉抛出异常的 Activity 的话会导致黑屏。


如何拦截?


简单来说是替换了 ActivityThread.mH.mCallback


Activity 生命周期所有方法都是在 mHhandleMessage方法中调用的,只要能拦截这个 handleMessage方法就能拦截所有生命周期的异常。然而我们没法通过反射替换掉这个 mH对象。因为 mH是 ActivityThread 中一个 H 类的实例,H 类又继承自 Handler,H 类又是 ActivityThread 中的一个私有类,但是 Handler会在调用 handleMessage前调用 mCallback.handleMessagemCallback是可以被替换掉的。


替换方式如下,可以参考 hookmH 方法


    //mhHandler是ActivityThread.mH,callbackField 是 mH 中的 mCallback 字段,可以通过反射得到    callbackField.set(mhHandler, new Handler.Callback() {      //拦截到生命周期相关的消息      @Override      public boolean handleMessage(Message msg) {        switch (msg.what) {          case LAUNCH_ACTIVITY:            try {              //调用ActivityThread.mH.handleMessage              mhHandler.handleMessage(msg);              return true;            } catch (Throwable throwable) {              //捕获到生命周期的异常,可以直接关闭该Activity,参考下文的 finish Activity生命周期异常的Activity            }            //...省略部分相似逻辑        }        return false;      }    });
复制代码


相关代码


  • Looper.loop()方法


  public static void loop() {    for (; ; ) {      Message msg = queue.next(); // might block      msg.target.dispatchMessage(msg);    }  }
复制代码


Android 主线程所有的消息都是在这调用的,包括生命周期回调,view 绘制,自己 new Handler post 的消息等等。 msg.target就是相关联的 Handler,如果自己 new Handler 并 post 消息的话那么这个 target 就是你 new 的 Handler,也就是说哪个 Handler post 的 Message 就交给那个 Handler 处理。生命周期相关的 Message 是 mH post 的,所以要交给 mH处理。


  • Handler.dispatchMessage方法


  public void dispatchMessage(Message msg) {    if (msg.callback != null) {      handleCallback(msg);    } else {      if (mCallback != null) {        if (mCallback.handleMessage(msg)) {          return;        }      }      handleMessage(msg);    }  }
复制代码


可以看到会先判断 mCallback是否存在,存在的话就交给 mCallbackhandleMessage处理,如果 mCallbackhandleMessage返回 true 则不再调用 Handler 的 handleMessage方法。所以我们可以通过上述方式实现拦截 Activity 生命周期的异常。

finish 生命周期异常的 Activity

通过 ActivityManagerfinishActivity结束掉生命周期抛出异常的 Activity。


各版本 Android 的 ActivityManager 获取方式, finishActivity的参数, mToken(binder对象)的获取不一样,我们可以去每个版本的 Activity 的 finish 方法中查看,比如 API26 调用的是如下方法:


ActivityManager.getService().finishActivity(mToken, resultCode, resultData, finishTask)
复制代码


mToken可以从 mH的 message 中获取。具体实现可以参考这 finish Activity。

拦截主线程的其他异常

上文说过 Android 主线程所有的消息都是在 Looper.loop()方法中调用的,只要能 try catch 住这个 loop 方法就能实现拦截主线程的所有异常,我们可以在 uncaughtException方法中执行如下代码。实现方式如下:


while (true) {  try {    Looper.loop();  } catch (Throwable e) {  }}
复制代码


  • 为什么要加个 while 死循环?


如果不加 while 的话就只能捕获一次主线程的异常,下次主线程再抛出异常的话就没法在这捕获了。


  • 加了 while 不会 ANR 吗?


不会的,因为 while 内部又调用了 Looper.loop(),这时主线程就又开始消息循环了,主线程会不断的取走主线程中唯一的消息队列头部的消息执行掉,然后等待下一个消息的到来。所以主线程不会卡住,当然不会 ANR。每次主线程抛出异常时就会被我们的 try catch 捕获到,然后又进入了 while 循环。

拦截其他异常

通过 Thread.setDefaultUncaughtExceptionHandler 捕获其他异常。


    Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {      @Override      public void uncaughtException(Thread t, Throwable e) {      }    });
复制代码

异常处理

有些异常是一定要终止 APP 的,不然可能会对公司造成更大的损失,而有些异常是可以直接忽略的。建议通过黑白名单控制拦截到的异常是直接忽略还是杀进程。可以在 APP 启动时,或者 crash 后下次重启时请求接口更新黑白名单。


什么样的异常可以不杀进程?


  • 如果忽略该异常不会对公司造成损失可以不杀进程

  • 如果忽略该异常只是造成某个 Activity 打不开,而没有其他副作用的话可以不杀进程

  • 如果忽略该异常只是部分 UI 不展示,而没有其他副作用的话可以不杀进程

  • 单纯的 UI 展示 Activity 的话可以不杀进程(比如只是展示商品详情等),涉及到金钱的 Activity 建议杀进程(比如当前 Activity 中有些开单计算,支付,退款等逻辑)

  • 对于一些顽疾,每个版本都出现,但又找不到问题所在,忽略后又没啥影响的异常可以不杀进程


总之,要不要杀进程由你决定,只要可以提升用户体验,并且不会对公司造成额外损失都可以不杀进程。


注意: ViewRootImpl抛出异常时可能会导致黑屏,这种情况建议直接终止 APP。


遍历出错堆栈,如果是 ViewRootImpl相关的异常建议直接杀进程,不然可能导致黑屏。


黑白名单如何配置?


只根据异常堆栈的话可能无法唯一确定一个问题,比如有两个 Activity,各有一个 Handler,都 post 了一个 Runnable,run 方法中一开始就都抛出了空指针异常,如果单纯根据异常堆栈的话我们无法确定到底是哪个 Activity 中的 Handler 抛出的异常。可以根据当前所在的 Activity 和异常堆栈来解决。如果还是无法确定问题出处的话,谨慎起见建议一律终止 APP。不过绝大多数情况下只根据异常堆栈就可以确定问题出处。


为了减少获取黑白名单的数据量,可以把当前所在 Activity 的类名称和异常堆栈拼接在一起,然后计算 md5 值,黑白名单中只包含该 md5 值即可,客户端捕获到异常时只需要进行同样的计算逻辑并判断 md5 值是否包含在黑白名单里。

Bandage 的不足之处

Bandage很多情况下只是忽略掉异常,让主线程再次进入消息循环,执行下一个消息, Bandage完全不清楚应该如何挽救。所以有时你会发现 Activity 根本打不开,又或者 Activity 中部分数据显示不完整,又或者 view 点击没反应,又或者其他奇奇怪怪的问题。但有些情况下直接忽略掉某些崩溃是没有任何影响的,或许直接忽略是最明智的选择。


Bandage可以最大程度保证 APP 可用,有人说这种拦截方式很暴力,但 Android 默认的异常杀进程逻辑不是更暴力吗,杀进程并不能解决问题,杀进程后再自动恢复 Activity 反而会导致更多的问题。

bugly 使用问题

bugly 也会通过设置 Thread.setDefaultUncaughtExceptionHandler 监听应用的异常,监听到后只是上报一下,然后又交给了原来的异常处理器处理, Bandage也会设置 DefaultUncaughtExceptionHandler,所以为了能让 bugly 主动上报异常,建议在 bugly 初始化前初始化 Bandage。另外 Activity 生命周期的异常会被 Bandage捕获,所以不会自动上传到 bugly,可以手动上传,同理 looper.loop() 由于被 Bandage捕获了,所以也不会自动上传到 bugly。特别注意: Bandage所捕获到的异常可能是由于上一个异常被忽略导致的,对于这种异常我们只需要修复之前的异常就可以了。

一点建议

开发阶段可以不启用 Bandage,以免发现不了 bug,如果开发阶段一定要启用 Bandage话可以在捕获到异常时开启个警告 Activity,或者所有 Activity 顶部置为绿色等,用于提示开发者已经出现了 bug,这时可以直接手动杀进程查 bug 了。

效果图

码链接

https://github.com/android-notes/Cockroach/tree/X


2020-03-11 22:191387

评论

发布
暂无评论
发现更多内容

低代码:时代的选择

树上有只程序猿

低代码 低代码开发平台

想做跨境电商不知道如何搭建网站?看这篇教程就够了

平平无奇爱好科技

软件开发“自我毁灭”的七宗罪

高端章鱼哥

计划 软件开发

9款好用的在线流程图软件推荐!

彭宏豪95

效率 流程图 在线工具 科技 流程图绘制

使用指南|如何将 GreptimeDB 数据保存在阿里云 OSS

Greptime 格睿科技

数据库 阿里云 云原生 Greptime GreptimeDB

TiDB 7.4 发版:正式兼容 MySQL 8.0

编程猫

跬智信息(Kyligence)入选 IDC《中国数据智能市场生态图谱V4.0》

Kyligence

数据分析 指标平台

等不及了,2023云栖大会精彩剧透提前看!

阿里云CloudImagine

云计算 云栖大会

跨境电商项目还在冷启动?请收好这份“破冰”秘籍

YG科技

腾讯云发布新品数据编排平台(dop)-与大数据生态紧密结合,提供通用数据编排服务

腾讯云大数据

大数据

大规模语言LLaVA:多模态GPT-4智能助手,融合语言与视觉,满足用户复杂需求

汀丶人工智能

人工智能 计算机视觉 GPT 大语言模型

“创新启变 聚焦增长”极狐(GitLab)媒体沟通会,共话智能时代软件开发新生态

Geek_2d6073

数字化转型与架构-架构设计篇|系统组件有哪些?

数字随行

数字化转型

快收藏!中小电商企业必用的ERP软件ODooo“奶妈级”教程来了

平平无奇爱好科技

中小企业数字化人才困境重重,华为云耀云服务器L实例妙手回春

平平无奇爱好科技

语音识别技术:端到端的挑战与解决方案

来自四九城儿

软件测试/测试开发丨深入了解性能测试:方法、工具和最佳实践

测试人

软件测试 性能测试 测试开发

大模型背景下软件工程的机遇与挑战

CODING DevOps

阿里内推强推的并发编程学习笔记,原理+实战+面试题,面面俱到!

小小怪下士

Java 程序员 并发编程

被誉为轻量云服务器“鼻祖”的腾讯云,遇到最硬核对手

平平无奇爱好科技

Disruptor在流程编排中的应用与探索

ZA技术社区

金融科技 众安保险 ZA技术社区 keji

文心4.0,启动大模型时代的飞轮效应

脑极体

AI

网络安全(黑客)自学方向

网络安全学海

程序员 黑客 网络安全 信息安全 渗透测试

主打一个遥遥领先,这款轻量应用服务器真是太“硬”了

平平无奇爱好科技

中小企业数字化既要效率又要效益,这款轻量云服务器打破悖论

YG科技

稳定币揭幕:了解发展策略

区块链软件开发推广运营

数字藏品开发 dapp开发 区块链开发 链游开发 NFT开发

Acrobat Pro DC 2023(PDF编辑软件)mac/win

iMac小白

Acrobat DC 2023 PDF编辑软件 Acrobat DC 2023破解版 Acrobat DC 2023下载

敏捷思维和免费敏捷管理工具

顿顿顿

敏捷项目管理 敏捷开发管理工具 scrum工具 scrum敏捷工具 敏捷开发工具

通过观测云快速了解真实用户体验

Yestodorrow

可观测性 用户增长 业务增长 真实用户体验

后起之秀 虽迟未晚!这款轻量云服务器乱拳打死老师傅

YG科技

有赞 Android 崩溃保护的探索与实践_文化 & 方法_有赞技术_InfoQ精选文章