崩潰詳情
嘗試復(fù)現(xiàn)
- 通過崩潰信息從網(wǎng)上找到的一些論述著洼,發(fā)現(xiàn)這個問題是因?yàn)橹骶€程被阻塞了樟遣,而 Toast 沒有及時銷毀導(dǎo)致的,那么接下來讓我們對它進(jìn)行復(fù)現(xiàn)
為什么出現(xiàn)這個問題身笤,是因?yàn)?Toast 的顯示是通過 Handler.sendMessage豹悬,所以這個操作是異步的,而 Thread.sleep 會阻塞主線程液荸,從而導(dǎo)致 Handler.handleMessage 在接收到消息的時候 WindowToken 已經(jīng)失效了
經(jīng)過實(shí)際的測試:如果是短吐司瞻佛,sleep 2000 毫秒的時候還是會拋出異常,sleep 1500 毫秒則不會發(fā)生異常娇钱;如果是長吐司伤柄,sleep 3500 毫秒的時候也是會拋出異常,sleep 3000 毫秒的時候就不會發(fā)生異常
由此可見忍弛,WindowToken 失效的時間是跟 Toast 的顯示時長有關(guān)响迂,如果是短吐司,那么 WindowToken 有效時長只能在 2 秒以內(nèi)细疚;如果是長吐司蔗彤,那么 WindowToken 的有效時長只能在 3.5 秒以內(nèi)
- 然后再通過 WindowManager.addView 的時候,它會對 WindowToken 例行檢查疯兼,如果是失效狀態(tài)則會拋出異常給上層然遏,而這個機(jī)制恰好是 Android 7.1 的時候才有的,谷歌那個時候并沒有考慮到對 Toast 的一些處理吧彪。因?yàn)橥ㄟ^瀏覽 Android 7.0 和 Android 6.0 的源碼待侵,發(fā)現(xiàn)谷歌也是沒有進(jìn)行 try 處理,但是崩潰的機(jī)型卻全是清一色的 Android 7.1
問題排查
- 通過查看這個崩潰都是在 Android 7.1 的機(jī)型才會出現(xiàn)姨裸,那么我們可以對比 Android 7.1 的源碼和 Android 8.0 看看
通過追蹤不同 API 等級的源碼秧倾,發(fā)現(xiàn)這個問題在 Android 8.0 上面已經(jīng)被被修復(fù)了
通過查看 Toast 的源碼,發(fā)現(xiàn) Toast 其實(shí)就是一個 WindowManager傀缩,并且通過 Handler 來顯示和隱藏那先。
而產(chǎn)生崩潰的地方是在 handleShow 方法里面
- 而 handleShow 方法是被 Toast 中的名為 TN 靜態(tài)內(nèi)部類中的 Handler 對象調(diào)用
進(jìn)行修復(fù)
- 那么解決這一問題的方式的思路是,將這個 Handler 對象通過反射獲取到赡艰,然后使用靜態(tài)代理的方式對它進(jìn)行回調(diào)并對進(jìn)行捕獲異常
最后經(jīng)過驗(yàn)證售淡,是 OK 的,已經(jīng)沒有崩潰的問題出現(xiàn)了。
但是新的問題又出現(xiàn)了揖闸,我們以前寫 Toast 是這樣的
Toast.makeText(this, "666", Toast.LENGTH_LONG).show();
- 但是如果為了修復(fù)這個崩潰問題揍堕,我們需要這樣寫
Toast toast = Toast.makeText(this, "666", Toast.LENGTH_LONG);
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
try {
// 獲取 mTN 字段對象
Field mTNField = Toast.class.getDeclaredField("mTN");
mTNField.setAccessible(true);
Object mTN = mTNField.get(toast);
// 獲取 mTN 中的 mHandler 字段對象
Field mHandlerField = mTNField.getType().getDeclaredField("mHandler");
mHandlerField.setAccessible(true);
final Handler mHandler = (Handler) mHandlerField.get(mTN);
// 偷梁換柱
mHandlerField.set(mTN, new Handler() {
@Override
public void handleMessage(Message msg) {
// 捕獲這個異常,避免程序崩潰
try {
mHandler.handleMessage(msg);
} catch (WindowManager.BadTokenException ignored) {}
}
});
} catch (IllegalAccessException | NoSuchFieldException ignored) {}
}
toast.show();
這樣寫感覺心好累汤纸,不想這樣寫衩茸,有沒有一種方式可以一勞永逸?
答案當(dāng)然是有了蹲嚣,使用第三方 Toast 封裝的框架:https://github.com/getActivity/Toaster递瑰,框架內(nèi)部已經(jīng)處理了這個問題,調(diào)用者無需關(guān)心此問題隙畜。
使用框架后抖部,可以這么寫
Toaster.show("666");
- 還是一句代碼,就問你 6 不 6
問題總結(jié)
問題描述:Toast 在主線程阻塞情況下會導(dǎo)致 WindowToken 失效议惰,從而導(dǎo)致應(yīng)用崩潰
涉及范圍:所有 Android 版本為 7.1 的用戶慎颗,并且項(xiàng)目中使用了原生 Toast 的地方都有可能觸發(fā)崩潰
解決方案:不直接使用原生 Toast,而使用第三方 Toast 框架