View

//无异常,但是onResume后子线程更新ui会异常
@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = findViewById(R.id.tv);
        new Thread(new Runnable() {
            @Override
            public void run() {
                tv.setText("Test");
            }
        }).start();
}

为什么最开始的在onCreate()里子线程对UI的操作没有报错呢?是因为ViewRootImp此时还没有创建,还未进行当前线程的判断; 现在,我们寻找ViewRootImp在何时创建,从Activity启动过程中寻找源码,通过分析可以查看ActivityThread.handleResumeActivity还未完成Activity的onResume

主线程更新UI的方法

Handler(mainLooper).post{
    tv.setText()
}

runOnUiThread {
    tv.setText()
}

view.post{
    tv.setText()
}


//AsyncTask也可,onPreExecute,onProgressUpdate,onCancelled,onPostExecute都可以,都有@MainThread标志

View 绘制流程

三级绘制流程

View的MeasureSpec。MeasureSpec是一个32位的int值,其中高2位表示测量模式(Mode),低30位表示测量大小(Size)。它用于在测量过程中将父容器的约束传递给子View。

三种模式:

EXACTLY(精确模式):对应数值为0。表示父容器已经确定了子View的确切大小。在这种情况下,子View的测量大小即为MeasureSpec中指定的Size值。对应布局参数中的具体数值(如100dp)或MATCH_PARENT。

AT_MOST(最大模式):对应数值为-2147483648(即0x80000000,二进制最高两位为10)。表示子View不能超过MeasureSpec中指定的Size值,具体大小需要根据子View的内容来计算。对应布局参数中的WRAP_CONTENT。

UNSPECIFIED(未指定模式):对应数值为1073741824(即0x40000000,二进制最高两位为01)。表示父容器对子View没有任何限制,子View可以想要多大就多大。通常用于系统内部,例如ScrollView测量子View时。

在自定义View时,我们通常需要根据MeasureSpec来测量并设置View的宽高。一般的处理方式如下:

注意:上述代码只是一个示例,实际处理时还需要考虑padding、margin等因素。

另外,在自定义ViewGroup时,我们需要测量每个子View,并根据子View的测量结果来设置自己的大小。这时需要调用每个子View的measure方法,传入根据当前ViewGroup的MeasureSpec和子View的LayoutParams计算得到的子View的MeasureSpec。

计算子View的MeasureSpec的规则通常如下(以宽度为例,高度同理):

如果子View的LayoutParams.width是具体数值,则子View的宽度MeasureSpec为EXACTLY,大小为该数值。

如果子View的LayoutParams.width是MATCH_PARENT,则:

如果子View的LayoutParams.width是WRAP_CONTENT,则:

三种模式详解

1. EXACTLY(精确模式)

模式值0(对应常量 MeasureSpec.EXACTLY

含义:父容器已确定了子 View 的确切尺寸,子 View 必须使用这个大小

对应场景

处理方式:直接使用 MeasureSpec 中的 size 作为最终尺寸

2. AT_MOST(最大模式)

模式值-21474836480x80000000,二进制 10开头)

含义:子 View 不能超过指定尺寸,但可以根据内容需要更小

对应场景:Layout 参数为 wrap_content

处理方式:计算内容所需尺寸,但不超过 MeasureSpec 中的 size

3. UNSPECIFIED(未指定模式)

模式值10737418240x40000000,二进制 01开头)

含义:父容器对子 View 无任何限制,子 View 可以任意大小

对应场景

处理方式:使用 View 自身需要的大小

核心计算规则

1. MeasureSpec 的生成(父容器决定子 View 的约束)

这是最重要的规则,决定了父容器如何根据自身约束和子 View 的 LayoutParams 生成子 View 的 MeasureSpec:

2. 自定义 View 中的测量处理

在自定义 View 的 onMeasure()中,需要根据 MeasureSpec 计算并设置自己的尺寸:

3. 实际测量示例

核心区别概览

特性

invalidate()

requestLayout()

触发目的

请求重绘(重新绘制内容)

请求重新测量和布局

调用流程

invalidate()onDraw()

requestLayout()onMeasure()onLayout()→ (可能) onDraw()

影响范围

当前 View 及其子 View 的绘制区域

当前 View 到 ViewRootImpl 的整条路径

性能开销

相对较小(只重绘)

较大(涉及测量、布局)

常见使用场景

内容变化但尺寸不变

尺寸或位置变化

详细解析

1. invalidate()- 请求重绘

触发条件:当 View 的内容发生变化,但尺寸和位置不变时调用。

调用流程

特点

只触发绘制流程,不触发测量和布局

可指定脏区域(invalidate(Rect))进行局部重绘

多次调用会被合并,在下一帧统一处理

适用于:动画、文本更新、颜色变化等

2. requestLayout()- 请求重新布局

触发条件:当 View 的尺寸或位置需要改变时调用。

调用流程

特点

触发完整的测量和布局流程

会遍历整个 View 树,从当前 View 到 ViewRootImpl

如果尺寸确实改变了,通常会自动调用 invalidate()

适用于:动态改变 View 大小、修改 LayoutParams 等

实际调用链分析

源码层面分析

invalidate()的关键实现

requestLayout()的关键实现

组合使用场景

场景1:先改变尺寸,再改变内容

场景2:自定义View中

Last updated