Invalidate/RequestLayout区别

先放上结论

  1. requestLayout会直接递归调用父窗口的requestLayout,直到ViewRootImpl,然后触发peformTraversals,由于mLayoutRequested为true,会导致onMeasure和onLayout被调用。不一定会触发OnDraw
  2. requestLayout触发onDraw可能是因为在在layout过程中发现l,t,r,b和以前不一样,那就会触发一次invalidate,所以触发了onDraw,也可能是因为别的原因导致mDirty非空(比如在跑动画)
  3. view的invalidate不会导致ViewRootImpl的invalidate被调用,而是递归调用父view的invalidateChildInParent,直到ViewRootImpl的invalidateChildInParent,然后触发peformTraversals,会导致当前view被重绘,由于mLayoutRequested为false,不会导致onMeasure和onLayout被调用,而OnDraw会被调用

面试:硬件加速相关_沙漠一只雕得儿得儿的博客-CSDN博客

在硬件加速关闭时,绘制内容会被 CPU 转换成实际的像素,然后直接渲染到屏幕。具体来说,这个「实际的像素」,它是由 Bitmap 来承载的。在界面中的某个 View 由于内容发生改变而调用 invalidate() 方法时,如果没有开启硬件加速,那么为了正确计算 Bitmap 的像素,这个 View 的父 View、父 View 的父 View 乃至一直向上直到最顶级 View,以及所有和它相交的兄弟 View,都需要被调用 invalidate()来重绘。一个 View 的改变使得大半个界面甚至整个界面都重绘一遍,这个工作量是非常大的。

而在硬件加速开启时,前面说过,绘制的内容会被转换成 GPU 的操作保存下来(承载的形式称为 display list,对应的类也叫做 DisplayList),再转交给 GPU。由于所有的绘制内容都没有变成最终的像素,所以它们之间是相互独立的,那么在界面内容发生改变的时候,只要把发生了改变的 View 调用 invalidate() 方法以更新它所对应的 GPU 操作就好,至于它的父 View 和兄弟 View,只需要保持原样。那么这个工作量就很小了。

正是由于上面的原因,硬件加速不仅是由于 GPU 的引入而提高了绘制效率,还由于绘制机制的改变,而极大地提高了界面内容改变时的刷新效率。

结合requestLayout和invalidate与View三大流程关系,有如下图:

1、当视图的内容,可见性发生变化,会调用invalidate,调用后只会触发Draw 过程。
2、requestLayout 会触发Measure、Layout过程,如果尺寸发生改变,则会调用invalidate。
3、当涉及View的尺寸、位置变化时使用requestLayout。
4、当仅仅需要重绘时调用invalidate。
5、如果不确定requestLayout 是否触发invalidate,可在requestLayout后继续调用invalidate。

源码解析:

Android requestLayout与invalidate的区别 - 简书

1. invalidate不调用performMeasure和performLayout

先看下 requestLayout 方法

public void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();//这里设置为truemLayoutRequested = true;scheduleTraversals();}
}

再来看 performTraversal方法的大致逻辑,对应的逻辑我都写到注释中了,挺清晰的。

    private void performTraversals() {......// 调用invalidate的话,mLayoutRequested为false,所以layoutRequested为false// 调用requestLayout,会把mLayoutRequested设置为trueboolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);......if (layoutRequested) {// Clear this now, so that if anything requests a layout in the// rest of this function we will catch it and re-run a full// layout pass.//每次使用performTraversals都会把mLayoutRequested重置为falsemLayoutRequested = false;}// 如果layoutRequested为false,那windowShouldResize一定是false了,不可能可以调用到performMeasure了boolean windowShouldResize = layoutRequested && windowSizeMayChange && ......;if (mFirst || windowShouldResize || ......) {......performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);......} .......// invalidate没有把mLayoutRequested设置为true,因此didLayout将为false,因此也无法调用performLayoutfinal boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);if (didLayout) {performLayout(lp, mWidth, mHeight);......}   ......performDraw()}

2. performDraw方法与requestLayout,view.invalidate的关系

private boolean draw(boolean fullRedrawNeeded) {······if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {······// 执行真正的绘制流程mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);······}······
}

可以看到,如果!dirty.isEmpty()为true,才会去绘制。

而view#invalidate时,会直接把自身的 left,right,top,bottom传递过去,并一路调用到 ViewRootImpl#invalidateRectOnScreen(Rect dirty),该方法中会调用scheduleTraversals()。从而保证可以调用performDraw()。而requestLayout是没有这一步的,因此往往不会执行绘制方法。

上面仅仅说明了单个布局Invalidate/RequestLayout联系,那么如果父布局调用了invalidate,那么子布局会走重绘过程吗?接下来列举这些关系。

这两个方法的使用场景:

ImageView的使用:

可以非常明显看到在ImageView中刷新图片时,如果宽高变了,则需要调用requestLayout。否则直接使用invalidate。

TextView的使用:

textView中大多数的都是两个方法需要同时调用:

子布局/父布局 Invalidate/RequestLayout 关系

子布局Invalidate
如果是软件绘制或者父布局开启了软件缓存绘制,父布局会走重绘过程(前提是WILL_NOT_DRAW标记没设置)。

子布局RequestLayout
父布局会重走Measure、Layout过程。

父布局Invalidate
如果是软件绘制,则子布局会走重绘过程。

父布局RequestLayout
如果父布局尺寸发生了改变,则会触发子布局Measure过程、Layout过程。

比较一下requestLayout和invalidate方法

比较一下requestLayout和invalidate方法 - 掘金

调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法,调用者 View 及其父 View 会从上往下重新进行 measure, layout 流程,一般情况下不会执行 draw 流程(子 View 会通过判断其尺寸/顶点是否发生改变而决定是否重新 measure/layout/draw 流程)。

子线程真不能绘制UI吗

在Activity onCreate里创建子线程并展示对话框:

    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.layout_group);new Thread(new Runnable() {@Overridepublic void run() {TextView textView = new TextView(MainActivity.this);textView.setText("hello thread");Looper.prepare();Dialog dialog = new Dialog(MainActivity.this);dialog.setContentView(textView);dialog.show();Looper.loop();}}).start();}

答案是可以的,接下来分析为什么可以。

在分析ViewRootImpl里requestLayout/invalidate过程中,发现其内部调用了checkThread()方法:

#ViewRootImpl.javavoid checkThread() {//当前调用线程与mThread不是同一线程则会抛出异常if (mThread != Thread.currentThread()) {//简单翻译来说:只有创建了ViewTree的线程才能操作里边的Viewthrow new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");}}

问题的关键是mThread是什么?从哪里来?

#ViewRootImpl.javapublic ViewRootImpl(Context context, Display display) {...//mThread 为Thread类型//也就是说哪个线程执行了构造ViewRootImpl对象,那么mThread就是指向那个线程mThread = Thread.currentThread();...}

而创建ViewRootImpl对象是在调用WindowManager.addView(xx)过程中创建的。
关于WindowManager/Window 请移步:Window/WindowManager 不可不知之事

现在回过头来看Dialog创建就比较明朗了:

1、dialog.show() 调用WindowManager.addView(xx),此时是子线程调用,因此ViewRootImpl对象是在子线程调用的,进而mThread指向子线程。
2、当ViewRootImpl对象构建成功后,调用其setView(xx)方法,里面调用了requestLayout,此时还是子线程。
3、checkThread()判断是同一线程,因此不会抛出异常。

实际上,"子线程不能更新ui" 更合理的表述应为:View只能被构建了ViewTree的线程操作。只是通常来说,Activity 构建ViewTree的线程被称作UI(主)线程,因此才会有上述说法。

postInvalidate 流程

postInvalidate 通过ViewRootImpl 里的handler切换到UI线程,最终执行
invalidate()。
ViewRootImpl 里的hanlder绑定的线程即是UI线程。

源码分析_Android UI何时刷新_Choreographer

我们调用View的requestLayout或者invalidate时,最终都会触发ViewRootImp执行scheduleTraversals()方法。这个方法中ViewRootImp会通过Choreographer来注册个接收Vsync的监听,当接收到系统体层发送来的Vsync后我们就执行doTraversal()来重新绘制界面。通过上面的分析我们调用invalidate等刷新操作时,系统并不会立即刷新界面,而是等到Vsync消息后才会刷新页面。

源码分析_Android UI何时刷新_Choreographer - 简书

Android面试:Invalidate、RequestLayout相关推荐

  1. “金三银四” “阿里” 我去定了,谁也拦不住我,这份《Android面试宝典》说的

    前言: 面试,跳槽,每天都在发生,而对程序员来说"金三银四"更是面试和跳槽的高峰期,跳槽,更是很常见的,对于每个人来说,跳槽的意义也各不相同,可能是一个人更向往一个更大的平台,更好 ...

  2. 金三银四阿里我去定了,谁也拦不住我,这份《Android面试宝典》说的

    前言: 面试,跳槽,每天都在发生,而对程序员来说"金三银四"更是面试和跳槽的高峰期,跳槽,更是很常见的,对于每个人来说,跳槽的意义也各不相同,可能是一个人更向往一个更大的平台,更好 ...

  3. Android面试总结(持续更新修改)

    ###Android面试总结(持续更新修改) 1.Android 的四大组件是哪些,它们的作用? ①Activity是Android程序与用户交互的窗口,是Android构造块中最基本的一种,它需要为 ...

  4. 【转载】Android 面试总结

    Android面试整理 本文转载自 xiao_nian 的Android面试整理 本文转载自 xiao_nian 的Android面试整理 本文转载自 xiao_nian 的Android面试整理 一 ...

  5. Android面试问题汇总

    GitHub持续更新:(声明:本答案为个人收集与总结并非标准答案,仅供参考,如有错误还望指出,谢谢!如有重复可能是常问问题) ArrayList的使用,ArrayList使用过程中有没有遇到过坑. 参 ...

  6. 应该是史上最全最新Java和Android面试题目(自己总结和收集的)

    Android面试题目 Java 基础 int占用几个字节 讲一下常见编码方式? UTF-8编码下中文占几个字节 int和Interger的区别 int.char.long各占多少字节数 string ...

  7. 无意苦争春,一任群芳妒!看完这份2020年度大厂Android面试总结,我直接起飞!

    前言 草色青青柳色黄,桃花历乱李花香.度过了愉快的春节,转眼间春天也就到了.金三银四青铜五,今年面试形势严峻,切勿临时抱佛脚.在博主认为,对于Android面试以及进阶的最佳学习方法莫过于刷题+博客+ ...

  8. Android面试宝典2022-(停止更新,请看面试专栏)

    Android面试宝典2020-持续更新 一.Java基础 1.java基本数据类型和引用类型 2.object equals和==的区别 equals和hashcode的关系? 3.static关键 ...

  9. Android面试大总结

    面试题:你似乎来到了没有知识存在的荒原 - 知乎 字节跳动Android面试题目与答案(2020) 2020年开春最新面试!字节跳动安卓面试题及答案 (已拿到 offer) Android面试必备26 ...

最新文章

  1. CellPress | 医学上人工智能的缺失
  2. matlab中rat=1函数,matlab中的format rat是什么意思
  3. 视频直播技术详解(2)采集
  4. 1.7 元注解作用及使用
  5. 兄弟,敬你是条汉子,请干了广告们~
  6. 第一阶段 XHTML.定位样式
  7. 重命名 docker 容器名
  8. AnalyticDB for MySQL技术架构解析
  9. centos 单机部署 LDAP 服务
  10. 361766103.jpg
  11. ad中pcb双面板怎么设置_html中表格tr的td单元格怎么设置宽度属性
  12. 为何AD快捷键不起作用
  13. JavaScript点击背景图片切换
  14. 联想从国有企业演变成民营集团揭秘(深度)
  15. TeamTalk的windows客户端流程
  16. 慕尼黑工业大学计算机博士申请条件,慕尼黑大学博士条件
  17. 详叙 @vue/cli 和 vue-cli
  18. 悟饭服务器连接中断,英雄联盟连接服务器失败解决方法
  19. 蚂蚱蚂蚱,我的骄傲放纵。
  20. 关于项目编译工具ninja、make、cmake的区别与优劣

热门文章

  1. 【100%通过率】华为OD机试真题 JS 实现【货币单位换算】【2023 Q1 | 100分】
  2. 网站如何经过身份验证_如何在微服务架构中实现安全性?
  3. 深入浅出的给大家分析下现在做抖音短视频还来得及吗?
  4. Android 通过sd卡路径获取指定文件夹的所有数据
  5. EditText 基本用法
  6. 控制文本框只能输入数字字母和汉字
  7. 基于dragonboard410c DHT11模块的驱动移植
  8. jdk与win7配色方案不兼容
  9. EBS开发_AR收款核销事务处理发票
  10. Re: 爱情物语(经典)