是不是有一絲的郁悶?
沒關(guān)系,作為擁有多年經(jīng)驗(yàn)的老鳥,總能立馬想到解釋的理由:
大家都知道在Activity#onCreate的時(shí)候,我們開個(gè)線程去執(zhí)行Text#setText也不會(huì)崩潰,原因是ViewRootImpl那時(shí)候還沒初始化,所以這次沒崩潰也是一個(gè)原因。
對(duì)應(yīng)源碼解釋是這樣的:
Dialog源碼
public void show() {
// 省略一堆代碼
mWindowManager.addView(mDecor, l);
}
我們首次創(chuàng)建的Dialog,第一次調(diào)用show方法,內(nèi)部確實(shí)會(huì)執(zhí)行mWindowManager.addView,這個(gè)代碼會(huì)執(zhí)行到:
WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
這個(gè)mGlobal對(duì)象是WindowManagerGlobal,我們看它的addView方法:
WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 省略了一堆代碼
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
果然立馬有new ViewRootImpl的代碼,你看ViewRootImpl沒有創(chuàng)建,所以這和Activity那個(gè)是一個(gè)情況。
好像有那么點(diǎn)道理哈…
我們繼續(xù)往下看。
應(yīng)屆小哥要繼續(xù)做需求了。
一個(gè)隱藏的問題
接下來的需求很奇怪,就是當(dāng)詢問"鴻洋帥氣嗎?"的時(shí)候,如果你點(diǎn)擊不是,那么Dialog不消失,在問題的末尾再加一個(gè)?號(hào),如此循環(huán),永不關(guān)閉。
這難不倒我們的小哥:
mBtnNo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String s = mTvTitle.getText().toString();
mTvTitle.setText(s+“?”);
}
});
運(yùn)行效果:
很完美。
如果我問,你覺得這個(gè)代碼有問題嗎?
你往上看了幾眼,就這兩行代碼有個(gè)雞兒?jiǎn)栴},可能有空指針?
當(dāng)然不是。
我稍微修改一下代碼:
mBtnNo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String s = mTvTitle.getText().toString();
mTvTitle.setText(s+“?”);
boolean uiThread = Looper.myLooper() == Looper.getMainLooper();
Toast.makeText(getContext(),"Ui thread = " + uiThread , Toast.LENGTH_LONG).show();
}
});
每次點(diǎn)擊的時(shí)候,我彈了個(gè)Toast,輸出當(dāng)前線程是不是UI線程。
看下效果:
發(fā)現(xiàn)問題了嗎?
出乎自己的意料嗎?
我們?cè)诜荱I線程一直在更新TextView的text。
這個(gè)時(shí)候,你不能跟我扯什么ViewRootImpl還沒有創(chuàng)建了吧?
別急…
還有更刺激的。
更刺激的事情
我再改一下代碼:
private Handler sUiHandler = new Handler(Looper.getMainLooper());
public QuestionDialog(@NonNull Context context) {
super(context);
setContentView(R.layout.dialog_question);
mBtnNo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sUiHandler.post(new Runnable() {
@Override
public void run() {
String s = mTvTitle.getText().toString();
mTvTitle.setText(s+“?”);
}
});
}
});
}
我搞了個(gè)UI線程的handler,然后post一下Runnable,確保我們的TextView#setText在UI線程執(zhí)行,嚴(yán)謹(jǐn)而又優(yōu)雅。
再停一下,以各位多年經(jīng)驗(yàn),這次會(huì)崩潰嗎?
按照我寫博客的套路,這次肯定是演示崩潰呀,不然博客怎么往下寫。
好像是這個(gè)道理…
我們跑一下效果:
[圖片上傳中…(image-bfd5c6-1588405433234-1)]
點(diǎn)擊了幾下,沒崩…
// 配圖:小朋友,你是不是有很多問號(hào)。
作為擁有多年經(jīng)驗(yàn)的老鳥,總能立馬想到解釋的理由:
UI線程更新當(dāng)然不會(huì)崩潰呀(言語(yǔ)中有一絲不自信)。
是嗎?
我們多點(diǎn)擊幾次:
崩潰了…
但是剛才在沒有添加UiHandler.post之前可沒有崩潰喲。
這個(gè)結(jié)果,我都得把代碼露出來了,怕你們說我演你們…
好了,再停一停。
我又要問大家一個(gè)問題了,這次你猜是什么崩潰?
是不是求我別搞你們了,直接揭秘吧。
com.example.testviewrootimpl E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.testviewrootimpl, PID: 18323
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8188)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1421)
at android.view.View.requestLayout(View.java:24434)
at android.view.View.requestLayout(View.java:24434)
at android.view.View.requestLayout(View.java:24434)
at android.view.View.requestLayout(View.java:24434)
at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:380)
at android.view.View.requestLayout(View.java:24434)
at android.widget.TextView.checkForRelayout(TextView.java:9667)
at android.widget.TextView.setText(TextView.java:6261)
at android.widget.TextView.setText(TextView.java:6089)
at android.widget.TextView.setText(TextView.java:6041)
at com.example.testviewrootimpl.QuestionDialog$1$1.run(QuestionDialog.java:38)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7319)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:934)
那個(gè)熟悉的身影回來了:
Only the original thread that created a view hierarchy can touch its views.
但是!
但是!
這次可是在切換到UI線程拋出來的。
對(duì)應(yīng)我開頭的靈魂拷問:
UI線程更新UI就不會(huì)出現(xiàn)上面的錯(cuò)誤了嗎?
是不是在一股懵逼又刺激的感覺中無法自拔…
還有更刺激的事情…嗯,篇幅問題,本篇我們就到這了,更刺激的事情我們下次再寫。
別怕,沒完,我總得告訴你們?yōu)槭裁窗伞?/p>
小做揭秘
其實(shí)這一切的根源都在于我們長(zhǎng)久的一個(gè)錯(cuò)誤的概念。
就是UI線程才能更新UI,這是不對(duì)的,為什么這么說呢?
Only the original thread that created a view hierarchy can touch its views.
這個(gè)異常是在ViewRootImpl里面拋出的對(duì)吧,我們?cè)俅蝸韺徱曇幌逻@段代碼:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
“Only the original thread that created a view hierarchy can touch its views.”);
}
}
其實(shí)就幾行代碼。
我們仔細(xì)看一下,他這個(gè)錯(cuò)誤信息并不是:
Only the UI Thread … 而是 Only the original thread。
對(duì)吧,如果真的想強(qiáng)制為Only the Ui Thread,上面的if語(yǔ)句應(yīng)該寫成:
if(UI Thread== Thread.currentThread()){}
而不是mThread。
根本原因說完了。
我再帶大家看下源碼解析:
這個(gè)mThread是什么?
是ViewRootImpl的成員變量,我們重點(diǎn)應(yīng)該關(guān)注它什么時(shí)候賦值的:
public ViewRootImpl(Context context, Display display) {
mContext = context;
mThread = Thread.currentThread();
}
在ViewRootImpl構(gòu)造的時(shí)候賦值的,賦值的就是當(dāng)前的Thread對(duì)象。
也就是說,你ViewRootImpl在哪個(gè)線程創(chuàng)建的,你后續(xù)的UI更新就需要在哪個(gè)線程執(zhí)行,跟是不是UI線程毫無關(guān)系。
對(duì)應(yīng)到上面的例子,我們中間也有段貼源碼的地方。
恰好說明了:
Dialog的ViewRootImpl,其實(shí)是在執(zhí)行show()方法的時(shí)候創(chuàng)建的,而我們的Dialog的show放在子線程里面,所以導(dǎo)致后續(xù)View更新,執(zhí)行到ViewRootImpl#checkThread的時(shí)候,都在子線程才可以。
這就說明了,為什么我們剛才切到UI線程去執(zhí)行TextView#setText為啥崩了。
這里有個(gè)思考題,注意我們上面演示的時(shí)候,切到UI線程執(zhí)行setText沒有立馬崩潰,而是執(zhí)行了好幾次之后才崩潰的,為什么呢?自己想。
大家可能還有個(gè)一問題:
ViewRootImpl怎么和View關(guān)聯(lián)起來的
其實(shí)我們看報(bào)錯(cuò)堆棧很好找到相關(guān)代碼:
com.example.testviewrootimpl E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.testviewrootimpl, PID: 18323
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8188)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1421)
at android.view.View.requestLayout(View.java:24434)
報(bào)錯(cuò)的堆棧都是由View.requestLayout觸發(fā)到ViewRootImpl的。
我們直接看這個(gè)方法:
public void requestLayout() {
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
注意里面這個(gè)mParent變量,它的類型是ViewParent接口。
見名知意。
我要問你一個(gè)View的mParent是什么,你肯定會(huì)回答是它的父View,也就是個(gè)ViewGroup。
對(duì),沒錯(cuò)。
public abstract class ViewGroup extends View implements ViewParent{}
自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過,也去過華為、OPPO等大廠,18年進(jìn)入阿里一直到現(xiàn)在。
深知大多數(shù)初中級(jí)安卓工程師,想要提升技能,往往是自己摸索成長(zhǎng),但自己不成體系的自學(xué)效果低效又漫長(zhǎng),而且極易碰到天花板技術(shù)停滯不前!
因此收集整理了一份《2024年最新Android移動(dòng)開發(fā)全套學(xué)習(xí)資料》送給大家,初衷也很簡(jiǎn)單,就是希望能夠幫助到想自學(xué)提升又不知道該從何學(xué)起的朋友,同時(shí)減輕大家的負(fù)擔(dān)。
由于文件比較大,這里只是將部分目錄截圖出來,每個(gè)節(jié)點(diǎn)里面都包含大廠面經(jīng)、學(xué)習(xí)筆記、源碼講義、實(shí)戰(zhàn)項(xiàng)目、講解視頻
如果你覺得這些內(nèi)容對(duì)你有幫助,可以添加下面V無償領(lǐng)?。。▊渥ndroid)
學(xué)習(xí)分享
在當(dāng)下這個(gè)信息共享的時(shí)代,很多資源都可以在網(wǎng)絡(luò)上找到,只取決于你愿不愿意找或是找的方法對(duì)不對(duì)了
很多朋友不是沒有資料,大多都是有幾十上百個(gè)G,但是雜亂無章,不知道怎么看從哪看起,甚至是看后就忘
如果大家覺得自己在網(wǎng)上找的資料非常雜亂、不成體系的話,我也分享一套給大家,比較系統(tǒng),我平常自己也會(huì)經(jīng)常研讀。
2021最新上萬(wàn)頁(yè)的大廠面試真題
七大模塊學(xué)習(xí)資料:如NDK模塊開發(fā)、Android框架體系架構(gòu)…
只有系統(tǒng),有方向的學(xué)習(xí),才能在段時(shí)間內(nèi)迅速提高自己的技術(shù)。文章來源:http://www.zghlxwxcb.cn/news/detail-842892.html
這份體系學(xué)習(xí)筆記,適應(yīng)人群:
**第一,**學(xué)習(xí)知識(shí)比較碎片化,沒有合理的學(xué)習(xí)路線與進(jìn)階方向。
**第二,**開發(fā)幾年,不知道如何進(jìn)階更進(jìn)一步,比較迷茫。
**第三,**到了合適的年紀(jì),后續(xù)不知道該如何發(fā)展,轉(zhuǎn)型管理,還是加強(qiáng)技術(shù)研究。如果你有需要,我這里恰好有為什么,不來領(lǐng)?。≌f不定能改變你現(xiàn)在的狀態(tài)呢!文章來源地址http://www.zghlxwxcb.cn/news/detail-842892.html
由于文章內(nèi)容比較多,篇幅不允許,部分未展示內(nèi)容以截圖方式展示 。如有需要獲取完整的資料文檔的朋友點(diǎn)擊我的【GitHub】免費(fèi)獲取。
實(shí)戰(zhàn)項(xiàng)目、講解視頻**
如果你覺得這些內(nèi)容對(duì)你有幫助,可以添加下面V無償領(lǐng)?。。▊渥ndroid)
[外鏈圖片轉(zhuǎn)存中…(img-ChnPFSr9-1710762804344)]
學(xué)習(xí)分享
在當(dāng)下這個(gè)信息共享的時(shí)代,很多資源都可以在網(wǎng)絡(luò)上找到,只取決于你愿不愿意找或是找的方法對(duì)不對(duì)了
很多朋友不是沒有資料,大多都是有幾十上百個(gè)G,但是雜亂無章,不知道怎么看從哪看起,甚至是看后就忘
如果大家覺得自己在網(wǎng)上找的資料非常雜亂、不成體系的話,我也分享一套給大家,比較系統(tǒng),我平常自己也會(huì)經(jīng)常研讀。
2021最新上萬(wàn)頁(yè)的大廠面試真題
[外鏈圖片轉(zhuǎn)存中…(img-OlwSx2AG-1710762804345)]
七大模塊學(xué)習(xí)資料:如NDK模塊開發(fā)、Android框架體系架構(gòu)…
[外鏈圖片轉(zhuǎn)存中…(img-ukdG2MtC-1710762804345)]
只有系統(tǒng),有方向的學(xué)習(xí),才能在段時(shí)間內(nèi)迅速提高自己的技術(shù)。
這份體系學(xué)習(xí)筆記,適應(yīng)人群:
**第一,**學(xué)習(xí)知識(shí)比較碎片化,沒有合理的學(xué)習(xí)路線與進(jìn)階方向。
**第二,**開發(fā)幾年,不知道如何進(jìn)階更進(jìn)一步,比較迷茫。
**第三,**到了合適的年紀(jì),后續(xù)不知道該如何發(fā)展,轉(zhuǎn)型管理,還是加強(qiáng)技術(shù)研究。如果你有需要,我這里恰好有為什么,不來領(lǐng)?。≌f不定能改變你現(xiàn)在的狀態(tài)呢!
由于文章內(nèi)容比較多,篇幅不允許,部分未展示內(nèi)容以截圖方式展示 。如有需要獲取完整的資料文檔的朋友點(diǎn)擊我的【GitHub】免費(fèi)獲取。
到了這里,關(guān)于Android UI 線程更新UI也會(huì)崩潰??的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!