Android面试:Invalidate、RequestLayout

Invalidate/RequestLayout 使用场合

结合requestLayout和invalidate与View三大流程关系,有如下图:

总结一下:

1、invalidate调用后只会触发Draw 过程。
2、requestLayout 会触发Measure、Layout过程,如果尺寸发生改变,则会调用invalidate。
3、当涉及View的尺寸、位置变化时使用requestLayout。
4、当仅仅需要重绘时调用invalidate。
5、如果不确定requestLayout 是否触发invalidate,可在requestLayout后继续调用invalidate。

上面仅仅说明了单个布局Invalidate/RequestLayout联系,那么如果父布局调用了invalidate,那么子布局会走重绘过程吗?接下来列举这些关系。

子布局/父布局 Invalidate/RequestLayout 关系

子布局Invalidate
如果是软件绘制或者父布局开启了软件缓存绘制,父布局会走重绘过程(前提是WILL_NOT_DRAW标记没设置)。

子布局RequestLayout
父布局会重走Measure、Layout过程。

父布局Invalidate
如果是软件绘制,则子布局会走重绘过程。

父布局RequestLayout
如果父布局尺寸发生了改变,则会触发子布局Measure过程、Layout过程。

子线程真不能绘制UI吗

在Activity onCreate里创建子线程并展示对话框:

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_group);

        new Thread(new Runnable() {
            @Override
            public void run() {
                TextView textView = new TextView(MainActivity.this);
                textView.setText("hello thread");
                Looper.prepare();
                Dialog dialog = new Dialog(MainActivity.this);
                dialog.setContentView(textView);
                dialog.show();
                Looper.loop();
            }
        }).start();
    }

答案是可以的,接下来分析为什么可以。

在分析ViewRootImpl里requestLayout/invalidate过程中,发现其内部调用了checkThread()方法:

#ViewRootImpl.java
    void checkThread() {
        //当前调用线程与mThread不是同一线程则会抛出异常
        if (mThread != Thread.currentThread()) {
            //简单翻译来说:只有创建了ViewTree的线程才能操作里边的View
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

问题的关键是mThread是什么?从哪里来?

#ViewRootImpl.java
    public ViewRootImpl(Context context, Display display) {
        ...
        //mThread 为Thread类型
        //也就是说哪个线程执行了构造ViewRootImpl对象,那么mThread就是指向那个线程
        mThread = Thread.currentThread();
        ...
    }

而创建ViewRootImpl对象是在调用WindowManager.addView(xx)过程中创建的。
关于WindowManager/Window 请移步:Window/WindowManager 不可不知之事

现在回过头来看Dialog创建就比较明朗了:

1、dialog.show() 调用WindowManager.addView(xx),此时是子线程调用,因此ViewRootImpl对象是在子线程调用的,进而mThread指向子线程。
2、当ViewRootImpl对象构建成功后,调用其setView(xx)方法,里面调用了requestLayout,此时还是子线程。
3、checkThread()判断是同一线程,因此不会抛出异常。

实际上,"子线程不能更新ui" 更合理的表述应为:View只能被构建了ViewTree的线程操作。只是通常来说,Activity 构建ViewTree的线程被称作UI(主)线程,因此才会有上述说法。

postInvalidate 流程

postInvalidate 通过ViewRootImpl 里的handler切换到UI线程,最终执行
invalidate()。
ViewRootImpl 里的hanlder绑定的线程即是UI线程。

猜你喜欢

转载自blog.csdn.net/cpcpcp123/article/details/114761400