區別與聯繫
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()
方法是在主線程中調用的。