Android Toast:除了主線程之外,子線程中慎用,你可能遇到過Toast不彈出(不管用)的問題(和Looper搭配的方式也要慎重使用)

Android開發中我們會經常用到Toast來在界面上打印提示信息,如果是在調試程序的時候,使用它打印一些中間過程的結果顯然比使用Log更直觀一些,因爲我們運行程序時可以直接從設備中看到彈出來的結果,而Log打印的內容還要通過控制檯去找。但是估計也有一些人遇到過一些問題,比如Toast沒有彈出來,好像沒有起作用,或者直接會報異常。一般大家應該也知道,很多情況下Toast主要還是在主線程中去使用,直接在子線程中去使用的時候會報異常:Can't toast on a thread that has not called Looper.prepare()。但是如果必須要在子線程中去使用,該怎麼做?最開始我是通過下面圖1.的方式去在子線程中使用Toast:

圖1.

(如果使用這種方式,上面的代碼邏輯當然可以自行優化,這裏只舉個簡單例子)

上面這種方式也就是封裝一下原生的Toast,通過Handler將Toast的調用post到主線程去,注意:這裏創建Handler實例的時候一定要傳入上述圖中的參數--Looper.getMainLooper()才行。可以驗證,這種方式是可以直接在線程中彈出Toast的,其實這和我們平時在子線程中通過handler將更新UI的操作發送到主線程是一個意思。

後來,我在網上又看到下面圖2的封裝方式:

圖2.

然後你在子線程中去調用這個封裝好的Toast,可以發現也是可以正常彈出的。而且,從代碼上看,似乎這種方式比圖1中的高級一些:首先,前面說了,Toast在子線程中直接使用時有個異常:Can't toast on a thread that has not called Looper.prepare(),這裏就針對這個問題自己調用了prepare還有loop兩個方法,然後在這兩個方法中間調用Toast的相關方法就OK了,平時我們可很少直接使用這兩個方法吧?是不是感覺技術含量高一點?其次,因爲前面說了圖1其實就是將顯示Toast內容的邏輯發送到主線程的消息隊列中的,而圖2這確實就是在當前子線程中執行,這種方式似乎還可以給主線程減少負擔,畢竟主線程要處理的事太多,動不動就給人家找點事,指望着這點事也得讓主線程自己去做,不利於發揮多線程的優勢。講真,我是感覺就圖1這點事對主線程的影響真的可以忽略不計了😂[笑哭.png]

但是,你可能很快就會發現問題,調用這個封裝的Toast能在子線程中彈出來是沒問題的,但是調用這個封裝的Toast之後的邏輯似乎沒有執行?沒錯,就是沒有執行。爲什麼?再去看一下這個封裝好的Toast方法代碼,是不是有Looper.loop()的調用?問題想必說到這也清楚了:loop方法內部是一個for(;;)的無限循環,調用了這個方法之後,循環無法結束,它後邊的代碼當然得不到執行。然後你可能會問:上面不是也調用了quit方法?答案是自己去看一下這個quit的源碼吧,這裏不貼出來了。主要的意思就是這個quit方法裏面就是清空了消息隊列,沒有結束這個無限循環,並不是你想的那樣退出了這個loop......

總之,在子線程中通過自己調用Looper.prepare以及Looper.loop去使用Toast的話,本身可以,並且其他的主線程中的操作也可以放在這裏(我沒去驗證),但是如果上面那樣,這個loop之後還有其他的邏輯,那麼這後面的就不能執行了。所以還是建議用圖1中的方式去封裝Toast,然後再去子線程中調用,如果不是子線程就不必進行這樣的封裝了。是否還有更好的其他方式,以及圖2是否可以改動以後就行了?等着你給我答案,目前我沒時間去探索[壞笑.jpg]

下面再說Toast可能沒有彈出,而且"沒有"(注意這裏的引號)異常出現的情況:

你直接在某個地方使用原生的方式---Toast.makeText(context, content, Toast.LENGTH_LONG).show(),發現並沒有Toast彈出,也沒有像有的時候那樣直接程序崩潰退出,這是怎麼回事?這種情況基本可以說是你在子線程中直接使用了Toast,並且這個使用是在try...catch塊中。因爲本身直接在子線程中使用Toast就會像上面說的那樣報異常,但是你如果這個Toast在try...catch塊中,那麼這個異常就被捕獲了,也就沒有崩潰,而直接到了catch中,所以Toast也沒能彈出...就像下邊這樣的邏輯

圖3.

如果你能看一下控制檯的log,細心的話就能看到下圖中的這樣:

圖4。

也就是代碼邏輯從try中遇到Toast.make...開始後,異常就被catch捕獲了,Toast根本沒正常執行。

其實,最難的是你可能之前並不知道你的這個Toast是在子線程中使用並且被try...catch包圍,因爲你可能看到的是在Toast附近並沒有try...catch包圍它,也沒有開子線程,但是這只是它的周圍沒有,如果是包含這個Toast的方法是在子線程中調用,而且是包含在try...catch中呢?是不是間接的滿足了上面的條件,一樣的意思?或者更外層的調用是在子線程中並且被包含在try...catch中呢?不能只看局部這一點吧?比如下圖5的簡單例子:test1方法中直接使用Toast,緊接着test1被test2調用,test3中又調用了test2,並且加了try...catch,直到最後第4步這裏開了子線程去調用test3...此時上邊說的兩種條件都具備了,結果最終就是在控制檯logcat那裏看到了類似於圖4的打印。所以,如果只看Toast所在的直接代碼塊顯然是分析不出來問題的。

圖5.

怎麼去確定Toast當前所在的線程是不是子線程?用Log打印一下這個Toast當前所在的線程id不就知道是不是在子線程中了嗎

總結:如果你對你當前要寫Toast的地方的代碼不熟,不確定你要寫Toast的地方是否位於子線程中並且被try...catch包圍,那麼一定要用Toast的話,就用圖1的封裝方式吧。

最後,希望這篇文章裏的一些內容沒有誤導人,還有就是希望有更好的解決問題方式分享出來。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章