Invalidate简单分析

invalivade 流程

背景

最近在做Flutter的分层渲染分析,发现Flutter的分层渲染可以让节点标脏限制在同一个 Layer 中,从而提升性能。然后想到 Android 在更新 DisplayList 的时候会判断节点 dirty.isEmpty,从而决定是否更新DisplayList,那么这个dirty是哪里来的呢,Andriod 原生是怎么判断一个节点是否需要更新的呢?

我们都知道要更新节点,需要调用 invalidate() 方法,所以需要分析一下这个方法。

分析

开头总结:invalidate会在 view 树中一级一级往上查找,一级一级的标脏。在查找的过程中会根据父节点的位置更新脏区域的偏移位置,如果遇到脏区域位于父节点剪裁区域的外面,或者其他导致 view 看不到的情况,那么就停止标脏。

invalidate 会设置缓存失效,调到 invalidateInternal 方法:

public void invalidate() {invalidate(true);
}void invalidate(boolean invalidateCache) {//mLeft、mRigth、mTop、mBottom记录的是当前View边界距离其父布局View边界的距离invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {// ...//如果当前视图为不可见状态且没有动画正在执行,且其父布局也没有过渡动画执行,则跳过if (skipInvalidate()) {return;}//当前View没有正在执行该方法//或绘制缓存可用或未重绘过或透明度发生改变//PFLAG_DRAWN会在该方法内去改标志位//PFLAG_INVALIDATED会在View.draw()方法执行时去掉该标志位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)) {//如果需要全部重绘,invalidate()未传参调用时默认为trueif (fullInvalidate) {mLastIsOpaque = isOpaque();mPrivateFlags &= ~PFLAG_DRAWN;}mPrivateFlags |= PFLAG_DIRTY;if (invalidateCache) {mPrivateFlags |= PFLAG_INVALIDATED;mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;}// Propagate the damage rectangle to the parent view.//damage记录的区域是需要更新的dirty区域,当前的坐标时相对于自身来设置的//通过不断调用到父类的invalidateChild()方法,来不断更新dirty区域的相对坐标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);}// ...}
}

invalidateInternal 方法主要是判断到底到底要不要重绘,判断条件包括是否可见 / 是否使用缓存 / 是否已经被设置了 invalidate。然后创建脏区域传给父节点的 invalidateChild 方法。 ViewParent是个父类,ViewGroup实现了它:

@Override
public final void invalidateChild(View child, final Rect dirty) {ViewParent parent = this;final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {      //drawAnimation记录调用该方法的子View是否正在执行动画final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)== PFLAG_DRAW_ANIMATION;//调用该方法的子View是否不透明:处于不透明状态且没有在执行动画且变化矩阵没有变化       //Matrix可以用于View的平移、缩放、扩放、旋转等操作,比如某些应用上的双指缩放功能Matrix childMatrix = child.getMatrix();final boolean isOpaque = child.isOpaque() && !drawAnimation &&child.getAnimation() == null && childMatrix.isIdentity();// Mark the child as dirty, using the appropriate flag// Make sure we do not set both flags at the same timeint opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;if (child.mLayerType != LAYER_TYPE_NONE) {mPrivateFlags |= PFLAG_INVALIDATED;mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;}//记录子View边界距离父View左边界和上边界的距离到Location中,用于下一段代码中的计算final int[] location = attachInfo.mInvalidateChildLocation;            location[CHILD_LEFT_INDEX] = child.mLeft;location[CHILD_TOP_INDEX] = child.mTop;       //如果子View设置了变换矩阵,则根据变换矩阵调整dirty区域if (!childMatrix.isIdentity() ||(mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {RectF boundingRect = attachInfo.mTmpTransformRect;boundingRect.set(dirty);Matrix transformMatrix;if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {Transformation t = attachInfo.mTmpTransformation;boolean transformed = getChildStaticTransformation(child, t);if (transformed) {transformMatrix = attachInfo.mTmpMatrix;transformMatrix.set(t.getMatrix());if (!childMatrix.isIdentity()) {transformMatrix.preConcat(childMatrix);}} else {transformMatrix = childMatrix;}} else {transformMatrix = childMatrix;}transformMatrix.mapRect(boundingRect);dirty.set((int) Math.floor(boundingRect.left),(int) Math.floor(boundingRect.top),(int) Math.ceil(boundingRect.right),(int) Math.ceil(boundingRect.bottom));}//这是一个从当前的布局View向上不断遍历当前布局View的父布局,最后遍历到ViewRootImpl的循环do {View view = null;          //parent可能为ViewGroup类型,也可能为ViewRootImpl类型                //最后一次循环执行时为ViewRootImpl类型if (parent instanceof View) {view = (View) parent;}//如果子View正在执行动画,设置遍历的父布局View的动画标识if (drawAnimation) {if (view != null) {view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;} else if (parent instanceof ViewRootImpl) {((ViewRootImpl) parent).mIsAnimating = true;}}//设置当前ViewGroup的Dirty标识,表示当前的ViewGroup需要重绘if (view != null) {if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&view.getSolidColor() == 0) {opaqueFlag = PFLAG_DIRTY;}if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;}}//调用当前布局View的invalidateChildParent()方法,返回的值为当前布局View的父布局          //通过循环向上调用,最后返回的根布局是ViewRootImpl对象          parent = parent.invalidateChildInParent(location, dirty);// 更新 dirty 位置if (view != null) {// Account for transform on current parentMatrix m = view.getMatrix();if (!m.isIdentity()) {RectF boundingRect = attachInfo.mTmpTransformRect;boundingRect.set(dirty);m.mapRect(boundingRect);dirty.set((int) Math.floor(boundingRect.left),(int) Math.floor(boundingRect.top),(int) Math.ceil(boundingRect.right),(int) Math.ceil(boundingRect.bottom));}}} while (parent != null);}
}

invalidateChild方法是关键,它的作用就是循环往上一直找到 ViewRootImpl,在这个过程中一直更新 dirty的位置。接下来看 invalidateChildInParent

@Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {     if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {        //如果ViewGroup有没有动画执行或者动画已经完成if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=FLAG_OPTIMIZE_INVALIDATE) {          //dirty记录的是最开始调到invalidate()的View的区域                //dirty的四个坐标值值在执行下面代码是相对于当前循环到上一个ViewGroup来确定的          //这里做了一个偏移动作,偏移的量是当前上一个ViewGroup相对于现在ViewGroup的偏移值          //做完下面的偏移操作后,dirty的四个坐标就是想对于当前ViewGroup的坐标值了dirty.offset([CHILD_LEFT_INDEX] - mScrollX,location[CHILD_TOP_INDEX] - mScrollY);          //如果当前ViewGroup需要裁剪View          //则将当前ViewGroup的区域与View的区域做求并集的操作if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {dirty.union(0, 0, mRight - mLeft, mBottom - mTop);}final int left = mLeft;final int top = mTop;//如果当前ViewGroup需要裁剪View,且ViewGroup区域与View区域没有并集,则dirty置空if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {dirty.setEmpty();}}mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;//用于循环到下一个ViewGroup时做offset操作location[CHILD_LEFT_INDEX] = left;location[CHILD_TOP_INDEX] = top;if (mLayerType != LAYER_TYPE_NONE) {mPrivateFlags |= PFLAG_INVALIDATED;}return mParent;} else {//如果当前ViewGroup中有动画要执行mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;location[CHILD_LEFT_INDEX] = mLeft;location[CHILD_TOP_INDEX] = mTop;          //如果需要对子View裁剪则设置dirty为当前ViewGroup区域                //如果不需要则求当前ViewGroup区域与原ditry区域并集          if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {dirty.set(0, 0, mRight - mLeft, mBottom - mTop);} else {// in case the dirty rect extends outside the bounds of this containerdirty.union(0, 0, mRight - mLeft, mBottom - mTop);}if (mLayerType != LAYER_TYPE_NONE) {mPrivateFlags |= PFLAG_INVALIDATED;}return mParent;}}return null;
}

invalidateChildInParent 方法计算子节点是否超出了父节点的剪裁空间,如果超出了,则将dirty置空。

最后调到 ViewRootImplinvalidateChildInParent

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {checkThread();if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);//如果传入一个null drity,则表示要重绘当前ViewRootImpl指示的整个区域     //如果传入一个empty dirty,则表示经过计算需要重绘的区域不需要绘制     if (dirty == null) {invalidate();return null;} else if (dirty.isEmpty() && !mIsAnimating) {return null;}...invalidateRectOnScreen(dirty);return null;
}

再来一个绘制判断,然后调用invalidateRectOnScreen

private void invalidateRectOnScreen(Rect dirty) {     //mDirty记录的是当前ViewRootImpl里还未进行重绘需要重绘的区域        //mDirty会在ViewRootImpl.draw()方法结尾处设置为empty     final Rect localDirty = mDirty;if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {mAttachInfo.mSetIgnoreDirtyState = true;mAttachInfo.mIgnoreDirtyState = true;}// Add the new dirty rect to the current one        //当前已有的dirty区域与此次dirty区域做并集localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);// Intersect with the bounds of the window to skip// updates that lie outside of the visible regionfinal float appScale = mAttachInfo.mApplicationScale;     //处理窗口缩放与做完并集的localDirty做交集final boolean intersected = localDirty.intersect(0, 0,(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));//如果没有交集     if (!intersected) {localDirty.setEmpty();}     //mWillDrawSoon在performTraversals()方法开始时置为true,结束时置false     //如果没有在执行performTraversals &&(intersected || 正在执行动画)if (!mWillDrawSoon && (intersected || mIsAnimating)) {scheduleTraversals();}
}

invalidateRectOnScreen方法中与之前的脏区域做并集,与窗口缩放做交集后请求下一帧渲染。

这样一趟下来,只要脏区域可见,那么该节点和所有父节点都被标记了:mPrivateFlags |= PFLAG_INVALIDATED; 接下来在更新DisplayList的时候 仅 mPrivateFlags PFLAG_INVALIDATED标记的更新:

// frameworks\base\core\java\android\view\ThreadedRenderer.java
private void updateViewTreeDisplayList(View view) {view.mPrivateFlags |= View.PFLAG_DRAWN;view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)== View.PFLAG_INVALIDATED;view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;view.updateDisplayListIfDirty();view.mRecreateDisplayList = false;
}// View.updateDisplayListIfDirty
public RenderNode updateDisplayListIfDirty() {final RenderNode renderNode = mRenderNode;// ......if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0|| !renderNode.hasDisplayList()|| (mRecreateDisplayList)) {// Don't need to recreate the display list, just need to tell our// children to restore/recreate theirsif (renderNode.hasDisplayList()&& !mRecreateDisplayList) {// ......dispatchGetDisplayList();// ......return renderNode; // no work needed}// 更新自己的DisplayList// ...}
}

总结

调用 invalidate()方法之后,会一直往上走标脏的同时设置脏区域。相较于重新全屏绘制来讲,在于会做剪裁区域的优化以及其他子树Display缓存重复使用的优化。和 Flutter 的分层渲染相比,优化还是有差距的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/77516.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

servlet接受参数和乱码问题

servlet接受参数和乱码问题 1、乱码问题 1&#xff09;get请求 传输参数出现中文乱码问题&#xff1a; 如果还存在问题&#xff1a; 2&#xff09;post请求 传输参数出现中文乱码问题&#xff1a; 2、接受参数&#xff1a; 3、登录注册案例

python数据处理程序代码,如何用python处理数据

大家好&#xff0c;给大家分享一下python数据处理程序代码&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 要求&#xff1a;分别以james&#xff0c;julie&#xff0c;mikey&#xff0c;sarah四个学生的名字建立文本文件&#xff0c;分别存…

python机器学习(六)决策树(上) 构造树、信息熵的分类和度量、信息增益、CART算法、剪枝

决策树算法 模拟相亲的过程&#xff0c;通过相亲决策图&#xff0c;男的去相亲&#xff0c;会先选择性别为女的&#xff0c;然后依次根据年龄、长相、收入、职业等信息对相亲的另一方有所了解。 通过决策图可以发现&#xff0c;生活中面临各种各样的选择&#xff0c;基于我们的…

Python开发环境Spyder介绍

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 Spyder简介 Spyder (前身是 Pydee) 是一个强大的交互式 Python 语言开发环境&#xff0c; 提供高级的代码编辑、交互测试、调试等特性&#xff0c;支持包括 Windows、Linux 和 OS X 系统。 &#x1f447; &#x1f44…

React Native获取手机屏幕宽高(Dimensions)

import { Dimensions } from react-nativeconsole.log(Dimensions, Dimensions.get(window)) 参考链接&#xff1a; https://www.reactnative.cn/docs/next/dimensions#%E6%96%B9%E6%B3%95 https://chat.xutongbao.top/

uni、css——制作表格样式的模型

案例展示 这里以5列做展示&#xff08;可随意调节&#xff09; 案例代码 <view class"list"><view class"item" v-for"(item,index) in list" :key"index">1</view> <!-- 有内容 --><view clas…

【零拷贝】

一、零拷贝 1、零拷贝的概念 学习零拷贝时我们先了解几个buffer缓冲区&#xff1a; 当某个程序或已存在的进程需要某段数据时&#xff0c;它只能在用户空间中属于它自己的内存中访问、修改&#xff0c;这段内存暂且称之为user buffer正常情况下&#xff0c;数据只能从磁盘(或…

LeetCode724. 寻找数组的中心下标

题干 给你一个整数数组 nums &#xff0c;请计算数组的 中心下标 。 数组 中心下标 是数组的一个下标&#xff0c;其左侧所有元素相加的和等于右侧所有元素相加的和。 如果中心下标位于数组最左端&#xff0c;那么左侧数之和视为 0 &#xff0c;因为在下标的左侧不存在元素。…

pycharm运行pytest无法实时输出信息

需要去掉控制台输出。根据查询相关信息显示pycharm运行pytest无法实时输出信息&#xff0c;需要去掉pycharm里面的运行模式&#xff0c;点击减号&#xff0c;再点击加号&#xff0c;添加python执行文件即可实时输出信息。 问题描述&#xff1a; 使用pycharm运行代码时&#x…

时间复杂度为O(nlogn)的两种排序算法

1.归并排序 归并排序的核心思想&#xff1a;如果要排序一个数组&#xff0c;我们先把数组从中间分成前后两部分&#xff0c;然后对前后两部分分别排序&#xff0c;再将排好序的两部分合并在一起&#xff0c;这样整个数组就都有序了。 归并排序使用的就是分治思想。分治&#x…

基于arcFace+faiss开发构建人脸识别系统

在上一篇博文《基于facenetfaiss开发构建人脸识别系统》中&#xff0c;我们实践了基于facenet和faiss的人脸识别系统开发&#xff0c;基于facenet后续提出来很多新的改进的网络模型&#xff0c;arcFace就是其中一款优秀的网络模型&#xff0c;本文的整体开发实现流程与前文相同…

单通道 6GSPS 16位采样DAC子卡模块--【资料下载】

FMC147是一款单通道6.4GSPS&#xff08;或者配置成2通道3.2GSPS&#xff09;采样率的12位AD采集、单通道6GSPS&#xff08;或配置成2通道3GSPS&#xff09;采样率16位DA输出子卡模块&#xff0c;该板卡为FMC标准&#xff0c;符合VITA57.4规范&#xff0c;该模块可以作为一个理想…

Metric3D:Towards Zero-shot Metric 3D Prediction from A Single Image

参考代码&#xff1a;Metric3D 介绍 在如MiDas、LeReS这些文章中对于来源不同的深度数据集使用归一化深度作为学习目标&#xff0c;则在网络学习的过程中就天然失去了对真实深度和物体尺寸的度量能力。而这篇文章比较明确地指出了影响深度估计尺度变化大的因素就是焦距 f f f…

Javascript学习(1)

在外部文件中放置脚本有如下优势&#xff1a; 分离了 HTML 和代码使 HTML 和 JavaScript 更易于阅读和维护已缓存的 JavaScript 文件可加速页面加载 如需向一张页面添加多个脚本文件 - 请使用多个 script 标签&#xff1a; JavaScript 能够以不同方式“显示”数据&#xff1…

[Linux]理解文件系统!动静态库详细制作使用!(缓冲区、inode、软硬链接、动静态库)

hello&#xff0c;大家好&#xff0c;这里是bang___bang_&#xff0c;今天来谈谈的文件系统知识&#xff0c;包含有缓冲区、inode、软硬链接、动静态库。本篇旨在分享记录知识&#xff0c;如有需要&#xff0c;希望能有所帮助。 目录 1️⃣缓冲区 &#x1f359;缓冲区的意义 …

MCU的类型和应用领域简介

MCU&#xff08;Microcontroller Unit&#xff09;根据存储器类型可分为无片内ROM型和带片内ROM型。无片内ROM型的芯片需要外接EPROM才能应用&#xff0c;而带片内ROM型则有不同的子类型&#xff0c;如片内EPROM型、MASK片内掩模ROM型和片内Flash型。 MCU还可以按照用途分为通…

Nodejs实现读写文件和文件流

在Nodejs中&#xff0c;文件操作是非常常见的任务之一。它允许我们读取和写入文件&#xff0c;以及处理大型文件而不会消耗太多内存。本篇博文将会首先介绍一下文件和文件流的区别&#xff0c;然后全面介绍如何在Nodejs中实现文件操作和读写&#xff0c;包括使用文件系统模块&a…

vxworks文件系统分析

参考https://www.freebuf.com/articles/endpoint/335030.html 测试固件 https://service.tp-link.com.cn/detail_download_7989.html 固件提取 binwalk解压固件&#xff0c;在第一部分即为要分析的二进制文件&#xff0c;可以拖进ida分析 设置为arm小端字节序&#xff0c;点…

IL汇编语言读取控制台输入和转换为整数

新建一个testcvt.il&#xff1b; .assembly extern mscorlib {}.assembly Test{.ver 1:0:1:0}.module test.exe.method static void main() cil managed{.maxstack 1.entrypointldstr "\n请输入一个数字:"call void [mscorlib]System.Console::Write(string)call st…

目标检测中的IOU

IOU 什么是IOU?IOU应用场景写代码调试什么是IOU? 简单来说IOU就是用来度量目标检测中预测框与真实框的重叠程度。在图像分类中,有一个明确的指标准确率来衡量模型分类模型的好坏。其公式为: 这个公式显然不适合在在目标检测中使用。我们知道目标检测中都是用一个矩形框住…