view draw_view绘制流程

view draw_view绘制流程三大流程触发 View的三大流程依次为measure(测量)—>layout(布局)—>draw(绘制), 结合activity的启动流程,activity对象被创建,然后经过create、start

三大流程触发

View的三大流程依次为measure(测量)—>layout(布局)—>draw(绘制),

结合activity的启动流程,activity对象被创建,然后经过create、start、resume阶段后,在resume中调用wm.addView(decor, l)。该方法中创建了ViewRootImpl对象,并调用ViewRootImpl的setVIew(Decorview)方法,之后调用requestLayout、scheduleTraversals()。

scheduleTraversals()中首先发送了同步屏障消息,然后发送异步消息来出发三个流程,msg的runnable为TraversalRunnable,回调里调用了doTraversal,之后调用了performTraversals,然后又调用了measureHierarchy,最后调到performMeasure,performLayout,performDraw开始了三个流程测量、布局、绘制。

先写个简单的布局,下面会用到

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/layout" android:layout_width="match_parent" android:layout_height="match_parent">

   <FrameLayout android:id="@+id/layout1" android:layout_width="match_parent" android:layout_height="300dp">

       <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World" />
   </FrameLayout>

   <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="我是按钮" />

</FrameLayout>

测量 measure

ViewRootImpl#performMeasure执行流程:DecorView#measure(实际是View#measure)->View#onMeasure(实际是DecorView#onMeasure)->FrameLayout#onMeasure->子View#measure。

重点方法的调用顺序:
基于上面的布局,measure中重要的方法调用如下:

R.id.layout 的measure被执行
R.id.layout 的onMeasure被执行
    R.id.layout1 的measure被执行
    R.id.layout1 的onMeasure被执行
            R.id.text 的measure被执行
            R.id.text 的onMeasure被执行
            R.id.text 的setMeasuredDimension被执行
    R.id.layout1 的setMeasuredDimension被执行
    R.id.button 的measure被执行
    R.id.button 的onMeasure被执行
    R.id.button 的setMeasuredDimension被执行
R.id.layout 的setMeasuredDimension被执行

因为父View需要子View测量后的尺寸来设置自己的尺寸,因此父view的setMeasuredDimension总是比子View的晚。

子View测量规格的计算:
MeasureSpec类由模式和尺寸组成,通过使用二进制,将mode和size打包成一个int值。一个int型有32位,其中31和32两位表示mode,前面30位表示size。 MeasureSpec类用一个变量携带两个数据(size,mode)来减少对象内存分配,并提供了打包和解包的方法,

onMeasure调用child.measure一般如下:
onMeasure -> measureChildWithMargins -> getChildMeasureSpec -> child.measure

关键看getChildMeasureSpec方法,里面的switch-case 和 if-else 可以汇总成下面的表。

横:父View测量模式
纵:子View的LayoutParms
EXACTLY(精确) AT_MOST(至多) UNSPECIFIED (未指明)
具体数值 EXACTLY+childDimension EXACTLY+size EXACTLY+size
match_parent EXACTLY+size AT_MOST+size UNSPECIFIED+0
wrap_content AT_MOST+size AT_MOST+size UNSPECIFIED+0

size为父的测量尺寸-子View的内外边距,与0取最大值

子View得到宽高的MeasureSpec后,会根据自己的特性进行计算,默认的计算方式如下:

getSuggestedMinimumWidth根据最小宽和背景计算,哪个大用哪个,因此当测量模式是UNSPECIFIED,就是根据这个建议的尺寸,另外两个模式时,则使用测量尺寸(见上方表格)

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
    
    
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

布局 layout

ViewRootImpl#performLayout执行流程:DecorView#layout(实际是ViewGroup#layout)->View#layout->DecorView#onLayout->FrameLayout#onLayout。

基于上面的布局,layout中重要的方法调用如下:

R.id.layout 的layout被执行
R.id.layout 的setFrame被执行
R.id.layout 的sizeChange被执行
R.id.layout 的onLayout被执行
    R.id.layout1 的layout被执行
    R.id.layout1 的setFrame被执行
    R.id.layout1 的sizeChange被执行
    R.id.layout1 的onLayout被执行
            R.id.text 的layout被执行
            R.id.text 的setFrame被执行
            R.id.text 的sizeChange被执行
            R.id.text 的onLayout被执行
            R.id.textonLayoutChange()被执行
    R.id.layout1onLayoutChange()被执行
    R.id.button 的layout被执行
    R.id.button 的setFrame被执行
    R.id.button 的sizeChange被执行
    R.id.button 的onLayout被执行
    R.id.buttononLayoutChange()被执行
R.id.layoutonLayoutChange()被执行

layout->setFrame(保存l、t、r、b)->sizeChange(宽高变化)->onLayout->onLayoutChange()

触发onMeasure后会标记PFLAG_LAYOUT_REQUIRED,因此必会触发onLayout。

绘制 draw

ViewRootImpl#performDraw执行流程:DecorView#draw(实际是View#draw)->View#drawBackground画背景->View#onDraw->View#dispatchDraw(实际上子View#draw)->View#onDrawForeground画前景。

performDraw到view的draw还需要判断PFLAG_INVALIDATE,下面会分析。

基于上面的布局,draw中重要的方法调用如下:

R.id.layout 的draw被执行
R.id.layout 的drawBackground被执行
R.id.layout 的onDraw被执行
R.id.layout 的dispatchDraw被执行
   R.id.layout1 的draw被执行
   R.id.layout1 的drawBackground被执行
   R.id.layout1 的onDraw被执行
   R.id.layout1 的dispatchDraw被执行
           R.id.text 的draw被执行
           R.id.text 的drawBackground被执行
           R.id.text 的onDraw被执行
           R.id.text 的dispatchDraw被执行
           R.id.text 的onDrawForeground被执行
   R.id.layout1 的onDrawForeground被执行
   R.id.button 的draw被执行
   R.id.button 的drawBackground被执行
   R.id.button 的onDraw被执行
   R.id.button 的dispatchDraw被执行
   R.id.button 的onDrawForeground被执行
R.id.layout 的onDrawForeground被执行

requestLayout和invalidate

requestLayout

调用某个View的requestLayout,会不断调用parent的requestLayout,最终调到ViewRootImp的requestLayout。

//View#requestLayout
public void requestLayout() {
    ……
    //添加了PFLAG_FORCE_LAYOUT的标记
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    //添加了PFLAG_INVALIDATED的标记
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    ……
}
//ViewRootImp#requestLayout
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
  • 在View的requestLayout方法中,会给View设置PFLAG_FORCE_LAYOUT,这个flag会让View的measure方法中的forceLayout为true,从而触发onMeasure测量。而测量后会设置上PFLAG_LAYOUT_REQUIRED,这个flag会让View触发onLayout。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ……
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    ……
    if (forceLayout || needsLayout) {
        // first clears the measured dimension flag
        ……
            onMeasure(widthMeasureSpec, heightMeasureSpec);
        ……
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
    ……
}

public void layout(int l, int t, int r, int b) {
    ……

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        ……
    }
……
}
  • 在ViewRootImp的requestLayout方法中,设置mLayoutRequested为true,在performTraversals时候会用mLayoutRequested来判断是否调用measureHierarchy,该方法会触发performMeasure、performLayout、performDraw。

performMeasure、performLayout会执行onMeasure和onLayout。而在performDraw内部draw的过程中发现mDirty为空,所以所有View的draw不会被调用。

//ViewRootImp#draw
private boolean draw(boolean fullRedrawNeeded) {
    ……
    //requestLayout时,dirty为empty,无法触发view的draw方法
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        ……
    }
    ……
}

值得注意的是:在layout方法调用中,会调用setFrame,该方法有个逻辑会触发invalidate,invalidate会触发onDraw,因此可以说requestLayout只有当某个view的l,t,r,b发生改变时,才会触发invalidate,从而触发onDraw。

//伪代码
if(l,t,r,b 发生改变) { 
……
    invalidate 
……
}

requestLayout是不断往上调用的,因此子view没有机会标记PFLAG_FORCE_LAYOUT,所以不会用forceLayout为true来判断是否要测量,而是走needsLayout的判断条件。

final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

根据上面的代码:

  1. specChanged 表示宽或高的测量规格发生变化,可以理解测量规格发生变化是needsLayout的前提。
  2. 后面还跟着三个条件,满足其一即可:
    • 版本小于等于6.0
    • 该view的宽或高的测量规格不是精确的
    • 该view的宽或高的测量尺寸发生了变化

invalidate

invalidate相对于requestLayout会比较复杂,调用某个View的invalidate(),内部调用invalidateInternal来修改标记,然后调用 parent的invalidateChild(this, damage);

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
    ……
        //添加 PFLAG_DIRTY
        mPrivateFlags |= PFLAG_DIRTY;
    
        if (invalidateCache) {
            //添加 PFLAG_INVALIDATED,只有该View加了这个标记,parent都没有加
            mPrivateFlags |= PFLAG_INVALIDATED;
            //移除 PFLAG_DRAWING_CACHE_VALID
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        } 
    ……
            p.invalidateChild(this, damage);
    ……
}

invalidateChild内部有个do while循环,不停调parent的invalidateChildInParent,一直到调用ViewRootImpl的invalidateChildInParent。

public final void invalidateChild(View child, final Rect dirty) {
    ……
        if (child.mLayerType != LAYER_TYPE_NONE) {
            //添加 PFLAG_INVALIDATED
            mPrivateFlags |= PFLAG_INVALIDATED;
            //移除 PFLAG_DRAWING_CACHE_VALID
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }
        ……
        do {
            ……
            if (view != null) {
                if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                    //mPrivateFlags 移除PFLAG_DIRTY_MASK,添加PFLAG_DIRTY
                    view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
                }
            }
                
            parent = parent.invalidateChildInParent(location, dirty);
            ……
        } while (parent != null);
    }
}
//ViewGroup#invalidateChildInParent
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    // mPrivateFlags 包含 PFLAG_DRAWN 或者 PFLAG_DRAWING_CACHE_VALID的标记
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
        //省去计算dirty,硬件渲染时不需要用到dirty
        ……
        //移除 PFLAG_DRAWING_CACHE_VALID
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        ////未设置LayerType,因此未添加 PFLAG_INVALIDATED
        if (mLayerType != LAYER_TYPE_NONE) {
            mPrivateFlags |= PFLAG_INVALIDATED;
        }
        return mParent;
    }
    return null;
}

ViewGroup的invalidateChildInParent方法主要是计算了dirty,移除了PFLAG_DRAWING_CACHE_VALID,注意是没有添加了PFLAG_INVALIDATED。

ViewRootImpl的invalidateChildInParent内部调用了invalidateRectOnScreen,之后调用scheduleTraversals,进而 performDraw->draw,mDirty非空就会调mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);最终调到mThreadedRenderer的updateViewTreeDisplayList

private void updateViewTreeDisplayList(View view) {
    //添加了 View.PFLAG_DRAWN;
    view.mPrivateFlags |= View.PFLAG_DRAWN;
    //判断是否有 PFLAG_INVALIDATED, 有的话为true
    view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)== View.PFLAG_INVALIDATED;
    //移除PFLAG_INVALIDATED
    view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
    //调了view的updateDisplayListIfDirty
    view.updateDisplayListIfDirty();
    view.mRecreateDisplayList = false;
}

View中的updateDisplayListIfDirty();

public RenderNode updateDisplayListIfDirty() {
    ……

    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.hasDisplayList()
            || (mRecreateDisplayList)) {
        // 只有添加了PFLAG_INVALIDATED标记的mRecreateDisplayList为true
        if (renderNode.hasDisplayList()
                && !mRecreateDisplayList) {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchGetDisplayList();

            return renderNode; // no work needed
        }
        //mRecreateDisplayList为true才有机会走到下面的draw
        ……
                    draw(canvas);
        ……
    return renderNode;
}

ViewGroup中的dispatchGetDisplayList,主要就是for循环调用recreateChildDisplayList,recreateChildDisplayList调用child.updateDisplayListIfDirty(); 如此不断往child走,直到遇到有PFLAG_INVALIDATED标记的view(即被调invalidate的那个view),才开始调用draw。

image.png

其他问题

  1. 调用两次requestLayout,会导致流程执行两遍吗?

    requestLayout会调到scheduleTraversals,scheduleTraversals使用了标记字段(mTraversalScheduled)控制, 如果上一个scheduleTraversals发出的消息被执行,mTraversalScheduled会置为false,那么第二次requestLayout会生效。如果上一个scheduleTraversals发出的消息没有被执行,那么第二次requestLayout不会生效;生效与否通过标记位(mTraversalScheduled)控制。

  2. 可以在非主线程更新UI吗?

    在Activity启动流程的resume阶段及之前,通过异步线程进行某些UI操作来调用requestLayout时,因为parent那时还没分分配,所以不会调到ViewRootImpl,也就不会调到requestLayout里的checkThread(),所以不会抛出异常。checkThread里判断了当前现场和创建ViewRootImpl的线程是否是同一个线程。

  3. getMeasureWidth()和getWidth()的区别?

    • getMeasureWidth()是在measure后有值,getWidth()是在layout后有值;
    • View#getWidth()通过mRight – mLeft计算而来,View#layout方法为public,可以通过自定义参数修改mRight、mLeft的值,导致getMeasureWidth()和getWidth()获得的值不一致。
  4. onDraw在什么时候情况下不会被调用?如何让它调用?

    在draw方法中,判断是透明,就不再走onDraw方法。在ViewGroup初始化的时候,它调用了一个私有方法:initViewGroup,它里面会有一句setFlags(WILLL_NOT_DRAW,DRAW_MASK);相当于调用了setWillNotDraw(true),所以说,对于ViewGroup,它就认为是透明的了。所以ViewGroup 一般不会绘制自身,只会绘制子 View,所以不会回调 onDraw(),这也是出于性能和效率的考虑。 如果希望调用,则需要存在背景或者需要在初始化时候手动调用setWillNotDraw(false)。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13769.html

(0)

相关推荐

  • redis笔记04

    redis笔记04Redis 数据备份与恢复 Redis SAVE 命令用于创建当前数据库的备份。 语法 redis Save 命令基本语法如下: redis 127.0.0.1:6379> SAVE 实例 redi…

    2023-02-06
    152
  • JAVA中的事务,事务模块总结「终于解决」

    JAVA中的事务,事务模块总结「终于解决」大家好,这是一个为了梦想而保持学习的博客。这是第二篇文章,分享一下我对【事务】的理解。文章的风格会一直保持问答的方式讲述,这是我个人喜欢的一种风格,也是相当于模拟面试。 什么是事务? 简单的来说,一…

    2023-02-01
    152
  • 2019年Java面试题基础系列228道(5),快看看哪些你还不会?

    2019年Java面试题基础系列228道(5),快看看哪些你还不会?23、不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。 比如你的ABC分别对应动物,猫,黑猫。 你把c转型为B,黑猫是猫吗?是啊,所以这是ok的。 这就不ok了,只知道这个b是一只猫,他不一定是黑猫。 这里的b本来就是黑…

    2023-07-25
    127
  • 利用 Python bytes() 将对象转换为原始字节序列

    利用 Python bytes() 将对象转换为原始字节序列在Python中,bytes()函数是用于表示二进制数据的类。它可以存储二进制数据的序列,使数据更容易读取及处理。bytes()函数可以接收单个字符串、字节数组或数字数组类型作为参数。当传递单个字符串作为参数时,字符串中的每个字符都会被转换成一个字节,形成一个新的bytes对象。通过bytes()函数转换后,Python程序可以更加容易地处理底层操作。

    2023-12-14
    114
  • 更高效的时间管理:Python的On-Time-In-Time策略

    更高效的时间管理:Python的On-Time-In-Time策略现代生活节奏快,时间管理显得愈加重要。合理地规划时间可以使我们更好地完成工作任务,提高工作效率,减少工作压力。On-Time-In-Time (OTIT)是一种帮助我们提高时间管理效率的策略。Python作为一种高效的编程语言,可以帮助我们实现这一策略。

    2024-03-19
    83
  • python的如何对比两个时间的简单介绍

    python的如何对比两个时间的简单介绍举例,一个时间偏移后的比较情况:

    2023-11-20
    113
  • Python中Callable的概念与实现

    Python中Callable的概念与实现在Python的世界中,我们常常会听到关于callable的概念。那么”callable”是什么呢?在Python中,callable是指一种特殊的对象,这种对象可以像函数一样”callable”或被调用,例如Python中的函数、方法、类以及实现了特殊方法__call__的对象。在本文中,我们将会详细介绍callable的概念、使用场景以及具体实现等方面的内容。

    2024-04-20
    72
  • clickhouse是什么类型数据库_clickhouse表引擎

    clickhouse是什么类型数据库_clickhouse表引擎ClickHouse属于分析型数据库,ClickHouse提供了许多数据类型,它们可以划分为基础类型、复合类型和特殊类型。其中基础类型使ClickHouse具备了描述数据的基本能力,而另外两种类型则使

    2023-06-03
    145

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注