從實例和源碼角度理解 postInvalidate() 和 invalidate() 的區別與聯繫

區別與聯繫

postInvalidate() 方法在非 UI 線程中調用,通知 UI 線程重繪。
invalidate() 方法在 UI 線程中調用,重繪當前 UI。

使用情景

近期在對 View 溫故而知新的學習過程中,看到一個 postInvalidate() 方法,讓我很好奇,這個方法與 invalidate() 方法有什麼區別和聯繫呢?讓我們假設一個場景,當前有一個自定義的 Button 如下 ——

public class TestButton extends AppCompatButton {
    private int tag = -1;

    public TestButton(Context context) {
        super(context);
    }

    public TestButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TestButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (++tag > 0) {
            setBackgroundColor(Color.GREEN);
        }
    }
}

這個 Button 的邏輯很簡單,當初始化加載完了之後 tag 的值應該爲0,也就是說如果我們調用它的 onDraw() 方法的話,那麼這個 Button 的背景色就會被設成綠色的。再來假設一個限定,我們現在只能在子線程中重繪這個 Button。子線程?很多小夥伴的第一想法就是 Handler 啦,啪啪啪敲完鍵盤寫下如下代碼:

    public class MainActivity extends AppCompatActivity {
    private TestButton mTestButton;
    private Handler mHandler = new InnerHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTestButton = (TestButton) findViewById(R.id.btn_test);

        mTestButton.setOnClickListener(v -> new Thread(() -> mHandler.sendEmptyMessage(0x123)).start());
    }

    private void handleMessage() {
        mTestButton.invalidate();
    }

    private static class InnerHandler extends Handler {
        private WeakReference<MainActivity> mActivity;

        private InnerHandler(MainActivity activity) {
            mActivity = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = mActivity.get();
            switch (msg.what) {
                case 0x123:
                    if (activity != null) {
                        activity.handleMessage();
                    }
                    break;
                default:
                    break;
            }
        }
    }
}

這可能是大部分小夥伴的選擇,那麼它的思路是什麼樣的呢?我們在點擊事件中創建一個子線程模擬我們的業務需求,然後再子線程調用主線程的 Handler 發送一個 0x123 消息,然後 Hanlder 在主線程收到了這個消息,調用了 MainActivity 的 handleMessage() 方法,也就是我們自定義 TestButton 的 invalidate() 來重繪我們的 Button,效果完美 ——

這裏寫圖片描述

這裏寫圖片描述

爲什麼我們需要通過 Handler 來通知 UI 線程重繪?因爲我們大家都知道,在 Android 中通過非 UI 線程更新 UI 是不可取的,我們不可以通過子線程來更新 UI,所以我們就藉助線程間通信,讓主線程調用相應 View 的 invalidate() 方法來更新 UI。那可不可以不通過線程間通信,直接在子線程通知 View 進行重繪?有!就是通過 postInvalidate()(實際上 postInvalidate() 底層的實現還是通過 Hanlder 的,但是底層封裝起來了,讓我們直接可以在子線程調用)。代碼如下 ——

public class MainActivity extends AppCompatActivity {
    private TestButton mTestButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTestButton = (TestButton) findViewById(R.id.btn_test);

        mTestButton.setOnClickListener(v -> new Thread(mTestButton::postInvalidate).start());
    }
}

代碼瞬間清晰明瞭,讓我們來看看它的實現思路 —— 在點擊事件中創建一個子線程,這個沒有問題,和之前的一樣,然後直接調用了 TestButton 的 postInvalidate() 方法就可以了!這感覺太簡單了,下面我們就一層層地剝開它神祕的面紗 ——

源碼解析

首先打開 postInvalidate() 源碼 ——

這裏寫圖片描述

我們可以看到類的解釋 —— 在下一個事件循環中通知重繪。在非 UI 線程中使用它去重繪。

我們繼續跟蹤下去,最後就會進入 ViewRootImpl 類中的 dispatchInvalidateDelayed() 方法——

這裏寫圖片描述

看到這裏我們似乎看到了很熟悉的東西,它其實就是取出一個消息對象,給它的 what 字段賦上 MSG_INVALIDATE 值,給它的 Object 字段附上傳入的 View 的引用。然後通過 Handler 發送這個消息,那麼我們下一步就是應該來看看這個 Handler 是如何處理消息的了,這個 Handler 實質上是 ViewRootHandler 的一個實例化對象,而 ViewRootHandler 是 ViewRootImpl 的一個內部類,我們來看看它的 handleMessage() 方法源碼 ——

這裏寫圖片描述

清晰了!先通過 msg.what 字段查找到該分支,然後通過 msg.obj 獲取到我們之前賦給的 View 引用,然後調用它的 invalidate() 方法就好了!當然,我們這裏需要注意的一點是,ViewRootImpl 是在主線程中被調用的,所以它的 Handler 的 handleMessage() 方法是在主線程中調用的。

發佈了41 篇原創文章 · 獲贊 81 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章