一
相信大家都有聽過,子線程更新UI的操作。但這種說法,不是很明確。有些人說子線程更新UI會掛,而有些人說子線程可以更新UI。接下來分析下這兩種情況。
二
先來說說子線程更新UI會掛的問題吧。
在Activity中onCreate完后,會生成一個(gè)ViewRootImpl,View的繪制都是同個(gè)它來實(shí)現(xiàn)的,而ViewRootImpl調(diào)用到requestLayout()來完成View的繪制操作??聪略创a:
//ViewRootImpl.java
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
但布局繪制或者發(fā)生變化時(shí),會調(diào)用requestLayout(),而里面有checkThread(),來看下它的源碼:
//ViewRootImpl.java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
其中mThread是在ViewRootImpl創(chuàng)建時(shí)初始化的,把創(chuàng)建時(shí)的線程和mThread綁定,而ViewRootImpl又是在主線程初始化的,所以mThread表示主線程。假如更新UI,會調(diào)用requestLayout() -> checkThread() -> mThread != Thread.currentThread(),判斷是否在主線程,如果在子線程中,會拋異常,所以子線程更新UI才會掛。
三
子線程更新UI會掛的思路明確了,再來看看子線程更新UI為什么不會掛吧。
剛才講到了,在Activity中onCreate完后,會生成一個(gè)ViewRootImpl,那么之后它就會去檢查你更新UI時(shí)在哪個(gè)線程。那假如我在onCreate時(shí)去開一個(gè)子線程更新UI,此時(shí)ViewRootImpl還沒創(chuàng)建,就不會去檢測UI變化,所以在onCreate中子線程是可以更新UI的。
那又有些人說,我不在onCreate里面更新,我就在onCreate后在子線程里面去更新,它還是不會掛,這又是為什么呢?主要還是看下更新UI的方法吧
text1.setOnClickListener {
thread {
it.post {
text1.text = "change"
}
}
}
這里用一個(gè)點(diǎn)擊事件,開子線程更新UI,運(yùn)行后發(fā)現(xiàn)沒掛,這是為什么?看下post的源碼:
//View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
通過源碼分析,你就知道這個(gè)mHandler是在ViewRootImpl里面賦值的,mHandler是主線程的Handler,而又掉了handler.post(),所以只要Handler在主線程,那么它post的所有的UI操作都是主線程。看起來像在子線程,實(shí)際是回到主線程更新UI。
繼續(xù)看這種
class MyTestActivity:AppCompatActivity() {
private val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.ab)
}
override fun onResume() {
super.onResume()
getHandler()
}
private fun getHandler(){
thread {
handler.post {
text0.text = "change"
}
}
}
}
由于創(chuàng)建handler時(shí)是在主線程,所以這個(gè)handler是屬于主線程的,所以其他的步驟就更上面的一樣了。另一種handler的發(fā)消息更新的方式我就不寫出來了,只要通過handler更新UI的,只要handler是主線程的,必定不會掛。之前有寫過Handler使用的文章,有興趣的同學(xué)可以看看~
還有一種就是調(diào)用runOnUiThread{}
class MyTestActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.ab)
}
override fun onResume() {
super.onResume()
thread()
}
private fun thread(){
thread {
runOnUiThread {
text.text = "change"
}
}
}
}
顧名思義,在主線程運(yùn)行,只要調(diào)了這個(gè)方法,所有操作都在主線程里面,所有只要的操作不會掛。
總結(jié)一點(diǎn),并不是說子線程可以更新,仔細(xì)點(diǎn)說:子線程中可以在調(diào)用主線程的Handler去更新UI,或者子線程可以調(diào)用runOnUIThread{}切換到主線程更新UI。
四
之前還遇到一種特殊情況,在onResume中更新UI不會掛,要是在最后一行加上 view.requestLayout()
@Override
protected void onResume() {
super.onResume();
new Thread() {
@Override
public void run() {
super.run();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
TextView view = findView(R.id.tv_content);
view.setText("我在子線程更新");
view.setBackgroundColor(Color.RED);
view.requestLayout();
}
}.start();
}
再試一下,就崩了
總結(jié)一點(diǎn):實(shí)際上,就是只要你改view,不觸發(fā)checkThread()就沒事,而TextView的寬高不改變,也不會去觸發(fā)requestLayout(),修改背景也同樣。不會觸發(fā)view的位置大小改變。當(dāng)然,這種情況,不是每個(gè)版本的android都有用,還是要規(guī)范的去主線程更新UI。