1. 程式人生 > >從例項和原始碼角度理解 postInvalidate() 和 invalidate() 的區別與聯絡

從例項和原始碼角度理解 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() 方法是在主執行緒中呼叫的。