从实例和源码角度理解 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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章