当前位置: 首页 > news >正文

Android View 绘制流程 与invalidate 和postInvalidate 分析--从源码角度

    整个View树的绘制流程是在ViewRootImpl.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为

 根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要布局视图的位置(layout)、以及是否需要重绘

 (draw),所以整个View的绘制过程,总结为三步:

1、 Measure:测量View大小

2 、Layout:对View进行布局

3、  Draw:绘制View:View背景、View内容、View边线、绘制子View(如果有);

 一、performTraversals() 函数触发时机

  在这里提问下,为什么View的绘制是从performTraversals() 函数开始,这个函数是在什么时候触发的呢?

  来通过源码来分析下到底是什么原因,本文的源码是基于最新的源码6.0.1

  首先我们知道是通过 PhoneWindow的 setContentView 将View 加进来的,分析其源码如下:

@Overridepublic voidsetContentView(View view,ViewGroup.LayoutParamsparams) {// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window// decor, when theme attributes and the like are crystalized. Do not check the feature// before this happens.if(mContentParent==null) { installDecor();}else if(!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {mContentParent.removeAllViews();}if(hasFeature(FEATURE_CONTENT_TRANSITIONS)) { view.setLayoutParams(params);finalScene newScene =newScene(mContentParent,view);transitionTo(newScene);}else{
mContentParent.addView(view,params);}mContentParent.requestApplyInsets();finalCallback cb = getCallback();if(cb !=null&& !isDestroyed()) { cb.onContentChanged();} }

然后调用到ViewGroup 的addView 方法:

/*** Adds a child view with the specified layout parameters.**<p><strong>Note:</strong>do not invoke this method from* {@link#draw(android.graphics.Canvas)}, {@link#onDraw(android.graphics.Canvas)},* {@link#dispatchDraw(android.graphics.Canvas)} or any related method.</p>**@paramchildthe child view to add*@paramparamsthe layout parameters to set on the child*/public voidaddView(View child,LayoutParams params) { addView(child,-1,params);}
/*** Adds a child view with the specified layout parameters.**<p><strong>Note:</strong>do not invoke this method from* {@link#draw(android.graphics.Canvas)}, {@link#onDraw(android.graphics.Canvas)},* {@link#dispatchDraw(android.graphics.Canvas)} or any related method.</p>**@paramchildthe child view to add*@paramindexthe position at which to add the child or -1 to add last*@paramparamsthe layout parameters to set on the child*/public voidaddView(View child, intindex,LayoutParams params) {if(DBG) { System.out.println(this+" addView");}if(child ==null) {throw newIllegalArgumentException("Cannot add a null child view to a ViewGroup");}// addViewInner() will call child.requestLayout() when setting the new LayoutParams// therefore, we call requestLayout() on ourselves before, so that the child's request// will be blocked at our levelrequestLayout();invalidate(true);addViewInner(child,index,params, false);}

  看见addView 的方法中调运invalidate方法,这不就真相大白了。

   当我们写一个Activity时,我们一定会通过setContentView方法将我们要展示的界面传入该方法,该方法会讲通过addView追加到id为content的一个FrameLayout(ViewGroup)中,然后addView方法中通过调运invalidate(true)去通知触发ViewRootImpl类的performTraversals()方法,至此递归绘制我们自定义的所有布局。

最终会调用到ViewRootImpl的invalidate方法,从而调用 scheduleTraversals

voidinvalidate() {mDirty.set(0,0,mWidth,mHeight);if(!mWillDrawSoon) { scheduleTraversals();} }

  

 进入到scheduleTraversals 放法中:

voidscheduleTraversals() {if(!mTraversalScheduled) {mTraversalScheduled=true;mTraversalBarrier=mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL,mTraversalRunnable, null);if(!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();} }

  来看看  mTraversalRunnable实现了什么,其代码如下:

final classTraversalRunnableimplementsRunnable {@Overridepublic voidrun() { doTraversal();} }finalTraversalRunnablemTraversalRunnable=newTraversalRunnable();
voiddoTraversal() {if(mTraversalScheduled) {mTraversalScheduled=false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);if(mProfile) { Debug.startMethodTracing("ViewAncestor");} performTraversals();if(mProfile) { Debug.stopMethodTracing();mProfile=false;} } }
   从上面代码可以看出 , scheduleTraversals 通过Handler的Runnable发送一个异步消息,然后调用doTraversal方法,然后最终调用performTraversals()执行重绘。文章开头背景知识介绍说过的,performTraversals就是整个View树开始绘制的调用入口,所以说View调运invalidate方法的实质是层层上传到父级,直到传递到ViewRootImpl后触发了scheduleTraversals方法,从而执行performTraversals 进行重绘;

至此,对于为什么View的绘制入口是在performTraversals 本文分析完成;

二、  View绘制三大过程的具体分析

接下来,来具体分析下View绘制的三大过程,即performTraversals 做了具体什么事情,其源码如下:

private void performTravelsals(){ .... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height) performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... int desiredWindowWidth; int desiredWindowHeight; performLayout(lp, desiredWindowWidth, desiredWindowHeight); ... performDraw(); }

从上述源码分析,也验证来了文章开头分析的View 绘制过程的三大步的正确性;

2.1  Measure 过程分析

先来看看performMeasure  做了什么事情,首先看代码中,该函数会去调用 View .measure 函数;
private voidperformMeasure(intchildWidthMeasureSpec, intchildHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW,"measure");try{mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);}finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW);} }
measure 具体实现如下:
/***<p>* This is called to find out how big a view should be. The parent* supplies constraint information in the width and height parameters.*</p>**<p>* The actual measurement work of a view is performed in* {@link#onMeasure(int,int)}, called by this method. Therefore, only* {@link#onMeasure(int,int)} can and must be overridden by subclasses.*</p>***@paramwidthMeasureSpecHorizontal space requirements as imposed by the* parent*@paramheightMeasureSpecVertical space requirements as imposed by the* parent**@see#onMeasure(int,int)*/public final voidmeasure(intwidthMeasureSpec, intheightMeasureSpec) {booleanoptical =isLayoutModeOptical(this);if(optical !=isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets();intoWidth = insets.left + insets.right;intoHeight = insets.top + insets.bottom;widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec,optical ? -oWidth : oWidth);heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec,optical ? -oHeight : oHeight);}// Suppress sign extension for the low byteslongkey = (long) widthMeasureSpec <<32| (long) heightMeasureSpec &0xffffffffL;if(mMeasureCache==null)mMeasureCache=newLongSparseLongArray(2);if((mPrivateFlags&PFLAG_FORCE_LAYOUT) ==PFLAG_FORCE_LAYOUT|| widthMeasureSpec !=mOldWidthMeasureSpec|| heightMeasureSpec !=mOldHeightMeasureSpec) {// first clears the measured dimension flagmPrivateFlags&= ~PFLAG_MEASURED_DIMENSION_SET;resolveRtlPropertiesIfNeeded();intcacheIndex = (mPrivateFlags&PFLAG_FORCE_LAYOUT) ==PFLAG_FORCE_LAYOUT? -1:mMeasureCache.indexOfKey(key);if(cacheIndex <0||sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag backonMeasure(widthMeasureSpec, heightMeasureSpec);mPrivateFlags3&= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}else{longvalue =mMeasureCache.valueAt(cacheIndex);// Casting a long to int drops the high 32 bits, no mask neededsetMeasuredDimensionRaw((int) (value >>32),(int) value);mPrivateFlags3|=PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}// flag not set, setMeasuredDimension() was not invoked, we raise// an exception to warn the developerif((mPrivateFlags&PFLAG_MEASURED_DIMENSION_SET) !=PFLAG_MEASURED_DIMENSION_SET) {throw new
http://www.gsyq.cn/news/1429084.html

相关文章:

  • 不只是编译:用BES SDK和GCC-Arm工具链,在Windows上打造你的第一个蓝牙音频固件
  • 基于Arduino与TEA5767的FM收音机制作:从原理到实践的完整指南
  • 第25篇|Surface 预览控制:ArkUI 页面如何接住相机画面
  • APP攻防-资产收集篇反代理反证书反模拟器MsgiskLSP模块系统证书
  • 猫抓Cat-Catch:浏览器视频下载神器,一键嗅探网页媒体资源完整指南
  • 解锁小说离线阅读新可能:novel-downloader重新定义数字阅读体验
  • 如何用SMUDebugTool解锁AMD Ryzen处理器的终极性能:完全指南
  • 别再死记硬背了!用Kettle+MySQL手把手还原一个‘客户忠诚度分级’复杂存储过程
  • COM3D2.MaidFiddler:如何用实时编辑器快速修改COM3D2女仆属性
  • 横向辅助驾驶及人机共驾控制策略优化【附仿真】
  • 终极指南:使用msoffcrypto-tool轻松解锁加密Office文档
  • 5分钟搞定200+小说网站:novel-downloader离线阅读终极指南
  • 5步实现加密音频格式转换:开源工具深度解析与应用指南
  • UniApp + Painter实战:从‘社交裂变’到‘数据报告’,解锁小程序图片生成的3个高级应用场景
  • HS2-HF Patch终极指南:如何轻松优化你的Honey Select 2游戏体验
  • 基于SCARA机械臂的DIY写字钟:从运动学算法到嵌入式实现
  • 基于Arduino与游戏手柄的机器人手臂糖果分发系统设计与实现
  • 2026石家庄手表回收真实成交 全套附件价更高 - 薛定谔的梨花猫
  • 专业级直播间数据抓取工具:Live Room Watcher 完整实战指南
  • 机器人基础模型:从预训练到部署的技术演进与应用挑战
  • 基于Arduino与PID控制的自平衡机器人设计与实现
  • 告别‘天书’公式:用动画和Tanner图轻松理解LDPC码的译码原理
  • TinkerCAD仿真入门:三按钮控制RGB LED混色电路设计与实践
  • 2026年上海家装十大品牌靠谱榜单,多维测评优选本地装企 - 商业新知
  • 告别闭集检测:用Open-Vocabulary Detection(OVD)让YOLO也能识别训练集外的物体
  • 算力拉满,GPU 却在摸鱼:深度学习里的访存瓶颈
  • 从RAII设计模式看C++11锁管理:手把手教你实现一个简易版的lock_guard
  • 全品类宠品售卖|活体猫狗、品牌粮品、用品玩具一站式配齐 - 余生黄金回收
  • 用Python的Pulp库搞定NDDF模型:一个环境经济学研究生的效率测算实战笔记
  • 2018技术趋势盘点:AI伦理、数据隐私与平台治理的反思与应对