參考:
AssetManager.finalize() Timed Out 分析
在項(xiàng)目中百匆,我們通常會(huì)遇見各種情況導(dǎo)致的
java.util.concurrent.TimeoutException
#34705 java.util.concurrent.TimeoutException
android.content.res.AssetManager.finalize() timed out after 120 seconds
android.content.res.AssetManager.destroy(Native Method)
#81204 java.util.concurrent.TimeoutException
java.io.FileInputStream.finalize() timed out after 120 seconds
java.lang.Daemons$Daemon.isRunning(Daemons.java:85)
#65204 java.util.concurrent.TimeoutException
android.content.res.AssetManager.finalize() timed out after 120 seconds
java.lang.Daemons$Daemon.isRunning(Daemons.java:89)
一.導(dǎo)致TimeoutException的原因
對(duì)象的析構(gòu)函數(shù)執(zhí)行時(shí)間超過了固定的最大時(shí)間
原理分析
Android在啟動(dòng)后會(huì)創(chuàng)建一些守護(hù)進(jìn)程旦袋,其中涉及到該問題的有兩個(gè),分別是FinalizerDaemon和FinalizerWatchdogDaemon
FinalizerDaemon 析構(gòu)守護(hù)線程楔敌。對(duì)于重寫了成員函數(shù)finalize的對(duì)象,它們被GC決定回收時(shí),并沒有馬上被回收郁轻,而是被放入到一個(gè)隊(duì)列中,等待FinalizerDaemon守護(hù)線程去調(diào)用它們的成員函數(shù)finalize文留,然后再被回收好唯。
FinalizerWatchdogDaemon析構(gòu)監(jiān)護(hù)守護(hù)線程。用來監(jiān)控FinalizerDaemon線程的執(zhí)行燥翅。一旦檢測那些重寫了finalize的對(duì)象在執(zhí)行成員函數(shù)finalize時(shí)超出一定時(shí)間骑篙,那么就會(huì)退出VM。
如果是FinalizerDaemon進(jìn)行對(duì)象析構(gòu)時(shí)間超過了MAX_FINALIZE_NANOS(這里是10s)森书,F(xiàn)inalizerWatchdogDaemon進(jìn)行就會(huì)拋出TimeoutException
二.異常出現(xiàn)的場景
在回收對(duì)象時(shí)靶端,設(shè)備進(jìn)入休眠狀態(tài),導(dǎo)致對(duì)象回收的結(jié)束時(shí)間和開始時(shí)間相差太久凛膏。
當(dāng)你的應(yīng)用處于后臺(tái)杨名,有對(duì)象需要釋放回收內(nèi)存時(shí)
記錄一個(gè)start_time 然后是FinalizerDaemon 開始析構(gòu)AssetManager對(duì)象
在這個(gè)過程中,設(shè)備突然進(jìn)入了休眠狀態(tài)猖毫,析構(gòu)執(zhí)行被暫停
當(dāng)過了一段時(shí)間台谍,設(shè)備被喚醒,析構(gòu)任務(wù)被恢復(fù)吁断,繼續(xù)執(zhí)行典唇,直至結(jié)束
在析構(gòu)完成后,得到一個(gè)end_time
FinalizerWatchdogDaemon 對(duì)end_time與start_time進(jìn)行差值對(duì)比胯府,發(fā)現(xiàn)超過了MAX_FINALIZE_NANOS介衔,于是就拋出了TimeOut異常
三. 解決方法
方法一
使用反射讓FinalizerWatchdogDaemon線程不執(zhí)行
public static void fix() {
try {
Class clazz = Class.forName("java.lang.Daemons$FinalizerWatchdogDaemon");
Method method = clazz.getSuperclass().getDeclaredMethod("stop");
method.setAccessible(true);
Field field = clazz.getDeclaredField("INSTANCE");
field.setAccessible(true);
method.invoke(field.get(null));
}
catch (Throwable e) {
e.printStackTrace();
}
}
注意一點(diǎn):
andriod 9.0不能防偽標(biāo)非SDK接口中的限制接口,但是
以上接口并不屬于限制接口骂因,可以正常訪問
方法二
在自定義UncaughtExceptionHandler中不處理改異常炎咖,這樣因?yàn)樵摦惓Mǔ0l(fā)生在應(yīng)用在后臺(tái)的時(shí)候,且該異常又沒被處理,所以用戶感知不到該異常乘盼。
override fun uncaughtException(t: Thread, e: Throwable) {
if(e is TimeoutException){
return
}
mDefaultHandler?.uncaughtException(t,e)
}