子線程不能更新UI已經是一個常識了,如果兩個線程同時更新UI,可能對同一個控件操作造成混亂,而更新UI涉及到整棵View樹的遍歷,加鎖又影響效率,索性在ViewRootIlmpl類中設置一個checkThread()方法,檢測當前線程和創建View的線程是否是同一個,如果不是直接Throw Exception,這就是不能在子線程更新UI的原理。但是實測發現在某些情況下可以在子線程中更新UI。
一、在onCreate中更新UI
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.text);
new Thread(new Runnable() {
@Override
public void run() {
textView.setText("apple");
}
}).start();
}
這個例子網上舉的很多了,因爲onCreate回調可能發生在ViewRootImpl創建之前,在textView.setText中的mLayout爲null,所以此時是不會深入到ViewRootImpl裏執行的,這個時候textView甚至沒有顯示出來,所以不需要調用onDraw重繪,只是單純改變一個變量而已。
if (mLayout != null) {
checkForRelayout();
}
二、不改變大小更新UI
實測在我的小米手機上不改變當前控件大小隻改變內容,可以正常更新不會閃退。這個情況網上目前只看到一篇文章提到,但是沒有找到正確的原因。根據網上的傳統分析,不改變大小時,checkForReLayout只會調用invalidate(改變大小會同時調用requestLayout和invalidate),invalidate的調用過程百度“invalidate源碼”可以看到很多分析,簡單說就是不斷調用父view的invalidateChildParent,直到到達ViewRootImpl的invalidateChildParent,ViewRootImpl的invalidateChildParent,會執行checkThread()檢測線程,從而閃退。然而在我的小米手機上並不會調用父view的invalidateChildParent方法。關鍵代碼和調試截圖如下:
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
onDescendantInvalidated(child, child);
return;
}
在檢測到硬件加速位mHardwareAccelerated爲true後會不斷調用父view的onDescendantInvalidated,從而避開了網上源碼分析中各級view對Dirty區域的反覆檢測處理(我也沒理解爲什麼要反覆檢測處理),直接到達ViewRootImpl的onDescendantInvalidated()方法,而該方法並沒有檢查線程這一步,所以在子線程中可以正常更新UI。希望這篇文章可以提示一點同樣遇到此問題的人,但是還不知道這個mHardwareAccelerated從何而來,硬件加速指的又是什麼。