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