安卓绘制原理之 那些年遇到的requestLayout埋下的坑

news2025/1/20 11:00:52

        需要了解的知识:安卓绘制原理概览_油炸板蓝根的博客-CSDN博客

        对于调用过requestLayout的View,PFLAG_FORCE_LAYOUT标记在requestLayout之后,onLayou完成之前都是一直存在的;对于ViewGroup而言,layout children都是在onLayout中完成的,因此,对于正在执行onLayout的ViewGroup而言,其子View的所有requestLayout请求都会因为自身的PFLAG_FORCE_LAYOUT阻拦而无法触达RootViewImpl,此时VIew的measure流程已经结束,由于measure的时候,自身并不存在PFLAG_LAYOUT_REQUIRED标志位,那么等到自身layout时候,就不会执行layout,导致view的视图不会按预期更新。

        为了解决潜在的问题,View引入了Layout during Layout机制。

        首先再看一次View.requestLayout方法

public class View {
    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        // mViewRequestingLayout 用于区别发起 requestLayout 的 源头 View 和 其parent
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                viewRoot.requestLayoutDuringLayout(this)
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }
}

public class ViewRootImpl {
    boolean requestLayoutDuringLayout(final View view) {
        if (!mLayoutRequesters.contains(view)) {
            mLayoutRequesters.add(view);
        }
        if (!mHandlingLayoutInLayoutRequest) {
            return true;
        } else {
            return false;
        }
    }
}

        可以看到,view.requestLayout会在调用parent.requestLayout之前,通过ViewRootImpl判断当前是否正在执行Layout流程,如果是,则会吧调用requestLayout的View添加到一个临时数组mLayoutRequesters中。之后呢?在当View Tree的根节点的layout执行完毕以后,ViewRootImpl会检查mLayoutRequesters,如果非空,会再次执行一次measure、layout流程,如果在第二次执行layout过程中,仍然有view调用requestLayout,则会在下一帧再处理这些view,毕竟不能无穷尽的layout下去,还有draw流程在等待

public class ViewRootImpl {
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
        mInLayout = true;
        final View host = mView;
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        mInLayout = false;
        validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters)
        
        if (validLayoutRequesters > 0) {
            mHandlingLayoutInLayoutRequest = true;
            for (int i = 0; i < validLayoutRequesters.size(); ++i) {
                final View view = validLayoutRequesters.get(i);
                view.requestLayout();
            }
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            mInLayout = true;
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            mHandlingLayoutInLayoutRequest = false;

            validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
            if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;
                        // Post second-pass requests to the next frame
                 getRunQueue().post(new Runnable() {
                            @Override
                            public void run() {
                                int numValidRequests = finalRequesters.size();
                                for (int i = 0; i < numValidRequests; ++i) {
                                    final View view = finalRequesters.get(i);
                                    Log.w("View", "requestLayout() improperly called by " + view +
                                            " during second layout pass: posting in next frame");
                                    view.requestLayout();
                                }
                            }
                        });
                    }
        }
        mInLayout = false;
    }
}

        Layout during layout解决了layout时候可能会发生的requestLayout调用被忽略的问题,但是还有一个小瑕疵:

        在每次layout结束后,会通过函数:getValidLayoutRequesters过滤合法的layout requesters,这个合法的判断规则如下:

  • 对于第一次layout期间requestLayout的view,第一次layout结束后,其PFLAG_FORCE_LAYOUT必须需要存在
  • 对于第二次layout期间requestLayout的view,第二次layout结束后,所有view都会在下一帧的回调中重新执行requestLayout

        假设 view A 在自己 onLayout 中调用自己的 requestLayout (一个常见的 case 是:监听了自身的 onLayoutChange ),View A onLayout 结束后 view A 的 PFLAG_FORCE_LAYOUT 就会被清空,如果 View A requestLayout 发生在第一次 layout 期间,layout 结束后 ViewRootImpl 就不会为 View A 发起 second layout;即使由其他 View 发起,View A 也会因为自身并不存在 PFLAG_FORCE_LAYOUT 而大概率不会被 measure 、 layout,这就导致了 View A 的 requestLayout 完全忽略;但如果这发生在第二次 layout 期间,就不会如此(参考下面的add判断条件)。

public class ViewRootImpl {
    private ArrayList<View> getValidLayoutRequesters(ArrayList<View> layoutRequesters, boolean secondLayoutRequests) {

        int numViewsRequestingLayout = layoutRequesters.size();
        ArrayList<View> validLayoutRequesters = new ArrayList<View>();
        for (int i = 0; i < numViewsRequestingLayout; ++i) {
            View view = layoutRequesters.get(i);
            if (secondLayoutRequests || (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) ==View.PFLAG_FORCE_LAYOUT) {
                if (!view.isShown()) {
                    validLayoutRequesters.add(view);
                }
            }
        }
        layoutRequesters.clear();
        return validLayoutRequesters;
    }
}

因此下面用法可能会存在问题:

view.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
    v.modifySize()
}

View.OnLayoutChangeListener 中更改 view UI 的代码最好使用 view.post 实现

        虽然layout期间调用requestLayout的问题解决了,但是导致requestLayout被忽略的屏障PFLAG_FORCE_LAYOUT从requestLayout调用后就存在,那么在其他时间内,requestLayout被忽略会导致问题么? 

  1. 在第一段时间内,虽然requestLayout无法触及到ViewRootImpl,但是下一次的Traversal还没开始,此时调用requestLayout相当于通过修改自己和祖先节点的PFLAG_FORCE_LAYOUT来把自己假如在下一次Traversal的白名单中,所以不会造成任何问题。
  2. 在第二段时间内,ViewRootImpl的measure流程已经开始,view可能已经执行过了measure,此时再调用requestLayout为时已晚。
    1. measure已经结束,无法执行onMeasure,也无法设置PFLAG_LAYOUT_REQUIRED标志(回忆一下measure流程,需要执行onMeasure之后,才会设置这个标志位,而执行onMeasure的前提是1. 大小确实发生了变化,2 执行过requestLayout,可见第二个判断早于了requestLayout)
    2. 由于没有PFLAG_LAYOUT_REQUIRED标志,layout、onLayout可能也无法执行。
    3. 还没有开始layout,Layout During Layout也无法解决问题。(因为isInLayout这个flag标志这layout期间,其他时间都是false)
    4. 没有触达ViewRootImpl,不会注册下一帧的Traversal callback
    5. 如果没有执行layout的话,自身PFLAG_FORCE_LAYOUT并不会被清除,导致children后续的requestLayout被阻塞(因为只有layout才会清除这个标志位)
  3. 在第三段时间内,由Layout during Layout负责解决requestLayout的潜在问题

        所以我们发现在第二段时间内,调用requestLayout是有风险的。官方并不打算解决这个问题。只能我们自己去避免这个问题发生。我们可以发现,当某一个节点的PFLAG_FORCE_LAYOUT没有清楚,他的孩子们的requestLayout将永远被忽略掉,而此时,他们自己还没有被measure、onlayout过,所以表现上会黑屏。

        这个问题的出现,可能有点绕,所以这里举一个例子,再说一次。首先,我们需要明白一下几件事情。

  •  requestLayout执行的时候,会设置PFLAG_FORCE_LAYOUT标志位
  • 如果父节点没有设置PFLAG_FORCE_LAYOUT,则会执行父节点的requestLayout
  • measure时,如果该view具有PFLAG_FORCE_LAYOUT标志位,则会设置PFLAG_LAYOUT_REQUIRED,并且会调用onMeasure
  • layout时,如果设置了PFLAG_LAYOUT_REQUIRED,则会执行onLayout,执行完onLayout之后,清除PFLAG_LAYOUT_REQUIRED,并且一定清除PFLAG_FORCE_LAYOUT标志位。

接下来,用1标志该view设置了PFLAG_FORCE_LAYOUT,用2标志该View设置了PFLAG_LAYOUT_REQUIRED

当C View执行requestLayout的时候,C、B、A将以此向上执行requestLayout,标志位变更如下所示。

此时从A开始执行measure,执行到CD的时候,D在执行measure方法,但还没有执行onMeasure的时候,标志位变更如下.

此时,E执行requestLayout方法。 标志位变更如下。

 此时,D的requestLayout无法向上调用,因为B还在requestLayout中。

然后开始执行layout方法,一次执行ABDC的onLayout方法,清楚他们的1 2标志位。标志位变化如下.

        很不幸,我们发现E的PFLAG_FORCE_LAYOUT无法被清楚掉,因为D只执行了layout方法,但无法执行onLayout方法(因为它没有PFLAG_LAYOUT_REQUIRED标志位),自然就无法调用E的layout方法了。 完蛋了。从刚才的分析发现,D的onMeasure没有执行,接下来,它也不会被layout,这一层永远的不会被绘制出来了。解决办法只有让E或者D再调用一次requestLayout了。

        发现了这个bug以后,就向Google官方提出https://issuetracker.google.com/issues/216163491?pli=1,但可惜回应是:

Status: Won't Fix (Intended Behavior)

This is expected behavior of views, despite being a bit of a foot gun. Requesting layout during layout is something that should not be permitted by custom views and callbacks attached to them, or if layout is requested for a child view while parent layout is in progress, it is the responsibility of the parent to perform measurement and layout to fulfill that request before returning from the parent's own layout process.

        看来,官方人员并不想解决这个问题,他们认为这是预期内的事情,即使确实不太完美。开发者应该自己认识到这个问题,并自己去避免它的发生。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux ipc通信(消息对列)

前言&#xff1a;消息队列也是linux开发ipc机制中较为重要的一个进程间通信机制。 1.系统创建或获取消息对列 int msgget(key_t key, int mode); 创建消息队列&#xff0c;或者获取消息队列。 参数&#xff1a; key - 使用ftok()获取到的key mode - IPC_CREAT|0666 返回&…

级联H桥储能变流器仿真

1.单个H桥模块的工作状态 2.仿真模型 3.仿真结果 3.1逆变电压网侧电压网侧电流 3.2功率跟踪情况 3.3电流跟踪情况 3.4电池SOC变化曲线 3.5相内SOC均衡效果 3.6相间SOC均衡效果 3.7最大零序电压注入与均衡速度 欢迎同行技术交流&#xff0c;联系方式见置顶文章的底部

NLP(4)--BERT

目录 一、自监督学习 二、BERT的两个问题 三、GLUE 四、BERT与Transformer的关系 五、BERT的训练方式 六、BERT的四个例子 1、语句分类&#xff08;情感分析&#xff09; 2、词性标注 3、立场分析 4、问答系统 七、BERT的后续 1、为什么预训练后的微调可以满足多…

408-2011

一、选择题&#xff08;2分/题&#xff09; 1.设 n 是描述问题规模的非负整数&#xff0c;下列程序片段的时间复杂度是______。 x2; while(x<n/2){x2*x; } A.O() B.O(n) C.O() D.O(n^2) 解答&#xff1a;A 假设执行 y次&#xff0c;则 (2^y)*xn/2,y&a…

艺术与AI:科技与艺术的完美融合

文章目录 艺术创作的新工具生成艺术艺术与数据 AI与互动艺术虚拟现实&#xff08;VR&#xff09;与增强现实&#xff08;AR&#xff09;机器学习与互动性 艺术与AI的伦理问题结语 &#x1f389;欢迎来到AIGC人工智能专栏~艺术与AI&#xff1a;科技与艺术的完美融合 ☆* o(≧▽≦…

机器学习笔记之最优化理论与方法(十)无约束优化问题——共轭梯度法背景介绍

机器学习笔记之最优化理论与方法——共轭梯度法背景介绍 引言背景&#xff1a;共轭梯度法线性共轭梯度法共轭方向共轭VS正交共轭方向法共轭方向法的几何解释 引言 本节将介绍共轭梯度法&#xff0c;并重点介绍共轭方向法的逻辑与几何意义。 背景&#xff1a;共轭梯度法 关于…

VAN LKA、LSKA

Visual Attention Network 2022 大核注意力机制LKA 在本文中&#xff0c;提出了一种新的大核注意力large kernal attention&#xff08;LKA&#xff09;模型&#xff0c; LKA吸收了卷积和自注意的优点&#xff0c;包括局部结构信息、长程依赖性和适应性。同时&#xff0c;避免…

【结合AOP与ReflectUtil对返回数据进行个性化填充展示】

结合AOP与ReflectUtil对返回数据进行个性化填充展示 背景 对于接口列表返回的数据&#xff0c;我们通常有时候会对某些特殊的字段进行转化&#xff0c;或者根据某逻辑进行重新赋值&#xff0c;举个例子&#xff0c; 比如返回的列表数据中有性别sex&#xff0c;我们通常会同时…

柏林噪声 (PERLIN NOISE)

简介 柏林噪声旨在描述自然中的随机效果&#xff0c;它创建的纹理可以直接运用于顶点着色器&#xff0c;而不是生成一张纹理图&#xff0c;然后用传统的纹理映射技术把贴图附加到一个三维物体上。 这也就相当于&#xff0c;纹理将不需要适应表面&#xff0c;我们只需要提供每个…

【算法训练-链表 七】【排序】:链表排序、链表的奇偶重排、重排链表

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【链表的排序】&#xff0c;使用【链表】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&am…

【LeetCode每日一题合集】2023.9.4-2023.9.10(⭐二叉树的重建二分答案拓扑排序)

文章目录 449. 序列化和反序列化二叉搜索树⭐⭐⭐⭐⭐&#xff08;二叉树的重建&#xff09;解法相关题目——297. 二叉树的序列化与反序列化⭐⭐⭐⭐⭐解法——深度优先搜索 2605. 从两个数字数组里生成最小数字哈希表分情况讨论位运算表示集合&#xff0c;分情况讨论&#x1…

Day60|单调栈part03:84.柱状图中最大的矩形

柱状图中最大的矩形 leetcode链接&#xff1a;力扣题目链接 视频链接&#xff1a;单调栈&#xff0c;又一次经典来袭&#xff01; LeetCode&#xff1a;84.柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;…

【多线程】线程安全的单例模式

线程安全的单例模式 饿汉模式懒汉模式单线程版多线程版多线程版(改进) 单例模式能保证某个类在程序中只存在 唯一 一份实例, 而不会创建出多个实例&#xff0c;从而节约了资源并实现数据共享。 比如 JDBC 中的 DataSource 实例就只需要一个. 单例模式具体的实现方式, 分成 “饿…

Unity3D URP 仿蜘蛛侠风格化BloomAO

Unity3D URP 仿蜘蛛侠风格化Bloom&AO BloomBloom效果流程&#xff1a;制作控制面板VolumeComponent.CSCustom Renderer FeatherCustom Renderer PassBloom ShaderComposite Shader 完善Custom Feather风格化AO 总结 本篇文章介绍在URP中如何进行风格化后处理&#xff0c;使…

【MATLAB第74期】#源码分享 | 基于MATLAB的ARX-ARMAX线性自回归移动平均外生模型(结合最小二乘思路)

【MATLAB第74期】#源码分享 | 基于MATLAB的ARX-ARMAX线性自回归移动平均外生模型&#xff08;结合最小二乘思路&#xff09; 根据ARX预测输出和实际输出的误差向量&#xff0c;采用ARMAX算法结合ARX误差建模&#xff0c;对预测值进一步细化。通过将误差描述为白噪声的移动平均…

Spring事务管理: 构建稳健的数据库事务处理

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

C++算法 —— 动态规划(4)子数组

文章目录 1、动规思路简介2、最大子数组和3、环形子数组的最大和4、乘积最大子数组5、乘积为正数的最长子数组长度6、等差数列划分7、最长湍流子数组8、单词拆分9、环绕字符串中唯一的子字符串 每一种算法都最好看完第一篇再去找要看的博客&#xff0c;因为这样会帮你梳理好思路…

商城系统优化

1、DB、模板的渲染速度&#xff08;thymeleaf&#xff09;、静态资源、日志、JVM 数据库的优化&#xff08;参照数据库优化课程&#xff09;使用索引&#xff0c;减少数据库的交互次数、缓存 thymeleaf使用缓存 静态资源&#xff1a;放到nginx中&#xff0c;实现动静分离 2、…

【数学】ABC 319 E

E - Bus Stops 题意&#xff1a; 思路&#xff1a; 感觉思路比较简单 首先注意到每个询问的范围是1e9&#xff0c;不难想到答案一定存在某个循环节&#xff0c;最后一定是要 %T的 那么问题就在于找到这个循环节是什么 猜想循环节为lcm(p1, p2, p3, ....) 用小数据验证 n…

一篇博客教会您SpringMVC文件上传、下载,多文件上传及工具jrebel的使用

目录 一.文件上传 二.文件下载 三.多文件上传 四&#xff0c;jrebel的介绍 前言&#xff1a; 我们之前已经实现了SpringMVC的增删改查&#xff0c;今天这一篇博客教会您SpringMVC文件上传、下载&#xff0c;多文件上传及工具jrebel的使用&#xff0c;希望这篇博客能够给正在…