我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android稳定性:可远程配置化的Looper兜底框架

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android稳定性:可远程配置化的Looper兜底框架

正文

App Crash对于用户来讲是一种最糟糕的体验,它会导致流程中断、app口碑变差、app卸载、用户流失、订单流失等。相关数据显示,当Android App的崩溃率超过0.4%的时候,活跃用户有明显下降态势。

目前受益于我司采取的一系列的治理、监控、防劣化体系,java crash率降低到了一个十万分级别的数字**,**今天分享的就是稳定性治理过程中的一个重要工具,下面开整。

1. 为什么抛出异常时app会退出

不细致分析了,网上随便找一下就是一堆博客,简单来说就是没有被catch的崩溃抛出时,会调用 Thread#dispatchUncaughtException(throwable) 来进行处理,而在进程初始化时,RuntimeInit#commonInit 里会注入默认杀进程的 KillApplicationHandler,如果我们没有实现自定义的 UncaughtExceptionHandler 时,dispatchUncaughtException 被调用就会走到 KillApplicationHandler 里,把当前的进程杀掉,即产生了一次用户感知的崩溃行为。

2. 有没有办法打造一个永不崩溃的app

这个问题问出来的前提是指发生的崩溃是来自于 java 层面的未捕获的异常,c 层就是另一回事了。我们来尝试回答一下这个问题:

答:可以,至少可以做到把所有的异常都吃掉。

问:那么怎么做呢?

答:当某个线程发生异常时,只要不让 KillApplicationHandler 处理这个异常就行了,即只要覆盖掉默认的 UncaughtExceptionHandler 就行了噢。

问:那当这样做的时候,比如主线程抛出一个异常被吃掉了,app还能正常运行吗?

答:不能了,因为不做任何处理的话,当前线程的 Looper.loop()就被终止了。如果是主线程的话,此时你将会获得一个 anr

问:怎么才能在吃掉异常的同时,让主线程继续运行呢?

答:当由于异常抛出,导致线程的 Looper.loop() 终止之后,接管 Looper.loop()。代码大概长下面这样:

public class AppCrashHandler implements UncaughtExceptionHandler {
    @Override
    public void uncaughtException(@NonNull Thread thread, @NonNull Throwable ex) {
        while (true) {
            try {
                if (Looper.myLooper() == null) {
                    Looper.prepare();
                }
                Looper.loop();
            } catch (Exception e) {
            }
        }
    }
}

上面这段代码,就是我标题中被描述为 Looper 兜底框架的实现机制。但是对于一个正常的app,线上是不可能这样无脑的catch,然后 Looper.loop的,这是因为:

  • 不是所有的异常都需要被catch住,如:OOM、launcher Activity onCreate之类的。
  • 稳定性不是靠屏蔽问题,而是靠解决问题,当异常无法解决或者解决成本太高,且异常被屏蔽对用户、业务来说并没有啥实质性的影响时,可以被屏蔽,当异常抛出时已经对业务产生了破坏,但是通过保护住然后重试可以让业务恢复运作时,也可以被屏蔽,只是多了个环节,即修复异常。

问:到底什么异常需要被吃掉呢?

上一个回答中我们大致将需要被吃掉的异常分了两类

  • 异常我们无法解决或者解决成本太高

举个例子,假如公司有使用 react native 之类的三方大框架,当业务抛出来一个如下的异常时,我们就可以认为这无法解决。

com.facebook.react.bridge.JSApplicationIllegalArgumentException: connectAnimatedNodes: Animated node with tag (child) [30843] does not exist
    at com.facebook.react.animated.NativeAnimatedNodesManager.connectAnimatedNodes(NativeAnimatedNodesManager.java:7)
    at com.facebook.react.animated.NativeAnimatedModule$16.execute
    at com.facebook.react.animated.NativeAnimatedModule$ConcurrentOperationQueue.executeBatch(NativeAnimatedModule.java:7)
    at com.facebook.react.animated.NativeAnimatedModule$3.execute
    at com.facebook.react.uimanager.UIViewOperationQueue$UIBlockOperation.execute
    at com.facebook.react.uimanager.UIViewOperationQueue$1.run(UIViewOperationQueue.java:19)
    at com.facebook.react.uimanager.UIViewOperationQueue.flushPendingBatches(UIViewOperationQueue.java:10)
    at com.facebook.react.uimanager.UIViewOperationQueue.access$2600
    at com.facebook.react.uimanager.UIViewOperationQueue$DispatchUIFrameCallback.doFrameGuarded(UIViewOperationQueue.java:6)
    at com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.java:1)
    at com.facebook.react.modules.core.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:7)
    at com.facebook.react.modules.core.ChoreographerCompat$FrameCallback$1.doFrame
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1118)
    at android.view.Choreographer.doCallbacks(Choreographer.java:926)
    at android.view.Choreographer.doFrame(Choreographer.java:854)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1105)
    at android.os.Handler.handleCallback(Handler.java:938)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:238)
    at android.os.Looper.loop(Looper.java:379)
    at android.app.ActivityThread.main(ActivityThread.java:9271)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:567)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1018)

2. 异常被屏蔽对用户、业务来说并没有实质性影响

- 比如老生常谈的 Android 7.x toast的 BadTokenException 之类的系统崩溃,一呢发生概率非常低,二呢在Android 8上的修复方式也只是 try-catch 住。

- 一些不影响业务、用户使用的三方库崩溃,比如瞎说一个,当使用 OkHttp 在请求接口时,内部切了个线程执行了个更新缓存的任务,结果里面抛出了一个 NPE 。外面没法 try-catch ,而且这个异常抛出时,顶多下次请求不走缓存,实际上没啥太大影响。

3. 异常很严重,但是吃掉之后通过修复运行环境能够让用户所使用的业务恢复正常运行

比如我们想要保存一张图片到磁盘上,但是磁盘满了, 抛出了一个no space left,这时候我们就可以将异常吃掉,同时清空app的磁盘缓存,并且告知用户重试,就可以成功的让用户保存图片成功

3. 如何Looper兜底框架辅助稳定性治理

我们先明确一下什么崩溃需要通过这种手段来治理、兜底:

系统崩溃,如老生常谈的 Android 7.x  toast的 BadTokenException

三方库的无痛崩溃,比如公司有使用 react native 之类的三方大框架,没有能力改或者不想改一些相关的 ui 引起的 崩溃,比如做动画时莫名其妙的抛出异常

 一些特殊崩溃,如磁盘空间不足引发的 no space left,可以尝试通过抓住崩溃同时清理一波app的磁盘缓存,再尝试继续运行。

其他...

问:为什么我的标题中强调了可远程配置化呢?

答:因为可远程配置化能够为框架本身赋能更多。

问:比如?

答:可以提供一种简易的线上容灾机制,假如线上在某个页面发生了一个崩溃,这个崩溃突然发生而且崩溃发生的点本身对业务来说无关紧要(比如有个开发手xx,Integer.parse整了个汉字,抛异常了),通过热修复来修吧,流程复杂,要改代码、打补丁包、配补丁包。紧急发版吧,成本比热修高了不知多少倍,这时如果有一个可配置化的Looper兜底框架,我通过更新我的配置,保护住这个 Integer.parse 异常,就能很轻松的解决线上问题。

4. 可配置化配置的是什么东西

首先这是一个崩溃保护的框架,那么配置的肯定是能描述崩溃的内容,那么什么东西能描述一个崩溃呢?无非就是以下元素:

  • throwable class name
  • throwable message
  • throwable stacktrace
  • Android version
  • app version
  • model
  • brand
  • ...

大致就是对崩溃做个标签匹配:这是个什么崩溃,发生在哪个Android版本,发生在哪个App版本,发生在哪个厂商哪个系统版本上。

5. 我们怎么做的

我们的画像标签大致长下面这样:

[  {    "class": "",    "message": "No space left on device",    "stack": [],
    "app_version": [],
    "clear_cache": 1,
    "finish_page": 0,
    "toast": "",
    "os_version": [],
    "model": []
  },
  {
    "class": "BadTokenException",
    "message": "",
    "stack": [],
    "app_version": [],
    "clear_cache": 0,
    "finish_page": 0,
    "toast": "",
    "os_version": [],
    "model": []
  }
]

配置里还加了一些额外的东西,比如:

  • 崩溃被保护住的时候,要不要清理下app的缓存
  • 崩溃被保护住的时候,要不要弹个 toast 告知用户
  • 崩溃被保护住的时候,要不要退出当前页面

就这样,我们的可配置化的Looper兜底框架的全貌就描述完了,最后再总结一下具体的工作流程吧。

Looper兜底流程

我们会注入自己的 UncaughtExceptionHandler,当App产生了一个未捕获的异常时,我们通过对这个异常进行几个标签的匹配来判断当前的崩溃是否要进行保护,当需要保护时,接管Looper.loop,让线程继续运行。

配置更新、生效流程:

当App启动时,拉取远程的崩溃画像配置,当未捕获的异常发生时,读取本地最新的配置,进行标签匹配,如果标签匹配成功,进行Looper兜底。

以上就是Android稳定性:可远程配置化的Looper兜底框架的详细内容,更多关于Android远程配置化Looper框架的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android稳定性:可远程配置化的Looper兜底框架

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android稳定性:可远程配置化的Looper兜底框架

这篇文章主要为大家介绍了Android稳定性可远程配置化的Looper兜底框架实例实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-02-12

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录