安卓子線程更新UI不閃退的問題

子線程不能更新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從何而來,硬件加速指的又是什麼。

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