最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

RxJava 異常時堆棧顯示不正確?解決方法都在這里

2023-02-20 14:06 作者:程序員徐公  | 我要投稿

本文首發(fā)我的博客,github 地址

大家好,我是徐公,今天為大家?guī)淼氖?RxJava 的一個血案,一行代碼 return null 引發(fā)的。

前陣子,組內(nèi)的同事反饋說 RxJava 在 debug 包 crash 了,捕獲到的異常信息不全。(即我們捕獲到的堆棧沒有包含我們自己代碼,都是一些系統(tǒng)或者 RxJava 框架的代碼)

典型的一些 error 信息如下:

在這里插入圖片描述

可以看到,上面的 Error 堆棧信息中,它并沒有給出這個 Error 在實(shí)際項(xiàng)目中的調(diào)用路徑。可以看到,報(bào)錯的堆棧,提供的有效信息較少, 我們只能知道是由于 callable.call() ?這里返回了 Null,導(dǎo)致出錯。卻不能判斷 callable 是哪里創(chuàng)建的,這時候我們只能結(jié)合日志上下文,判斷當(dāng)前之前的代碼大概在哪里,再逐步排查。

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);
????}

}

一頓操作猛如虎,很多,我們結(jié)合一些讓下文日志,發(fā)現(xiàn)是這里返回了 null,導(dǎo)致出錯

backgroundTask(Callable<Any>?{
????Log.i(TAG,?"btn_rx_task:?")
????Thread.sleep(30)
????return@Callable?null
})?.subscribe()
/**
?*?創(chuàng)建一個rx的子線程任務(wù)Observable
?*/

private?fun?<T>?backgroundTask(callable:?Callable<T>?):?Observable<T>??{
????return?Observable.fromCallable(callable)
????????????.compose(IOMain())
}

如果遇到 callable 比較多的情況下,這時候 一個個排查 callable,估計(jì)搞到你吐血。

那有沒有什么較好的方法,比如做一些監(jiān)控?完整打印堆棧信息。

第一種方案,自定義 Hook 解決

首先,我們先來想一下,什么是堆棧?

在我的理解里面,堆棧是用來儲存我們程序當(dāng)前執(zhí)行的信息。在 Java 當(dāng)中,我們通過 java.lang.Thread#getStackTrace 可以拿到當(dāng)前線程的堆棧信息,注意是當(dāng)前線程的堆棧。

而 RxJava 拋出異常的地方,是在執(zhí)行 Callable#call ?方法中,它打印的自然是 Callable#call 的方法調(diào)用棧,而如果 Callable#call 的調(diào)用線程跟 callable 的創(chuàng)建線程不一致,那肯定拿不到 創(chuàng)建 callable 時候的堆棧。

而我們實(shí)際上需要知道的是 callable 創(chuàng)建的地方,對應(yīng)到我們我們項(xiàng)目報(bào)錯的地方,那自然是 Observable.fromCallable 方法的調(diào)用棧。

這時候,我們可以采用 Hook 的方式,來 Hook 我們的代碼

為了方便,我們這里采用了 wenshu 大神的 Hook 框架, github, 想自己手動去 Hook 的,可以看一下我兩年前寫的文章 Android Hook 機(jī)制之簡單實(shí)戰(zhàn),里面有介紹介紹一些常用的 Hook 手段。

很快,我們寫出了如下代碼,對 Observable#fromCallable 方法進(jìn)行 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
????????}

????}

再次執(zhí)行我們的代碼

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

可以看到,當(dāng)我們的 Callable 返回為 empty 的時候,這時候報(bào)錯的信息會含有我們項(xiàng)目的代碼, perfect。

image-20211122164509577

RxJavaExtensions

最近,在 Github 上面發(fā)現(xiàn)了這一個框架,它也可以幫助我們解決 RxJava 異常過程中信息不全的問題。它的基本使用如下:

使用

https://github.com/akarnokd/RxJavaExtensions

第一步,引入依賴庫

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

第二步:先啟用錯誤追蹤:

RxJavaAssemblyTracking.enable();

第三步:在拋出異常的異常,打印堆棧

????/**
?????*?設(shè)置全局的?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")
????????}
????}

image-20211122170335525

原理

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);
????}
}

可以看到,它調(diào)用了 RxJavaPlugins.setOnObservableAssembly 方法,設(shè)置了 RxJavaPlugins onObservableAssembly 變量

而我們上面提到的 Observable#fromCallable 方法,它里面會調(diào)用 RxJavaPlugins.onAssembly 方法,當(dāng)我們的 onObservableAssembly 不為 null 的時候,會調(diào)用 apply 方法進(jìn)行轉(zhuǎn)換。

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;
}

因此,即當(dāng)我們設(shè)置了 RxJavaAssemblyTracking.enable(), Observable#fromCallable 傳遞進(jìn)來的 supplier,最終會包裹一層,可能是 ObservableOnAssemblyScalarCallable,ObservableOnAssemblyCallable,ObservableOnAssembly。典型的裝飾者模式應(yīng)用,這里不得不說,RxJava 對外提供的這個點(diǎn),設(shè)計(jì)得真巧妙,可以很方便我們做一些 ?hook。

我們就以 ObservableOnAssemblyCallable 看一下

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

????final?ObservableSource<T>?source;

????//?將在哪里創(chuàng)建的?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 的構(gòu)造方法的時候,直接將 Callable 的堆棧信息保存下來,類為 RxJavaAssemblyException。

而當(dāng) error 報(bào)錯的時候,調(diào)用 ?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 信息完整打印出來的流程已經(jīng)講明白了,其實(shí)就是在創(chuàng)建 Callable 的時候,采用一個包裝類,在構(gòu)造函數(shù)的時候,將 error 信息報(bào)錯下來,等到出錯的時候,再將 error 信息,替換成保存下來的 error信息

我們的自定義 Hook 也是利用這種思路,提前將 callable 創(chuàng)建的堆棧暴露下來,換湯不換藥。

一些思考

上述的方案我們一般不會帶到線上,為什么呢? 因?yàn)閷τ诿恳粋€ callable,我們需要提前保存堆棧,而獲取堆棧是耗時的。那有沒有什么方法呢?

如果項(xiàng)目有接入 Matrix 的話,可以考慮借用 Matrix trace 的思想,因?yàn)樵诜椒ㄇ昂蟛迦?AppMethodBeat#iAppMethodBeat#o 這樣當(dāng)我們執(zhí)行方法的時候,因?yàn)椴鍢读?,我們可以方便得獲取到方法執(zhí)行耗時,以及方法的調(diào)用棧。

//?第一步:需要在合適的實(shí)際先生成?beginRecord
AppMethodBeat.IndexRecord??beginRecord?=?AppMethodBeat.getInstance().maskIndex("AnrTracer#dispatchBegin");
//?第二步:方法的調(diào)用棧信息在?data?里面
long[]?data?=?AppMethodBeat.getInstance().copyData(beginRecord);
第三步:
將?data?轉(zhuǎn)化為我們想要的?stack(初步看了代碼,需要我們修改?trace?的代碼)


RxJava 異常時堆棧顯示不正確?解決方法都在這里的評論 (共 條)

分享到微博請遵守國家法律
荥经县| 绩溪县| 海丰县| 分宜县| 东阳市| 左云县| 鲜城| 青海省| 道真| 阳春市| 穆棱市| 柯坪县| 雷州市| 确山县| 呈贡县| 金堂县| 德州市| 长沙县| 安徽省| 商洛市| 永顺县| 桐梓县| 黑龙江省| 营口市| 石家庄市| 乳山市| 大连市| 北海市| 沐川县| 关岭| 高清| 微山县| 青龙| 肃北| 巴楚县| 揭西县| 漳州市| 视频| 贺兰县| 玉林市| 石棉县|