区别与联系
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()
方法是在主线程中调用的。