记一个EventBus与反射的坑

问题引入

代码示例如下

通过 EventBus 发送一个 MyEvent 事件,然后在接收事件的地方调用 Class.forName 后,问题现象是:forName 函数接下来所有的函数调用都没有执行,并且没有任何crash 堆栈。

//发送Event
EventBus.getDefault().post(MyEvent())


//接受Event
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEvent(event:MyEvent){
    //这里一系列的函数调用,最终调用到 testError 函数
    testError()
}


fun testError(){
    try{
        val clazz = Class.forName("xxx")

        //其它函数调用
    }catch(e:Exception){
        e.printStackTrace()
    }

}

开始分析

客诉反馈问题,我们最后复现定位到 testError 函数这里,但是没有进入 catch 块中,这是为什么呢?

是不是 EventBus 捕获了这个 NoSuchFieldError?

testError 函数是 EventBus 中调用的,但是从 EventBus 内部的代码 invokeSubscriber 就是发送事件到订阅者的,并没有 catch 相关的 Exception 这就很奇怪了。

因此我选择断点的方式来调试,发现进入的 InvocationTargetException 这个异常捕获里面

进入处理该异常的方法 handleSubscriberException 一探究竟

问题发现了,很明显,logSubscriberExceptions 这个参数是 false,WTF,哪个沙雕在项目中配置了 false 的。

于是我去项目中找到了这段配置,真是一万个草泥马,就是因为这个导致异常被 EventBus 内部消化了,没有任何的打印信息。

EventBus.builder()
.sendNoSubscriberEvent(BuildConfig.DEBUG)
.logNoSubscriberMessages(BuildConfig.DEBUG)
.logSubscriberExceptions(BuildConfig.DEBUG)// release 包居然把这个关闭了,怪不得,怪不得
.installDefaultEventBus();

为什么会被 InvocationTargetException 捕获了呢?

我尝试分析了 EventBus 是如何将事件分发给订阅者的,从上图中的 invokeSubscriber 中很明显,异常被 InvocationTargetException 捕获了,这个异常就是反射调用时产生的异常,我们来深入看看为什么 testError 中产生的异常最终会变成这个 InvocationTargetException

反射异常InvocationTargetException

为了验证这个问题,我写一个 demo 来验证看看:
在如下的代码中,点击事件内部去调用 "testThrowError" 方法,并且该方法内部抛出 NoSuchFieldError 错误,按照正常理解,整个程序肯定是 crash 了,但是实际上并没有,程序正常运行,并且捕获到了 InvocationTargetException 异常,你说奇不奇怪呢?

textView.setOnClickListener {
    Log.e("MainActivity", "click")
    try {
        AA::class.java.getDeclaredMethod("testThrowError").invoke(this)
    }catch (e:InvocationTargetException){
        Log.e("AA", "InvocationTargetException:${e.cause}")
        e.printStackTrace()
    }
}

fun testThrowError() {
    throw NoSuchFieldError("click")
}

来看看异常堆栈哦:


啧,这费解了🤔...

于是我打开 Method 类,想看看 invoke 函数是如何实现,但是事与愿违,是 native 的实现。

native 如何调用 invoke 函数?

根据异常的 InvocationTargetException 名字,我反推这个异常创建的地方,也就是下图这里。

我来解释一下,在 invokeMethodImpl 中,会将调用 invoke 中产生的异常进行包装成
InvocationTargetExceptionWrap any excetion with "Ljava/lang/reflect/InvocationTargetException" .... 嗯,看到源码这句注释应该就大概知道为什么会返回这个异常了吧。

下面来梳理一下创建InvocationTargetException的过程:
● 首先,它会获取当前发生的异常(包括 Error 类型也会被获取到),这个异常是通过soa.Env()->ExceptionOccurred()获取的。
● 然后,它会清除当前的异常,这是通过soa.Self()->ClearException()完成的。
● 接着,它会创建一个新的InvocationTargetException实例。这是通过调用soa.Env()->NewObject方法完成的,这个方法接受InvocationTargetException的类对象,构造器方法对象,以及原始的异常对象(这个原始异常对象就是被包装的那个异常 cause)。
● 最后,如果InvocationTargetException实例创建成功,它会将这个新的异常抛出。这是通过调用soa.Env()->Throw方法完成的。

所以,看到这里就明白了,为什么发送 EventBus 后在接收事件的地方出现异常并不会出现 crash 就是这个原因呢。

总结几点

  1. EventBus 的日志一定要打开,不然排查问题真的很要命
  2. 反射 Method.invode 方法记得要处理InvocationTargetException 然后获取该异常的 e.getCause() 得到包装的异常,就像 EventBus 源码就针对这个异常做了处理的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • EventBus大家一定不陌生,组件之间传递消息只要post一下,对方就能收到,还可以任意切换线程,究竟内部是如何...
    99123阅读 1,566评论 0 1
  • BroadcastReceiver ​ 本质上是一个监听器,分为接收者和发送者。用于监听(接收)应用发出的广播...
    dw_0920阅读 241评论 0 0
  • EventBus源码分析 Android开发中我们最常用到的可以说就是EventBus了,今天我们来深入研究一下E...
    BlackFlag阅读 507评论 3 4
  • 之前写过一篇关于EventBus的文章,大家的反馈还不错(EventBus3.0使用详解),如果你还没有使用过Ev...
    Android平头哥阅读 7,349评论 0 38
  • 对于Android开发老司机来说肯定不会陌生,它是一个基于观察者模式的事件发布/订阅框架,开发者可以通过极少的代码...
    飞扬小米阅读 1,467评论 0 50