IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> View 工作流程 -> 正文阅读

[移动开发]View 工作流程

提示:本文基于 Android API 31

ViewRootImpl

任何控件的展示都是通过 WindowManager.addView 来实现的并且 WindowManager 是一个接口真正的实现是 WindowManagerImpl 它又直接调用了 WindowManagerGlobal 的 addView 方法在这个方法里创建了 ViewRootImpl 它是所有控件的抽象父控件,它没有不是继承自 View 但是实现了 ViewParent 接口是 DecorView 的 parent,在 Activity 中通过 setContentView 方法传入的布局就是设置给 DecorView 的一个 id 为 android.R.id.content 的子控件的

// android.view.WindowManagerGlobal#addView    
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    // 代码省略 ..
    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        // 代码省略 ..

      	// 创建 ViewRootImpl 对象
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

在 ViewRootImpl 的 setView 方法里会调用 requestLayout() 方法触发测量、布局、绘制工作

// android.view.ViewRootImpl#requestLayout
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
      	// 标记位置为 true
        mLayoutRequested = true;
      	// 调度
        scheduleTraversals();
    }
}

// android.view.ViewRootImpl#scheduleTraversals
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
      	// 发送一个同步屏障消息优先处理异步消息
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
      	// 发送一个 mTraversalRunnable
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

mTraversalRunnable 是 TraversalRunnable 对象在下一次 Vsync 信号来的时候会调用其 run 方法

// android.view.ViewRootImpl.TraversalRunnable
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

// android.view.ViewRootImpl#doTraversal
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
      	// 移除同步屏障消息
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

      	// 真正的开始 View 的工作流程
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}	

// android.view.ViewRootImpl#performTraversals
private void performTraversals() {
    // 代码省略 ..
    performMeasure
    // 代码省略 ..
    performLayout
    // 代码省略 ..
    performDraw
    // 代码省略 ..
}

performTraversals 方法中会一次调用测量、布局、绘制的方法,依次看一下

测量

// mWidth mHeight 是屏幕宽高
// lp 是 android.view.WindowManager.LayoutParams
// lp.width lp.height 默认是 MATCH_PARENT
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);


 // Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

DecorView 因为是顶层 View 没有实际父级只有抽象父级 ViewRootImpl 所以它的测量过程与普通控件有一些区别,其他控件的 MeasureSpec 都是通过父级的 MeasureSpec 和自己的 ViewGroup.LayoutParams 得到的而 DecorView 的 MeasureSpec 是通过屏幕的宽高和 WindowManager.LayoutParams 得到的,下面先来介绍一下 MeasureSpec

MeasureSpec 代表一个 32 位 int 值,高 2 位代表 SpecMode,低 30 位代表 SpecSize,SpecMode 是指测量模式,而 SpecSize 是指在某种测量模式下的规格大小。

SpecMode 有三类,每一类都表示特殊的含义,如下所示。

UNSPECIFIED:父容器不对 View 有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。

EXACTLY:父容器已经检测出 View 所需要的精确大小,这个时候 View 的最终大小就是 SpecSize 所指定的值。它对应于 LayoutParams 中的 match_parent 和具体的数值这两种模式。

AT_MOST:父容器指定了一个可用大小即 SpecSize,View 的大小不能大于这个值,具体是什么值要看不同的 View 的具体实现。它对应于 LayoutParams 中的 wrap_content

《Android 开发艺术探索》

// 确定 DecorView 的 MeasureSpec 再去测量子控件
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
      	// 递归测量所有控件
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

// android.view.View#measure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    // 代码省略 ..

    // 如果设置了 PFLAG_FORCE_LAYOUT 标记表示需要强制布局
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

    // 如果大小改变则需要重新测量
    final boolean needsLayout = specChanged
            && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

    if (forceLayout || needsLayout) {
           // 代码省略 ..    
        onMeasure(widthMeasureSpec, heightMeasureSpec);    
        // 代码省略 ..

      	// 设置 PFLAG_LAYOUT_REQUIRED 标记下面会再提到
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }

     // 代码省略 ..
}

在view.measure()的方法里,仅当给与的MeasureSpec发生变化时,或要求强制重新布局时,才会进行测量。

强制重新布局 : 控件树中的一个子控件内容发生变化时,需要重新测量和布局的情况,在这种情况下,这个子控件的父控件(以及父控件的父控件)所提供的MeasureSpec必定与上次测量时的值相同,因而导致从ViewRootImpl到这个控件的路径上,父控件的measure()方法无法得到执行,进而导致子控件无法重新测量其布局和尺寸。

解决途径 : 因此,当子控件因内容发生变化时,从子控件到父控件回溯到ViewRootImpl,并依次调用父控件的requestLayout()方法。这个方法会在mPrivateFlags中加入标记PFLAG_FORCE_LAYOUT,从而使得这些父控件的measure()方法得以顺利执行,进而这个子控件有机会进行重新布局与测量。这便是强制重新布局的意义所在。

Android View的工作流程

measure 是 View 的 final 方法不可被子类重写,在这个方法里会调用 onMeasure 传入控件的 MeasureSpec 完成子控件的测量,ViewGroup 并没有重写 onMeasure 方法因为不同的 ViewGroup 的子类的测量规则是不一样的所以需要子类自己去实现,因为 DecorView 继承自 FrameLayout 所以看一下 FrameLayout 的 onMeasure 方法

// android.widget.FrameLayout#onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();

    // 如果自己宽高之一测量模式不是 EXACTLY 模式
    final boolean measureMatchParentChildren =
            MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
            MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    mMatchParentChildren.clear();

    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            // 遍历所有子控件逐一测量
            // 如果子控件是 ViewGroup 则在子控件中重复此过程
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            // 记录最大宽高用于最后 FrameLayout 测量自己
            maxWidth = Math.max(maxWidth,
                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight,
                    child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            if (measureMatchParentChildren) {
              	// 如果子控件宽高之一是 MATCH_PARENT 则记录下来
                if (lp.width == LayoutParams.MATCH_PARENT ||
                        lp.height == LayoutParams.MATCH_PARENT) {
                    mMatchParentChildren.add(child);
                }
            }
        }
    }

    // 省略一些最大值的检查 ..

    // 设置自己的大小
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));

    count = mMatchParentChildren.size();
    // 如果 FrameLayout 自己宽高之一是 wrap_content 并且有超过一个子控件宽高之一是 MATCH_PARENT 则再测量一次
    // 因为 MATCH_PARENT 的子控件需要跟 FrameLayout 一样大但是 FrameLayout 只有测量了所有的控件拿到最大的控件的大小后才能知道自己的大小
    // 所以 FrameLayout 在知道自己的大小后再重新测量一次子控件
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            final View child = mMatchParentChildren.get(i);
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            final int childWidthMeasureSpec;
            if (lp.width == LayoutParams.MATCH_PARENT) {

                final int width = Math.max(0, getMeasuredWidth()
                        - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                        - lp.leftMargin - lp.rightMargin);
                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                        width, MeasureSpec.EXACTLY);
            } else {
                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                        lp.leftMargin + lp.rightMargin,
                        lp.width);
            }

            // 高的逻辑与宽一致 

            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

FrameLayout 的 onMeasure 方法大致就是逐一测量所有的子控件然后使用最大的子控件的宽高设置自己的大小,如果有特殊情况则再测量一次子控件,看一下测量子控件的方法

// android.view.ViewGroup#measureChildWithMargins
protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

measureChildWithMargins s是 ViewGroup 的方法,处理了间距调用 getChildMeasureSpec 方法,在 FrameLayout 需要二次测量的时候也调用了这个方法

// android.view.ViewGroup#getChildMeasureSpec
// 参数 spec 是父控件的 MeasureSpec
// 参数 padding 是间距,表示父控件的 padding + 子控件自己的 margin
// 参数 childDimension 是子控件的 LayoutParams 的大小即在 xml 文件中声明的宽高
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  	// 父控件的测量模式
    int specMode = MeasureSpec.getMode(spec);
  	// 父控件的测量大小
    int specSize = MeasureSpec.getSize(spec);
  	// 父控件的可用大小(测量大小 - 间距)
    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // 父控件是精确模式
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
          	// 如果子控件宽高是精确值 xxdp 则直接使用这个值
            resultSize = childDimension;
          	// 子控件的测量模式也是精确模式
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // 如果子控件宽高是 MATCH_PARENT 则使用父控件的大小充满父控件
            resultSize = size;
          	// 子控件的测量模式也是精确模式
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // 子控件宽高是 WRAP_CONTENT 填充模式 
            resultSize = size;
          	// 子控件的测量模式是 '最大' 模式表示最大不超出父控件大小 size
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    // 父控件是 '最大' 模式
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            // 如果子控件宽高是精确值 xxdp 则直接使用这个值
            resultSize = childDimension;
          	// 子控件的测量模式也是精确模式
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
						// 如果子控件宽高是 MATCH_PARENT 则使用父控件的大小充满父控件
            resultSize = size;
          	// 通常这个时候父控件也还没有确定自己的大小所以子控件也是 '最大' 模式
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // 子控件宽高是 WRAP_CONTENT 填充模式
            resultSize = size;
            // 通常这个时候父控件也还没有确定自己的大小所以子控件也是 '最大' 模式
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    // 父控件不限制子控件的大小    
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            // 如果子控件是明确的大小就是用这个大小模式是 '精确' 模式
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // 如果子控件宽高是 MATCH_PARENT 大小取决于 View.sUseZeroUnspecifiedMeasureSpec 的值
          	// View.sUseZeroUnspecifiedMeasureSpec 的值默认为 true 即子控件大小为 0
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // 如果子控件宽高是 WRAP_CONTENT 大小取决于 View.sUseZeroUnspecifiedMeasureSpec 的值
          	// View.sUseZeroUnspecifiedMeasureSpec 的值默认为 true 即子控件大小为 0
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    // 根据测量的宽高封装一个 MeasureSpec 对象
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

方法已经详细注释了,具体逻辑可以通过一个表呈现:

View 的 MeasureSpec 创建规则
右下角两项 UNSPECIFIED 为 0 或 parentSize 取决于 View 中静态变量 sUseZeroUnspecifiedMeasureSpec 的值,它的值是 sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M

最后根据测量大小和测量模式封装成一个 MeasureSpec 然后去测量子控件,子控件的 measure 调用 onMeasure 方法接下来看一下 View 的 onMeasure 方法

// android.view.View#onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

// 如果有设置 background 则取 background 的原始宽度和 minWidth 的大值
// 如果没有设置 background 则取 minWidth 对应 androind:minWidth 属性
// getSuggestedMinimumHeight 也是这个逻辑相当于获取控件的默认大小
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    // 父控件的测量模式 
    int specMode = MeasureSpec.getMode(measureSpec);
    // 父控件的测量大小
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    // 如果测量模式是 UNSPECIFIED 则使用默认大小
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    // 测量模式是 AT_MOST 或 EXACTLY 都使用父控件的大小  
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

通过 getDefaultSize 方法可以知道在 View 的默认实现中,不管是 EXACTLY 还是 AT_MOST 模式拿到的都是父控件的可用大小,这就是说在 xml 文件中不管是设置为 match_parent 还是 wrap_content 显示出来的大小都是一样的所以当自定义 View 的时候通常要重写 onMeasure 方法根据需求给一个 AT_MOST 模式下的默认大小

总结一下 View 的测量过程就是从 DecorView 开始,根据屏幕宽高和 WindowManager.LayoutParams(width, height 默认是 MATCH_PARENT)得到 MeasureSpec 然后去测量自己,因为自己是 ViewGroup 所以会先测量所有的子控件才能知道自己的大小,如果子控件是 View 测量就结束了如果是 ViewGroup 则重复这个过程,这里普通控件(非 DecorView 其他 View)与 DecorView 的区别是普通控件的 MeasureSpec 是通过父控件的 MeasureSpec 和自己的 ViewGroup.LayoutParams 得到的

布局

布局从 ViewRootImpl 的 performLayout 开始然后调用真正的顶级控件 DecorView 的 layout 方法,FrameLayout 并没有重写 layout 方法 ViewGroup 也是简单的调用了 super.layout 方法所以直接看 View 的 layout 方法

// android.view.ViewRootImpl#performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
  	// 代码省略 ..  
  	host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
  	// 代码省略 ..
}    

// android.view.View#layout
// 参数 l 对应上面 0
// 参数 t 对应上面 0
// 参数 r 对应上面 host.getMeasuredWidth() 测量宽度
// 参数 b 对应上面 host.getMeasuredHeight() 测量高度
public void layout(int l, int t, int r, int b) {
    // 代码省略 ..
  
  	// 通过 setFrame 设置子控件在父控件中的位置
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
		
 		// 如果位置有变化或者设置了 PFLAG_LAYOUT_REQUIRED 标记(通过 view.requestLayout 触发重新测量、布局时在 view.measure 中标记)调用 onLayout 重新布局,这里是针对 ViewGroup 让其去遍历布局子控件
  	// View 的 onLayout 是空实现
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);

    		// 代码省略 ..
    }

		// 代码省略 ..
}

// android.view.View#setFrame
protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    // 代码省略 ..

    // 位置改变
    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;

        // Remember our drawn bit
        int drawn = mPrivateFlags & PFLAG_DRAWN;

        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

        // 会触发重绘
        invalidate(sizeChanged);

        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

        mPrivateFlags |= PFLAG_HAS_BOUNDS;


        if (sizeChanged) {
            sizeChange(newWidth, newHeight, oldWidth, oldHeight);
        }

        // 代码省略 ..
    }
    return changed;
}

所谓布局其实就是把自己在父控件的坐标记录下来,之后就可以通过 getWidth/getHeight 拿到宽高了,所以 getWidth/getHeight 与 getMeasureWidth/getMeasureHeight 的区别就是时机不同,getMeasureWidth/getMeasureHeight 是在测量完成后可以拿到 getWidth/getHeight 是在布局完成后可以拿到,并且结果通常都是一样的(可以在布局时处理成不一样但没有意义)

ViewGroup 中 onLayout 是抽象方法需要子类去根据自己的需要重写,在具体 ViewGroup 子类中会遍历所有的子控件,如果子控件是 View 流程结束如果子控件是 ViewGroup 则递归执行这个过程

绘制

// android.view.ViewRootImpl#performDraw
private void performDraw() {
    // 代码省略 ..
    try {
        boolean canUseAsync = draw(fullRedrawNeeded);
        if (usingAsyncReport && !canUseAsync) {
            mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
            usingAsyncReport = false;
        }
    }
		// 代码省略 ..
}

// android.view.ViewRootImpl#draw
private boolean draw(boolean fullRedrawNeeded) {
     // 代码省略 ..
     if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty || mNextDrawUseBlastSync) {
         // 如果开启了硬件加速 
         if (isHardwareEnabled()) {
             // 代码省略 ..
           	 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
         } else {
             // 代码省略 ..
             if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                     scalingRequired, dirty, surfaceInsets)) {
                 return false;
             }
         }
     }
 }

// 先来看未开启硬件加速的情况
// android.view.ViewRootImpl#drawSoftware
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    // 代码省略 ..    
    // 获取画布
    final Canvas = mSurface.lockCanvas(dirty);
    // 代码省略 ..    
    // 开始绘制流程
    mView.draw(canvas);
    // 代码省略 ..    
}

// android.view.View#draw(android.graphics.Canvas)
public void draw(Canvas canvas) {
    // 代码省略 ..

    // 绘制背景不能重写
    drawBackground(canvas);

    // 代码省略 ..
    // 绘制自己
    onDraw(canvas);

    // 绘制子控件
    // 针对 ViewGroup 去分发绘制子控件
    // 对于 View 没有意义
    dispatchDraw(canvas);

  	// 绘制自动填充
    drawAutofilledHighlight(canvas);

    // 绘制 Overlay 在前景下面
    if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
    }

    // 绘制装饰,前景、精度条等
    onDrawForeground(canvas);

    // 绘制高亮
    drawDefaultFocusHighlight(canvas);
}

View 的绘制流程主要是 onDraw 绘制自己 dispatchDraw 分发绘制子控件,对于自定义 View 不需要重写 dispatchDraw 需要重写 onDraw 绘制自己的逻辑,对于自定义 ViewGroup 一般不需要重写 onDraw 绘制自己(并且 ViewGroup 的 onDraw 默认情况下不执行)需要重写 dispatchDraw 分发绘制所有子控件,如果子控件也是 ViewGroup 重复此过程

// android.view.ViewGroup#dispatchDraw
protected void dispatchDraw(Canvas canvas) {
    // 代码省略 ..
    for (int i = 0; i < childrenCount; i++) {
        // 代码省略 ..

        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
        final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            // 绘制子控件
            more |= drawChild(canvas, child, drawingTime);
        }
    }
    // 代码省略 .. 
}

// android.view.ViewGroup#drawChild
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

// android.view.View#draw(android.graphics.Canvas, android.view.ViewGroup, long)
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    // 代码省略 ..
    if (!drawingWithDrawingCache) {
        if (drawingWithRenderNode) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            ((RecordingCanvas) canvas).drawRenderNode(renderNode);
        } else {
            // 如果 View 设置了 WILL_NOT_DRAW 标记并且背景、前景、高亮都为空则会设置 PFLAG_SKIP_DRAW 标记跳过绘制自己
            // ViewGroup 在初始化的时候默认会设置 WILL_NOT_DRAW 标记
            // 所以如果 ViewGroup 不设置背景一般不会执行 onDraw 方法只会去尝试绘制子控件
            if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchDraw(canvas);
            } else {
              	// 否则执行自己完成的绘制流程
                draw(canvas);
            }
        }
    }

    // 代码省略 ..

    mRecreateDisplayList = false;

    return more;
}

总结一下未开启硬件加速的流程从 android.view.ViewRootImpl#performDraw 调用开始调用 android.view.ViewRootImpl#draw 通过 Surface 对象申请一块画布调用 com.android.internal.policy.DecorView#draw 传入这块画布先通过 onDraw 方法绘制自己(也可以通过设置不绘制自己)再调用 dispatchDraw 绘制所有的子控件如果子控件是 ViewGroup 则递归执行这个流程。在看硬件加速的流程之前先看一下 android.view.View#invalidate() 这个方法(postInvalidate 是在子线程使用通过 Handler 间接调用的 invalidate 方法)

invalidate

// android.view.View#invalidate()
public void invalidate() {
    // 参数值为 true
    invalidate(true);
}

public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
    // 代码省略 ..    

    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
            || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
            || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
            || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
        // 代码省略 ..   

        if (invalidateCache) {
            // 添加 PFLAG_INVALIDATED 标记
            mPrivateFlags |= PFLAG_INVALIDATED;
            // 移除 PFLAG_DRAWING_CACHE_VALID 标记
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            // 调用父控件的重绘方法并且把传入自己要绘制的脏区
            p.invalidateChild(this, damage);
        }

        // 代码省略 ..   
    }
}

// android.view.ViewGroup#invalidateChild
public final void invalidateChild(View child, final Rect dirty) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null && attachInfo.mHardwareAccelerated) {
        // 如果开启了硬件加速 
        onDescendantInvalidated(child, child);
        return;
    }

    ViewParent parent = this;
    if (attachInfo != null) {
        // 如果关闭硬件加速这里应该是 LAYER_TYPE_SOFTWARE 吧所以会执行吧
        if (child.mLayerType != LAYER_TYPE_NONE) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        // 代码省略 ..

        do {
            View view = null;
            if (parent instanceof View) {
                view = (View) parent;
            }

            // 代码省略 ..

            // 循环调用父控件的 invalidateChildInParent 方法
            // 因为 ViewRootImpl 是 DecorView 的 parent 
            // 所以最后会调用到 ViewRootImple 的 invalidateChildInParent 方法
            parent = parent.invalidateChildInParent(location, dirty);
            // 代码省略 ..
        } while (parent != null);
    }
}

// android.view.ViewGroup#onDescendantInvalidated
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
    // 代码省略 ..
    // 调用是循环调用 parent 的 onDescendantInvalidated 方法直到 ViewRootImpl
    if (mParent != null) {
        mParent.onDescendantInvalidated(this, target);
    }
}

// android.view.ViewRootImpl#onDescendantInvalidated
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
    // 代码省略 ..
    invalidate();
}

// android.view.ViewRootImpl#invalidate
void invalidate() {
    // 代码省略 .. 
  	// 触发 View 工作流程(并不一定会执行测量、布局、绘制所有流程)
    scheduleTraversals();
}

从上面最开始的分析已经知道 scheduleTraversals 会在下一次 Vsync 信号来的时候调用 performTraversals 再调用 performDraw 触发重绘,如果开启了硬件加速则继续执行到 android.view.ThreadedRenderer#draw

// android.view.ThreadedRenderer#draw
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    // 代码省略 ..
    updateRootDisplayList(view, callbacks);
    // 代码省略 ..
}

private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    // 代码省略 ..
    updateViewTreeDisplayList(view);
    // 代码省略 ..
}

private void updateViewTreeDisplayList(View view) {
    view.mPrivateFlags |= View.PFLAG_DRAWN;
  	// 因为在调用 invalidate 时设置了 PFLAG_INVALIDATED 所以 mRecreateDisplayList 为 true 
    view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
            == View.PFLAG_INVALIDATED;
    view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
    view.updateDisplayListIfDirty();
    view.mRecreateDisplayList = false;
}

// android.view.View#updateDisplayListIfDirty
public RenderNode updateDisplayListIfDirty() {
		// 代码省略 ..
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.hasDisplayList()
            || (mRecreateDisplayList)) {
        if (renderNode.hasDisplayList()
                && !mRecreateDisplayList) {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
          	// 如果不需要重新绘制则继续往下分发绘制流程
            dispatchGetDisplayList();

            return renderNode; // no work needed
        }

        mRecreateDisplayList = true;

        // 代码省略 ..

        final RecordingCanvas canvas = renderNode.beginRecording(width, height);

        try {
            if (layerType == LAYER_TYPE_SOFTWARE) {
                buildDrawingCache(true);
                Bitmap cache = getDrawingCache(true);
                if (cache != null) {
                    canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                }
            } else {
								// 代码省略 ..

                // 与软件绘制一样同样判断是否需要跳过绘制自己
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    // 跳过的话则直接去分发绘制子控件
                    dispatchDraw(canvas);
                    drawAutofilledHighlight(canvas);
                    if (mOverlay != null && !mOverlay.isEmpty()) {
                        mOverlay.getOverlayView().draw(canvas);
                    }
                    if (isShowingLayoutBounds()) {
                        debugDrawFocus(canvas);
                    }
                } else {
                    // 执行完整的绘制流程
                    draw(canvas);
                }
            }
        } 
      // 代码省略 ..
    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}

// 此方法在 View 中是空方法因为 View 不需要去分发绘制
// android.view.ViewGroup#dispatchGetDisplayList
protected void dispatchGetDisplayList() {
    final int count = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
            recreateChildDisplayList(child);
        }
    }
    // 代码省略 ..
}

// android.view.ViewGroup#recreateChildDisplayList
private void recreateChildDisplayList(View child) {
    child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
    child.mPrivateFlags &= ~PFLAG_INVALIDATED;
    child.updateDisplayListIfDirty();
    child.mRecreateDisplayList = false;
}

总结一下当通过 invalidate 请求重绘时调用 invalidate 的子控件会添加 PFLAG_INVALIDATED 标记并且通过控件的 parent 逐级请求重绘直到调用到 ViewRootImpl 的 scheduleTraversals 方法在下一次 Vsync 信号来的时候执行真正的重绘操作 并且因为只有直接调用 invalidate 的控件设置了 PFLAG_INVALIDATED 标记 view.mRecreateDisplayList 为 true 会执行重绘,其他控件调用 dispatchGetDisplayList 分发子控件的重绘操作并且同样会根据是否添加了 PFLAG_INVALIDATED 标记判断是否执行重绘直至完成所有控件的重绘动作,如果是首次打开页面直接通过 android.view.ViewRootImpl#requestLayout 触发整个流程应该是所有的控件都会被重绘

简单来说当关闭硬件加速时所有子 View 都会被重新绘制,开启硬件加速时只有调用 invalidate 方法的 View 才会重新绘制

requestLayout

// android.view.View#requestLayout
public void requestLayout() {
    // 代码省略 ..
    // 设置强制布局标志
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    // 调用 parent requestLayout
    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    // 代码省略 ..
}

最后看一下 View 的 requestLayout 方法确实都设置了 PFLAG_FORCE_LAYOUT 强制布局标记。invalidate 与 requestLayout 的区别是 invalidate 只会触发绘制操作不会触发测量、布局流程因为 invalidate 方法没有设置 PFLAG_FORCE_LAYOUT 标记,requestLayout 通常情况下只会触发测量、布局流程不会触发重绘流程,但如果控件大小改变则也会触发绘制流程。

总结:首次 View 工作流程是通过 android.view.ViewRootImpl#requestLayout 触发的,它调用了 scheduleTraversals 方法向 Choreographer 发送一个 Runnable 和一个同步屏障消息(保证优先执行这个 Runnable 异步消息)在下一次 Vsync 信号到来后执行这个 Runnable 执行 View 真正的工作测量、布局、绘制。测量的入口是 perfromMeasure 方法它会调用 DecorView 的 measure 从顶层 View 开始整个 View 树的测量,如果 View 只是 View 则只测量自己就结束了如果是 ViewGroup 要先测量所有子控件再测量自己如果子控件还是 ViewGroup 递归执行。布局的入口是 performLayout 方法它会调用 DecorView 的 layout 从顶层 View 开始整个 View 树的布局,如果 View 只是 View 则只布局自己就结束了如果是 ViewGroup 要先布局自己再递归布局所有子控件。绘制的入口是 performDraw 方法它会调用 DecorView 的 draw 开始整个 View 树的绘制,如果 View 只是 View 则只绘制自己就结束了如果是 ViewGroup 要先绘制自己(ViewGroup 默认不绘制自己)再递归绘制所有子控件,并且绘制还有是否开启硬件加速两种情况,当开启硬件加速时只绘制直接调用 invalidate 触发重绘的 View 当未开启硬件加速时绘制所有控件。

参考与感谢

比较一下requestLayout和invalidate方法

一文读懂 View 的 Measure、Layout、Draw 流程

Android View的工作流程

Android ViewGroup onDraw为什么没调用

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-07-03 10:57:45  更:2022-07-03 10:58:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/3 18:47:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码