关于android:RxJava-异常时堆栈显示不正确解决方法都在这里

2次阅读

共计 6749 个字符,预计需要花费 17 分钟才能阅读完成。

本文首发我的博客,github 地址

大家好,我是徐公,明天为大家带来的是 RxJava 的一个血案,一行代码 return null 引发的。

前阵子,组内的共事反馈说 RxJava 在 debug 包 crash 了,捕捉到的异样信息不全。(即咱们捕捉到的堆栈没有蕴含咱们本人代码,都是一些零碎或者 RxJava 框架的代码)

典型的一些 error 信息如下:

能够看到,下面的 Error 堆栈信息中,它并没有给出这个 Error 在理论我的项目中的调用门路。能够看到,报错的堆栈,提供的无效信息较少,咱们只能晓得是因为 callable.call() 这里返回了 Null,导致出错。却不能判断 callable 是哪里创立的,这时候咱们只能联合日志上下文,判断以后之前的代码大略在哪里,再逐渐排查。

public final class ObservableFromCallable<T> extends Observable<T> implements Callable<T> {
  

    @Override
    public void subscribeActual(Observer<? super T> observer) {DeferredScalarDisposable<T> d = new DeferredScalarDisposable<T>(observer);
        observer.onSubscribe(d);
        if (d.isDisposed()) {return;}
        T value;
        try {// callable.call()  这里返回了 Null,并传递给了 RxJavaPlugins 的 errorHandler
            value = ObjectHelper.requireNonNull(callable.call(), "Callable returned null");
        } catch (Throwable e) {Exceptions.throwIfFatal(e);
            if (!d.isDisposed()) {observer.onError(e);
            } else {RxJavaPlugins.onError(e);
            }
            return;
        }
        d.complete(value);
    }

}

一顿操作猛如虎,很多,咱们联合一些让下文日志,发现是这里返回了 null,导致出错

backgroundTask(Callable<Any> {Log.i(TAG, "btn_rx_task:")
    Thread.sleep(30)
    return@Callable null
})?.subscribe()
/**
 * 创立一个 rx 的子线程工作 Observable
 */
private fun <T> backgroundTask(callable: Callable<T>?): Observable<T>? {return Observable.fromCallable(callable)
            .compose(IOMain())
}

如果遇到 callable 比拟多的状况下,这时候 一个个排查 callable,预计搞到你吐血。

那有没有什么较好的办法,比方做一些监控?残缺打印堆栈信息。

第一种计划,自定义 Hook 解决

首先,咱们先来想一下,什么是堆栈?

在我的了解外面,堆栈是用来贮存咱们程序以后执行的信息。在 Java 当中,咱们通过 java.lang.Thread#getStackTrace 能够拿到 以后线程 的堆栈信息,留神是以后线程的堆栈

而 RxJava 抛出异样的中央,是在执行 Callable#call 办法中,它打印的天然是 Callable#call 的办法调用栈,而如果 Callable#call 的调用线程跟 callable 的创立线程不统一,那必定拿不到 创立 callable 时候的堆栈。

而咱们实际上须要晓得的是 callable 创立的中央,对应到咱们咱们我的项目报错的中央,那天然是 Observable.fromCallable 办法的调用栈。

这时候,咱们能够采纳 Hook 的形式,来 Hook 咱们的代码

为了不便,咱们这里采纳了 wenshu 大神的 Hook 框架, github, 想本人手动去 Hook 的,能够看一下我两年前写的文章 Android Hook 机制之简略实战,外面有介绍介绍一些罕用的 Hook 伎俩。

很快,咱们写出了如下代码,对 Observable#fromCallable 办法进行 hook

    fun hookRxFromCallable() {//        DexposedBridge.findAndHookMethod(ObservableFromCallable::class.java, "subscribeActual", Observer::class.java, RxMethodHook())
        DexposedBridge.findAndHookMethod(
            Observable::class.java,
            "fromCallable",
            Callable::class.java,
            object : XC_MethodHook() {override fun beforeHookedMethod(param: MethodHookParam?) {super.beforeHookedMethod(param)
                    val args = param?.args
                    args ?: return

                    val callable = args[0] as Callable<*>
                    args[0] = MyCallable(callable = callable)

                }

                override fun afterHookedMethod(param: MethodHookParam?) {super.afterHookedMethod(param)
                }
            })
    }

    class MyCallable(private val callable: Callable<*>) : Callable<Any> {

        private val TAG = "RxJavaHookActivity"
        val buildStackTrace: String?

        init {buildStackTrace = Rx2Utils.buildStackTrace()
        }

        override fun call(): Any {Log.i(TAG, "call:")
            val call = callable.call()
            if (call == null) {Log.e(TAG, "call should not return null: buildStackTrace is $buildStackTrace")
            }
            return call
        }

    }

再次执行咱们的代码

backgroundTask(Callable<Any> {Log.i(TAG, "btn_rx_task:")
    Thread.sleep(30)
    return@Callable null
})?.subscribe()

能够看到,当咱们的 Callable 返回为 empty 的时候,这时候报错的信息会含有咱们我的项目的代码,perfect。

RxJavaExtensions

最近,在 Github 下面发现了这一个框架,它也能够帮忙咱们解决 RxJava 异样过程中信息不全的问题。它的根本应用如下:

应用

https://github.com/akarnokd/R…

第一步,引入依赖库

dependencies {implementation "com.github.akarnokd:rxjava2-extensions:0.20.10"}

第二步:先启用谬误追踪:

RxJavaAssemblyTracking.enable();

第三步:在抛出异样的异样,打印堆栈

    /**
     * 设置全局的 onErrorHandler。*/
    fun setRxOnErrorHandler() {
        RxJavaPlugins.setErrorHandler { throwable: Throwable ->
            val assembled = RxJavaAssemblyException.find(throwable)
            if (assembled != null) {Log.e(TAG, assembled.stacktrace())
            }
            throwable.printStackTrace()
            Log.e(TAG, "setRxOnErrorHandler: throwable is $throwable")
        }
    }

原理

RxJavaAssemblyTracking.enable();

public static void enable() {if (lock.compareAndSet(false, true)) {
 
        // 省略了若干办法

        RxJavaPlugins.setOnObservableAssembly(new Function<Observable, Observable>() {
            @Override
            public Observable apply(Observable f) throws Exception {if (f instanceof Callable) {if (f instanceof ScalarCallable) {return new ObservableOnAssemblyScalarCallable(f);
                    }
                    return new ObservableOnAssemblyCallable(f);
                }
                return new ObservableOnAssembly(f);
            }
        });

     
        lock.set(false);
    }
}

能够看到,它调用了 RxJavaPlugins.setOnObservableAssembly 办法,设置了 RxJavaPlugins onObservableAssembly 变量

而咱们下面提到的 Observable#fromCallable 办法,它外面会调用 RxJavaPlugins.onAssembly 办法,当咱们的 onObservableAssembly 不为 null 的时候,会调用 apply 办法进行转换。

public static <T> Observable<T> fromCallable(Callable<? extends T> supplier) {ObjectHelper.requireNonNull(supplier, "supplier is null");
    return RxJavaPlugins.onAssembly(new ObservableFromCallable<T>(supplier));
}
public static <T> Observable<T> onAssembly(@NonNull Observable<T> source) {
    Function<? super Observable, ? extends Observable> f = onObservableAssembly;
    if (f != null) {return apply(f, source);
    }
    return source;
}

因而,即当咱们设置了 RxJavaAssemblyTracking.enable()Observable#fromCallable 传递进来的 supplier,最终会包裹一层,可能是 ObservableOnAssemblyScalarCallable,ObservableOnAssemblyCallable,ObservableOnAssembly。典型的装璜者模式利用,这里不得不说,RxJava 对外提供的这个点,设计得真奇妙,能够很不便咱们做一些 hook。

咱们就以 ObservableOnAssemblyCallable 看一下

final class ObservableOnAssemblyCallable<T> extends Observable<T> implements Callable<T> {

    final ObservableSource<T> source;

    // 将在哪里创立的 Callable 的堆栈信息保留下来
    final RxJavaAssemblyException assembled;

    ObservableOnAssemblyCallable(ObservableSource<T> source) {
        this.source = source;
        this.assembled = new RxJavaAssemblyException();}

    @Override
    protected void subscribeActual(Observer<? super T> observer) {source.subscribe(new OnAssemblyObserver<T>(observer, assembled));
    }

    @SuppressWarnings("unchecked")
    @Override
    public T call() throws Exception {
        try {return ((Callable<T>)source).call();} catch (Exception ex) {Exceptions.throwIfFatal(ex);
            throw (Exception)assembled.appendLast(ex);
        }
    }
}

public final class RxJavaAssemblyException extends RuntimeException {

    private static final long serialVersionUID = -6757520270386306081L;

    final String stacktrace;

    public RxJavaAssemblyException() {this.stacktrace = buildStackTrace();
    }
 }

能够看到,他是间接在 ObservableOnAssemblyCallable 的构造方法的时候,间接将 Callable 的堆栈信息保留下来,类为 RxJavaAssemblyException。

而当 error 报错的时候,调用 RxJavaAssemblyException.find(throwable) 形式,判断是不是 RxJavaAssemblyException,是的话,间接返回。

public static RxJavaAssemblyException find(Throwable ex) {Set<Throwable> memory = new HashSet<Throwable>();
    while (ex != null) {if (ex instanceof RxJavaAssemblyException) {return (RxJavaAssemblyException)ex;
        }

        if (memory.add(ex)) {ex = ex.getCause();
        } else {return null;}
    }
    return null;
}

到这里,RxJavaAssemblyTracking 能将 error 信息残缺打印进去的流程曾经讲明确了,其 实就是在创立 Callable 的时候,采纳一个包装类,在构造函数的时候,将 error 信息报错下来,等到出错的时候,再将 error 信息,替换成保留下来的 error 信息

咱们的自定义 Hook 也是利用这种思路,提前将 callable 创立的堆栈裸露下来,换汤不换药。

一些思考

上述的计划咱们个别不会带到线上,为什么呢?因为对于每一个 callable,咱们须要提前保留堆栈,而获取堆栈是耗时的。那有没有什么办法呢?

如果我的项目有接入 Matrix 的话,能够思考借用 Matrix trace 的思维,因为在办法前后插入 AppMethodBeat#iAppMethodBeat#o 这样当咱们执行办法的时候,因为插桩了,咱们能够不便得获取到办法执行耗时,以及办法的调用栈。

// 第一步:须要在适合的理论学生成 beginRecord
AppMethodBeat.IndexRecord  beginRecord = AppMethodBeat.getInstance().maskIndex("AnrTracer#dispatchBegin");
// 第二步:办法的调用栈信息在 data 外面
long[] data = AppMethodBeat.getInstance().copyData(beginRecord);
第三步:将 data 转化为咱们想要的 stack(初步看了代码,须要咱们批改 trace 的代码)

参考资料

rxjava-2-doesnt-tell-the-error-line

how-to-log-a-stacktrace-of-all-exceptions-of-rxjava2

举荐浏览

我的 5 年 Android 学习之路,那些年一起踩过的坑

职场上这四件事,越早晓得越好

腾讯 Matrix 增量编译 bug 解决之路,PR 已通过

我是站在伟人的肩膀上成长起来的,同样,我也心愿成为你们的伟人。感觉不错的话能够关注一下我的微信公众号 徐公

正文完
 0