Android UI 線程更新UI也會(huì)崩潰?

繼續(xù)上一篇的Android可不可以在子線程中更新UI?http://m.itdecent.cn/p/99a6aeba1161

今天再繼續(xù)深入了解一個(gè)話題Android UI 線程更新UI是否也會(huì)崩潰?

我們先從一個(gè)小需求開始說起:

點(diǎn)擊一個(gè)按鈕,請(qǐng)求服務(wù)端獲取一些文本信息在客戶端用Dialog展示出來,然后再與對(duì)話框一些交互操作。

下面貼一下代碼:



代碼很簡(jiǎn)單,就是點(diǎn)擊按鈕,新啟動(dòng)一個(gè)線程去模擬網(wǎng)絡(luò)請(qǐng)求,結(jié)果拿到后,把問題展示在Dialog。運(yùn)行完畢,點(diǎn)擊按鈕直接崩潰:

說明在子線程執(zhí)行Dialog崩潰:

java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-3,5,main] that has not called Looper.prepare()

這個(gè)有點(diǎn)經(jīng)驗(yàn)的安卓開發(fā)都知道,在子線程彈Dialog,需要初始化Looper,和啟動(dòng)Looper循環(huán)。

此時(shí)的運(yùn)行效果是這樣的,沒有發(fā)生崩潰了。

下面開始引出我們的問題,仔細(xì)發(fā)現(xiàn)


箭頭所指向的mTvTitle.setText(title);是在子線程更新title,居然一點(diǎn)也沒報(bào)錯(cuò)。

boolean isUiThread=Looper.getMainLooper()==Looper.myLooper();? 這句判斷是否在主線程日志中也打印的是false,另外也可以看PID19559-后面的數(shù)字是否和PID相等,不想等的話也是子線程

2022-08-06 01:15:14.190 19559-19689/? D/tanlin: false。

如果說是按照前篇文章所描述的那樣 ViewRootImpl還沒創(chuàng)建的話,這個(gè)時(shí)候肯定是創(chuàng)建的了我認(rèn)為,我們是延遲一秒再更新的title。那么是什么原因?qū)е聸]有報(bào)錯(cuò)呢?

接下來我添加一段代碼邏輯,就是點(diǎn)擊不是的時(shí)候,我就把文案在以前的基礎(chǔ)上加上"猶豫中?"

看看效果。

效果也是正常的。

雖然此時(shí)沒有崩潰,也沒有任何錯(cuò)誤日志,但我還是相信UI只能在UI線程更新以前一直這么認(rèn)為的。

當(dāng)我把它放在主線程中去更新時(shí),我點(diǎn)擊不是的時(shí)候點(diǎn)了三次結(jié)果反而崩潰了。

這就是回到了今天開始的主題:Android UI 線程更新UI也會(huì)崩潰:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

我們都很熟悉了這個(gè)錯(cuò)誤。而且是我點(diǎn)了三下才崩潰的,沒加上handler.post還是好好的。

現(xiàn)在我們來仔細(xì)分析這個(gè)錯(cuò)誤:打開安卓的framework源碼搜


從錯(cuò)誤可以推斷出mThread != Thread.currentThread()的,那么我們來看看mThread是在哪賦值的,

是在ViewRootImpl構(gòu)造的時(shí)候賦值的,賦值的就是當(dāng)前的Thread對(duì)象。

也就是說,你ViewRootImpl是在哪個(gè)線程創(chuàng)建的,你后續(xù)的UI更新就需要在哪個(gè)線程執(zhí)行,跟是不是UI線程毫無關(guān)系。

那么我們的Dialog其實(shí)是在子線程中創(chuàng)建的,Dialog的ViewRootImpl也就是在子線程中創(chuàng)建的,那么后續(xù)Dialog的更新也需要在子線程中。所以導(dǎo)致后續(xù)Dialog更新,執(zhí)行到ViewRootImpl#checkThread的時(shí)候,都在子線程才可以。這就是說明了剛才我加上handler.post到UI線程卻崩潰了,

再來想想,為什么我點(diǎn)第三次才崩潰,前兩次都沒有呢?它們有啥區(qū)別,是觸發(fā)了什么事件嗎?下面這張圖是點(diǎn)擊兩次的時(shí)候,當(dāng)再點(diǎn)一次就崩潰了。


想想第三次和第二次的TextView高度不一樣了改變了。調(diào)用setTextView時(shí)高度發(fā)生改變了,就會(huì)觸發(fā)了requestLayout。(補(bǔ)充一點(diǎn):這里我寬度是match_parent。如果改為wrap_parent。點(diǎn)擊一次也會(huì)崩潰。寬度改變了也會(huì)觸發(fā)requesLayout)

所以當(dāng)你的TextView觸發(fā)requestLayout,會(huì)輾轉(zhuǎn)到ViewRootImpl的requestLayout,然后再找到它的checkThread。而checkThread判斷的mThread和當(dāng)前線程對(duì)比。

具體TextView的寬高改變觸發(fā)requestLayout時(shí)機(jī),下次寫篇文章源碼繼續(xù)分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容