1.View Flag
由于View之中的状态很多,Flag是通过二进制的管理方式实现处理的
static final int PFLAG_WANTS_FOCUS = 0x00000001;
static final int PFLAG_FOCUSED = 0x00000002;
static final int PFLAG_SELECTED = 0x00000004;
static final int PFLAG_IS_ROOT_NAMESPACE = 0x00000008;
static final int PFLAG_HAS_BOUNDS = 0x00000010;
static final int PFLAG_DRAWN = 0x00000020;
/**
* When this flag is set, this view is running an animation on behalf of its
* children and should therefore not cancel invalidate requests, even if they
* lie outside of this view's bounds.
*/
static final int PFLAG_DRAW_ANIMATION = 0x00000040;
static final int PFLAG_SKIP_DRAW = 0x00000080;
static final int PFLAG_REQUEST_TRANSPARENT_REGIONS = 0x00000200;
static final int PFLAG_DRAWABLE_STATE_DIRTY = 0x00000400;
static final int PFLAG_MEASURED_DIMENSION_SET = 0x00000800;
static final int PFLAG_FORCE_LAYOUT = 0x00001000;
static final int PFLAG_LAYOUT_REQUIRED = 0x00002000;
private static final int PFLAG_PRESSED = 0x00004000;
static final int PFLAG_DRAWING_CACHE_VALID = 0x00008000;
/**
* Flag used to indicate that this view should be drawn once more (and only once
* more) after its animation has completed.
*/
static final int PFLAG_ANIMATION_STARTED = 0x00010000;
private static final int PFLAG_SAVE_STATE_CALLED = 0x00020000;
/**
* Indicates that the View returned true when onSetAlpha() was called and that
* the alpha must be restored.
*/
static final int PFLAG_ALPHA_SET = 0x00040000;
/**
* Set by {@link #setScrollContainer(boolean)}.
*/
static final int PFLAG_SCROLL_CONTAINER = 0x00080000;
/**
* Set by {@link #setScrollContainer(boolean)}.
*/
static final int PFLAG_SCROLL_CONTAINER_ADDED = 0x00100000;
/**
* View flag indicating whether this view was invalidated (fully or partially.)
*
* @hide
*/
static final int PFLAG_DIRTY = 0x00200000;
/**
* Mask for {@link #PFLAG_DIRTY}.
*
*/
static final int PFLAG_DIRTY_MASK = 0x00200000;
/**
* Indicates whether the background is opaque.
*
*/
static final int PFLAG_OPAQUE_BACKGROUND = 0x00800000;
/**
* Indicates whether the scrollbars are opaque.
*
*/
static final int PFLAG_OPAQUE_SCROLLBARS = 0x01000000;
/**
* Indicates whether the view is opaque.
*
*/
static final int PFLAG_OPAQUE_MASK = 0x01800000;
/**
* Indicates a prepressed state;
* the short time between ACTION_DOWN and recognizing
* a 'real' press. Prepressed is used to recognize quick taps
* even when they are shorter than ViewConfiguration.getTapTimeout().
*
*/
private static final int PFLAG_PREPRESSED = 0x02000000;
/**
* Indicates whether the view is temporarily detached.
*/
static final int PFLAG_CANCEL_NEXT_UP_EVENT = 0x04000000;
/**
* Indicates that we should awaken scroll bars once attached
*
* PLEASE NOTE: This flag is now unused as we now send onVisibilityChanged
* during window attachment and it is no longer needed. Feel free to repurpose it.
*
*/
private static final int PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
/**
* Indicates that the view has received HOVER_ENTER. Cleared on HOVER_EXIT.
*/
private static final int PFLAG_HOVERED = 0x10000000;
/**
* Flag set by {@link AutofillManager} if it needs to be notified when this view is clicked.
*/
private static final int PFLAG_NOTIFY_AUTOFILL_MANAGER_ON_CLICK = 0x20000000;
static final int PFLAG_ACTIVATED = 0x40000000;
/**
* Indicates that this view was specifically invalidated, not just dirtied because some
* child view was invalidated. The flag is used to determine when we need to recreate
* a view's display list (as opposed to just returning a reference to its existing
* display list).
*/
static final int PFLAG_INVALIDATED = 0x80000000;
复制代码
是否包含指定Flag
//是否包含PFLAG_FORCE_LAYOUT flag 包含为1 不包含为0
view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0
复制代码
清除指定Flag
//清除指定Flag
view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT
复制代码
设置指定Flag
//设置指定Flag
view.mPrivateFlags |= View.PFLAG_FORCE_LAYOUT
复制代码
检查Flag是否改变
int old = mViewFlags;
mViewFlags = (mViewFlags & ~mask) | (flags & mask);
//检查Flag是否改变 changed ==1 为改变
int changed = mViewFlags ^ old;
复制代码
2.硬件加速
-
使⽤ CPU 绘制到 Bitmap,然后把 Bitmap 贴到屏幕,就是软件绘制
-
使⽤ CPU 把绘制内容转换成 GPU 操作,交给 GPU,由 GPU 负责真正的绘制,就叫硬件绘制
-
GPU 绘制简单图形(例如⽅形、圆形、直线)在硬件设计上具有先天优势,会更快,流程得到优化(重绘流程涉及的内容更少)
兼容性。由于使⽤ GPU 的绘制(暂时)⽆法完成某些绘制,因此对于⼀些特定的 API,需要关闭硬件加速来转回到使⽤ CPU 进⾏绘制。
基本原理
- 软件绘制是将Canvas的一系列操作写入到Bitmap里
- 硬件加速绘制,是每个View 都有一个RenderNode,当需要绘制的时候,从RenderNode里获取一个RecordingCanvas
- 与软件绘制一样,也是调用Canvas一系列的API,只不过调用的这些API记录为一系列的操作行为存放在DisplayList
- 当一个View录制结束,再将DisplayList交给RenderNode
- 此时,绘制的步骤已经记录在RenderNode里,到此,针对单个View的硬件绘制完成,这个过程也称作为DisplayList的构建过程。
判断方法
canvas.isHardwareAccelerated()
复制代码
根据判断方法获取到的Canvas对象也是不同的
- RecordingCanvas(支持硬件加速的Canvas)
- Cavas(普通Canvas)
硬件加速的层级控制
层级 | 开启方式 | 开启后的影响 | 关闭后的影响 | 是否默认开启 |
---|---|---|---|---|
Application | android:hardwareAccelerated=”true|false” | 同时控制了Activity/Window/View的开启 Activity/View可以选择关闭硬件加速 |
同时控制了Activity/Window的关闭 Activity/Window可以单独开启硬件加速 |
是 |
Activity | android:hardwareAccelerated=”true|false” | 同时控制了Window/View的开启 View可以选择关闭硬件加速 |
同时控制了View的关闭 | 是 |
Window | WindowManager.LayoutParams. FLAG_HARDWARE_ACCELERATED |
无法直接关闭Window的硬件加速 | 无法直接关闭Window的硬件加速 | |
View | 无法直接开启View的硬件加速 | 通过 View.LAYER_TYPE_SOFTWARE 或android:layerType=”software” 关闭 |
-
Application
- 通过android:hardwareAccelerated=”true|false”设置
- Application层级的硬件加速开启,也同时控制了Activity/Window/View的开启,Activity/View可以选择关闭硬件加速
- Application层级的硬件加速关闭,也同时控制了Activity/Window的关闭,Activity/Window可以单独开启硬件加速
- 默认开启
-
Activity
- 通过android:hardwareAccelerated=”true|false”设置
- Activity层级的硬件加速开启,也同时控制了Window/View的开启,View可以选择关闭硬件加速
- Activity层级的硬件加速关闭,也同时控制了View的关闭
- 默认开启
-
Window
-
//开启硬件加速 getWindow().setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 复制代码
通过此方法开启,无法直接关闭Window的硬件加速
-
-
View
-
//禁用硬件加速 View.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 复制代码
无法直接开启View的硬件加速
-
3.Canvas的创建管理
三大流程的是在ViewRootImpl的performTraversals()中开启的,绘制的部分也是根据是否支持硬件加速去获得不同的Canvas
Cavas(普通Canvas)
- 普通Canvas的获取是Surface.lockCanvas(Rect inOutDirty)实现的,根据传入的Rect 确定Canvas的大小,具体的创建是在Native层实现的
RecordingCanvas(支持硬件加速的Canvas)
- 每个View 都有一个RenderNode,当需要绘制的时候,从RenderNode里获取一个RecordingCanvas
- RecordingCanvas的管理用到 pool 的概念,通过一个池来实现回收(release)复用(acquire)
- 最后在 finally 中,会对 Canvas进行释放
- pool并没有初始 size,或者说初始 size 就是 0 ,最大 size 是在 DisplayListCanvas中指定为 POOL_LIMIT = 25,DisplayListCanvas还额外指定了 drawBitmap()方法中 bitmap 最大的 size 100M
4.离屏缓存
LAYER_TYPE_NONE(不使用绘制缓存)
- Canvas类型为:CompatibleCanvas
- 从ViewRootImpl.drawSoftware()开始,通过mSurface.lookCanvas(dirty)生成了Canvas对象
- 整个ViewTree共享同一个Canvas对象
LAYER_TYPE_SOFTWARE(使用软件绘制缓存)
- Canvas的类型为:Canvas
- 从View.buildDrawingCacheImpl()开始,通过new Canvas()生成了Canvas对象,并存入AttachInfo之中
- 每个使用软件缓存的View都生成了新的Canvas,如果从AttachInfo之中能获取到则重复使用
如果是此类型的离屏缓冲,并且关闭了硬件加速
LAYER_TYPE_HARDWARE(使用硬件绘制缓存)
- Canvas类型为:RecordingCanvas
- 从View.UpdateDIsplayListIfDirty()开始,通过renderNode.beginRecording()生成了Canvas对象
- 每个支持硬件加速的View都从新生成了Canvas对象
5.动画区别
- Animation:不会改变View的真实值,结束后会还原
- Animator:会改变View属性的真实值,结束后不会还原
6.绘制层级以及实现方法
- View和ViewGroup的绘制层级以及实现方法都是不同的,区别如下
类型 | 绘制层级 | 实现方法 |
---|---|---|
View | 背景—>内容—>前景 | 实现了draw() ondraw()和dispathDraw()是空实现 |
ViewGroup | 背景—>内容—>子布局的层级—>前景 | 实现了dispathDraw() |
7.View.draw()
- 和测量,布局一样,绘制也有调度方法,就是draw()
源码流程
draw的完整流程如下:
-
drawBackground()(绘制背景)
-
canvas.getSaveCount()(记录Canvas状态,为绘制渐变做准备(先保存Canvas坐标,因为要改变))
-
onDraw()(绘制)
-
dispathDraw()(分发Draw,绘制子布局调用drawChild()方法)
-
mOverlay 绘制(绘制在内容之上,在前景之下)
//代码调用实现 View viewGroup = findViewById(R.id.myviewgroup); //给overLay 指定一个Drawable Drawable drawable = ContextCompat.getDrawable(this, R.drawable.shapeme); //设置Drawable 的尺寸 drawable.setBounds(0, 0, 400, 58); //为overLay添加Drawable viewGroup.getOverlay().add(drawable); 复制代码
-
-
绘制边缘渐变效果
-
onDrawForeground()(绘制前景)
前景和mOverlay 是不同的,前景会重新调整尺寸至和View大小一致
-
drawDefaultFocusHighlight()(绘制默认高亮效果)
但是draw有两条不同的处理分支:
- 默认的处理是1,3,4,6,7
- 带边缘绘制的是1,2,3,4,5,6,7
8.ViewGroup.dispatchDraw()
- dispatchDraw()其实就是ViewGroup的调度View绘制的方法,还实现了动画支持,Padding裁切等功能,我们逐个分析
动画支持
- dispatchDraw()方法之中根据FLAG对动画完成时,调用完成监听
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&mLayoutAnimationController.isDone() && !more) {
//擦除绘制缓存并在绘制下一帧后通知侦听器,因为动画结束后,drawChild()会引起一个额外的invalidate()
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
@Override
public void run() {
//调用动画监听
notifyAnimationListener();
}
};
post(end);
}
private void notifyAnimationListener() {
mGroupFlags &= ~FLAG_NOTIFY_ANIMATION_LISTENER;
mGroupFlags |= FLAG_ANIMATION_DONE;
if (mAnimationListener != null) {
final Runnable end = new Runnable() {
@Override
public void run() {
mAnimationListener.onAnimationEnd(mLayoutAnimationController.getAnimation());
}
};
post(end);
}
//最终会调用一次invalidate()
invalidate(true);
}
复制代码
Padding裁切
-
根据FLAG做判断,当设置了padding后,子布局的canvas需要根据padding进行裁减。
//FLAG 判断 final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; if (clipToPadding) { //因此需要对canvas坐标进行变换,先保存其状态 clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); //裁切 canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop, mScrollX + mRight - mLeft - mPaddingRight, mScrollY + mBottom - mTop - mPaddingBottom); } 复制代码
-
如果要设置禁用裁切,使用如下方法
setClipToPadding(false) android:clipToPadding="false" 复制代码
调度View绘制
-
**dispatchDraw()**会在内部遍历所有的child
-
调用**drawChild(Canvas canvas, View child, long drawingTime)**方法
-
drawChild(Canvas canvas, View child, long drawingTime)方法会调用**View.draw(Canvas canvas, ViewGroup parent, long drawingTime)**方法
此时调用的draw(Canvas canvas, ViewGroup parent, long drawingTime)其实并不是上文的draw(),而且此方法只能由ViewGroup.dispatchDraw()调用
9.View.draw(Canvas canvas, ViewGroup parent, long drawingTime)
- 此方法是View专门基于图层类型和硬件加速去区分不同的渲染行为
硬件加速绘制
- 硬件加速绘制的条件是:canvas支持硬件加速+该Window支持硬件加速
boolean drawingWithRenderNode = mAttachInfo != null&& mAttachInfo.mHardwareAccelerated&& hardwareAcceleratedCanvas;
复制代码
- 如果支持硬件加速则最终调用updateDisplayListIfDirty()方法
if (drawingWithRenderNode) {
//该View支持硬件加速
//则尝试构建DisplayList,并返回renderNode
renderNode = updateDisplayListIfDirty();
......
}
复制代码
View.updateDisplayListIfDirty()
- 获取View的RenderNode,如果需要则更新其DisplayList
1.获取RenderNode
-
每个View构造的时候都会创建一个RenderNode:mRenderNode,称之为渲染节点
final RenderNode renderNode = mRenderNode; 复制代码
-
获取到RenderNode之后首先要判断是否支持硬件加速,如果不支持,直接返回
if (!canHaveDisplayList()) { return renderNode;} 复制代码
2.判断是否绘制完成
- 根据以下条件判断是否绘制完成(或者关系)
- 绘制缓存失效
- 渲染节点还没有DisplayList
- 渲染节点有DisplayList,但是需要更新
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0|| !renderNode.hasDisplayList() || (mRecreateDisplayList)) {
//执行绘制逻辑
}
复制代码
3.根据状态执行不同的绘制策略
-
根据以下条件判断是否需要构建DisplayList
//如果有DisplayList且该DisplayList无需更新,则说明该View不需要重新走Draw过程 renderNode.hasDisplayList() && !mRecreateDisplayList 复制代码
3.1.情况1:不需要重新Draw
-
标记该View已经绘制完成且缓存是有效的
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; 复制代码
-
继续查看子布局是否需要构建DisplayList,即调用dispatchGetDisplayList()方法
dispatchGetDisplayList()
- 此方法用于使ViewGroup的子级还原或重新创建其显示列表,通过遍历子布局,并调用它们的重建方法
-
通过遍历child,调用recreateChildDisplayList()方法尝试重建
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)) { //重建子布局DisplayList recreateChildDisplayList(child); } } 复制代码
-
recreateChildDisplayList()内部也是通过FLAG去判断是否需要重建,最终又调用updateDisplayListIfDirty()方法
private void recreateChildDisplayList(View child) { //判断是否需要重建 child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0; child.mPrivateFlags &= ~PFLAG_INVALIDATED; //调用子布局重建方法 child.updateDisplayListIfDirty(); child.mRecreateDisplayList = false; } 复制代码
3.2.情况2:需要构建DisplayList,并最终调用Draw
-
根据layout阶段确定的View坐标,以及layerType(离屏缓存类型)创建出RecordingCanvas(支持硬件加速的Canvas)对象,并开启录制(renderNode.beginRecording(width, height))
//layout 阶段确定的View的坐标此时用到了 int width = mRight - mLeft; int height = mBottom - mTop; //获取当前设置的layerType int layerType = getLayerType(); //从renderNode里获取Canvas对象,Canvas的尺寸初始化为View的尺寸 //该Canvas是RecordingCanvas类型,简单理解为用来录制的Canvas final RecordingCanvas canvas = renderNode.beginRecording(width, height); 复制代码
-
根据layerType(离屏缓存类型),执行不同的绘制逻辑
-
如果是软件绘制缓存,则构建缓存,并写入至Bitmap之中,最终将Bitmap绘制到Canvas里
//如果是软件绘制缓存 if (layerType == LAYER_TYPE_SOFTWARE) { //构建缓存 buildDrawingCache(true); //将绘制操作写入Bitmap里 Bitmap cache = getDrawingCache(true); if (cache != null) { //将该Bitmap绘制到Canvas里 canvas.drawBitmap(cache, 0, 0, mLayerPaint); } } 复制代码
buildDrawingCache()方法下文解析
-
如果没有设置软件绘制缓存会通过FLAG确认是否需要跳过绘制自身内容(包括内容、前景、背景等),如果跳过则直接绘制child,如果不跳过则调用draw()发起绘制自身
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { //该View不需要绘制自身内容(包括内容、前景、背景等) //直接发起绘制子布局的请求 dispatchDraw(canvas); drawAutofilledHighlight(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } if (debugDraw()) { debugDrawFocus(canvas); } } else { //需要绘制自身 draw(canvas); } 复制代码
-
-
最终结束canvas录制,并将录制产生的结果:DisplayList交给renderNode
finally { //最后结束canvas录制,并将录制产生的结果:DisplayList交给renderNode renderNode.endRecording(); setDisplayListProperties(renderNode); } 复制代码
软件缓存绘制
- 软件缓存绘制的条件是:设置了离屏软件绘制缓存或,View不支持硬件加速绘制
if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {
//两者满足其一则是软件缓存绘制
//1、设置了离屏软件绘制缓存 2、View不支持硬件加速绘制
if (layerType != LAYER_TYPE_NONE) {
//可能设置了软件缓存或者硬件缓存,此时硬件缓存当做软件缓存来使用
layerType = LAYER_TYPE_SOFTWARE;
//绘制到软件缓存
buildDrawingCache(true);
}
//取出软件缓存
cache = getDrawingCache(true);
}
复制代码
- 最终调用buildDrawingCache()绘制到软件缓存,通过getDrawingCache()取出软件缓存
View.buildDrawingCache()
- 此方法是用于构建缓存的,但是在API11之后加入了硬件加速,视图绘图缓存在很大程度上被淘汰了。借助硬件加速,中间缓存层在很大程度上是不必要的,并且由于创建和更新硬件的成本很高,容易导致性能的损失
-
如果缓存标记为失效或者缓存为空,则通过buildDrawingCacheImpl()方法构建缓存
public void buildDrawingCache(boolean autoScale) { //如果缓存标记为失效或者缓存为空 if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?mDrawingCache == null : mUnscaledDrawingCache == null)) { try { //构建缓存 buildDrawingCacheImpl(autoScale); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } } 复制代码
View.buildDrawingCacheImpl()
- 此方法是内部专用的buildDrawingCache实现,用于启用跟踪
-
根据Bitmap不存在或者Bitmap尺寸和View不一致,则进行创建
int width = mRight - mLeft; int height = mBottom - mTop; boolean clear = true; Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache; ..... //bitmap 不存在或者bitmap与View尺寸不一致,则创建 bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),width, height, quality); 复制代码
-
尝试从attachInfo中获取Canvas,如果为空则创建,获取Canvas实例之后则关联bitmap
Canvas canvas; if (attachInfo != null) { canvas = attachInfo.mCanvas; if (canvas == null) { //第一次,AttachInfo里并没有Canvas canvas = new Canvas(); } //关联bitmap canvas.setBitmap(bitmap); attachInfo.mCanvas = null; } 复制代码
-
给FLAG设置标记,表示软件绘制缓存生效
if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated || mLayerType != LAYER_TYPE_NONE) { //设置标记,说明软件绘制缓存已生效 mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID; } 复制代码
-
通过FLAG确认是否需要跳过绘制自身内容(包括内容、前景、背景等),如果跳过则直接绘制child,如果不跳过则调用draw()发起绘制自身,通用实现
//同样调用公共方法 if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); drawAutofilledHighlight(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } } else { draw(canvas); } 复制代码
-
将绘制完毕的Canvas记录至attachInfo之中,以便下次使用
//记录下Canvas至attachInfo之中,下次创建直接使用 if (attachInfo != null) {attachInfo.mCanvas = canvas;} 复制代码
动画处理
- 在draw(Canvas canvas, ViewGroup parent, long drawingTime)方法会调用applyLegacyAnimation()方法,实现动画处理
//获取动画
final Animation a = getAnimation();
if (a != null) {
//调用动画处理
//more为动画是否结束
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
}
复制代码
applyLegacyAnimation(ViewGroup parent, long drawingTime,Animation a, boolean scalingRequired)
-
开始执行动画则添加PFLAG_ANIMATION_STARTED的FLAG
protected void onAnimationStart() { mPrivateFlags |= PFLAG_ANIMATION_STARTED; } 复制代码
-
如果动画没有结束,根据是否改变界限(Bounds)进行不同的处理
情况1:如果没有改变界限,根据传入的layoutAnimation进行处理坐标,并添加FLAG,最终调用parent.invalidate(mLeft, mTop, mRight, mBottom),完成特定区域的刷新
if (!a.willChangeBounds()) { if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==ViewGroup.FLAG_OPTIMIZE_INVALIDATE) { parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED; } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) { //孩子需要绘制动画(可能不在屏幕上),因此要调用invalidate() parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION; parent.invalidate(mLeft, mTop, mRight, mBottom); } } 复制代码
情况2:如果改变了界限,则进行界限计算,把计算后的结果传入parent.invalidate()方法之中,完成特定区域的刷新
else { if (parent.mInvalidateRegion == null) { parent.mInvalidateRegion = new RectF(); } final RectF region = parent.mInvalidateRegion; a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,invalidationTransform); parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION; final int left = mLeft + (int) region.left; final int top = mTop + (int) region.top; //孩子需要绘制动画(可能不在屏幕上),因此要调用invalidate() parent.invalidate(left, top, left + (int) (region.width() + .5f),top + (int) (region.height() + .5f)); } 复制代码
10.View.requestLayout()
- 表示重新请求布局
-
清空测量缓存
//清空测量缓存 if (mMeasureCache != null) mMeasureCache.clear(); 复制代码
-
添加强制布局标记和重新绘制标记
//添加强制layout 标记,该标记触发layout mPrivateFlags |= PFLAG_FORCE_LAYOUT; //添加重绘标记 mPrivateFlags |= PFLAG_INVALIDATED; 复制代码
-
通过mParent不断调用requestLayout(),最终在ViewRootImpl requestLayout()中调用scheduleTraversals()方法开启三大流程(会标记layout请求)
//父布局继续调用requestLayout mParent.requestLayout(); public void requestLayout() { //是否正在进行layout过程 if (!mHandlingLayoutInLayoutRequest) { //检查线程是否一致 checkThread(); //标记有一次layout的请求 mLayoutRequested = true; //开启View 三大流程 scheduleTraversals(); } } 复制代码
总结如下:
- requestLayout 最终将会触发Measure、Layout 过程
- 由于没有设置重绘区域,因此Draw 过程将不会触发
- 由于设置的PFLAG_FORCE_LAYOUT,则在layout阶段的setFram()方法之中,会根据尺寸是否发生变化去调用invalidate()方法
11.View.invalidate()
- 表示使当前绘制内容无效,需要重新绘制,最终回调用View.invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate)方法实现
View.invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate)
- 此方法时真正实现重新绘制的地方,参数invalidateCache为False则表示View的尺寸或者内容没有发生改变
-
如果设置了跳过绘制则直接return
if (skipInvalidate()) {return;} 复制代码
-
通过FLAG确定当前view是否已经测量和布局过
- 如果经历过则清除绘制标记
- 设置需要绘制标记,加上绘制失效标记和清除绘制缓存标记
//PFLAG_DRAWN 表示此前该View已经绘制过 PFLAG_HAS_BOUNDS表示该View已经layout过,确定过坐标了 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 (fullInvalidate) { //默认true mLastIsOpaque = isOpaque(); //清除绘制标记 mPrivateFlags &= ~PFLAG_DRAWN; } //需要绘制 mPrivateFlags |= PFLAG_DIRTY; if (invalidateCache) { //1、加上绘制失效标记 //2、清除绘制缓存有效标记 //这两标记在硬件加速绘制分支用到 mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } 复制代码
- 如果经历过则清除绘制标记
-
记录重新绘制的区域至damge中,调用ViewGroup.invalidateChild()方法进行重新绘制
final AttachInfo ai = mAttachInfo; final ViewParent p = mParent; if (p != null && ai != null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; //记录需要重新绘制的区域 damge,该区域为该View尺寸 damage.set(l, t, r, b); //p 为该View的父布局 //调用父布局的invalidateChild p.invalidateChild(this, damage); } 复制代码
ViewGourp.invalidateChild(View child, final Rect dirty)
- 此方法为了处理所有child的绘制刷新,但是是final方法,不能重写
1.根据是否支持硬件加速执行不同的处理
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
//如果是支持硬件加速,则走该分支
onDescendantInvalidated(child, child);
return;
}
复制代码
情况1:硬件加速分支 ViewGourp.onDescendantInvalidated(@NonNull View child, @NonNull View target)
- 此方法的目的是不断向上寻找其父布局,并将父布局PFLAG_DRAWING_CACHE_VALID 标记清空,也就是绘制缓存清空,如果需要重新绘制则加上PFLAG_INVALIDATED FLAG,最终向上寻找到ViewRootImpl.onDescendantInvalidated()
public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
mPrivateFlags |= (target.mPrivateFlags & PFLAG_DRAW_ANIMATION);
if ((target.mPrivateFlags & ~PFLAG_DIRTY_MASK) != 0) {
//此处都会走
mPrivateFlags = (mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY;
//清除绘制缓存有效标记
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
if (mLayerType == LAYER_TYPE_SOFTWARE) {
//如果是开启了软件绘制,则加上绘制失效标记
mPrivateFlags |= PFLAG_INVALIDATED | PFLAG_DIRTY;
//更改target指向
target = this;
}
if (mParent != null) {
//调用父布局的onDescendantInvalidated
mParent.onDescendantInvalidated(this, target);
}
}
复制代码
ViewRootImpl.onDescendantInvalidated()
- 最终在方法之中调用自己的invalidate(),方法内部计算出脏区域(需要重写绘制的数据),最终调用scheduleTraversals()方法开启三大流程
public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
if ((descendant.mPrivateFlags & PFLAG_DRAW_ANIMATION) != 0) {
mIsAnimating = true;
}
invalidate();
}
void invalidate() {
//mDirty 为脏区域,也就是需要重绘的区域
//mWidth,mHeight 为Window尺寸
mDirty.set(0, 0, mWidth, mHeight);
if (!mWillDrawSoon) {
//开启View 三大流程
scheduleTraversals();
}
}
复制代码
情况2:软件绘制分支 ViewGroup.invalidateChildInParent(final int[] location, final Rect dirty)
- 此方法实现软件绘制的绘制刷新
-
根据传入的失效区域进行判断,对失效区域做出修正
- 如果超出父布局区域则扩大失效区域
- 如果没有超出父布局区域则取相交区域
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))!= FLAG_OPTIMIZE_INVALIDATE) { //修正重绘的区域 dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,location[CHILD_TOP_INDEX] - mScrollY); if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) { //如果允许子布局超过父布局区域展示,则该dirty 区域需要扩大 dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } final int left = mLeft; final int top = mTop; if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) { //默认会走这 //如果不允许子布局超过父布局区域展示,则取相交区域 if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {dirty.setEmpty();} } //记录偏移,用以不断修正重绘区域,使之相对计算出相对屏幕的坐标 location[CHILD_LEFT_INDEX] = left; location[CHILD_TOP_INDEX] = top; } 复制代码
-
如果父布局mParent不为空则也一致调用invalidateChildInParent()方法,直到ViewRootImpl 的实现
- 如果失效区域为空,则刷新整个窗口
- 如果不为空则对失效区域取并集,最终调用scheduleTraversals()方法开启三大流程
public ViewParent invalidateChildInParent(int[] location, Rect dirty) { //线程检查 checkThread(); if (dirty == null) { //脏区域为空,则默认刷新整个窗口 invalidate(); return null; } else if (dirty.isEmpty() && !mIsAnimating) { return null; } invalidateRectOnScreen(dirty); return null; } private void invalidateRectOnScreen(Rect dirty) { final Rect localDirty = mDirty; //合并脏区域,取并集 localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); if (!mWillDrawSoon && (intersected || mIsAnimating)) { //开启View的三大绘制流程 scheduleTraversals(); } } 复制代码
12.ViewRootImpl.scheduleTraversals()
- 无论是requestLayout()还是invalidate()都会最终执行此方法,也就是开启三大步骤的入口
- 标记绘制请求,屏蔽短时间内的重复请求
- 向主线程Looper队列之中发送同步屏障消息,提高异步消息优先级
- Choreographer任务处理调度
void scheduleTraversals() {
if (!mTraversalScheduled) {
//标记一次绘制请求,屏蔽短时间内的重复请求
mTraversalScheduled = true;
//往主线程Looper队列里放同步屏障消息,用来控制异步消息的执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//放入mChoreographer队列里,将mTraversalRunnable放入队列
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
复制代码
13.Choreographer
- Vsync脉冲信号到来之时,Choreographer通过对Vsync脉冲信号周期的调整,来控制每一帧绘制操作的时机,以提供一个稳定的Message处理的时机
60Hz 的刷新率的处理逻辑
Choreographer初始化
1.Choreographer实例获取
- Choreographer是线程单例的,保存在ThreadLocal区域,并且还绑定了Looper对象供内部的Handler使用
//Choreographer保存在ThreadLocal区域
private static final ThreadLocal<Choreographer> sThreadInstance =new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
// 获取当前线程的 Looper
Looper looper = Looper.myLooper();
// 构造 Choreographer 对象
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// 初始化 FrameHandler
mHandler = new FrameHandler(looper);
// 初始化 DisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC? new FrameDisplayEventReceiver(looper, vsyncSource): null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
// 初始化 CallbacksQueues
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
复制代码
2.FrameDisplayEventReceiver
- Vsync的注册,申请,接收都是通过FrameDisplayEventReceiver完成的,在Choreographer构造就会创建出此对象
- FrameDisplayEventReceiver 的初始化过程中,通过 BitTube(本质是一个 socket pair),来传递和请求 Vsync 事件,当 SurfaceFlinger 收到 Vsync 事件之后,通过 appEventThread 将这个事件通过 BitTube 传给 DisplayEventDispatcher ,DisplayEventDispatcher 通过 BitTube 的接收端监听到 Vsync 事件之后,回调 Choreographer.FrameDisplayEventReceiver.onVsync ,触发开始一帧的绘制
private Choreographer(Looper looper, int vsyncSource) {
.....
// 初始化 DisplayEventReceiver
mDisplayEventReceiver = USE_VSYNC? new FrameDisplayEventReceiver(looper, vsyncSource): null;
......
}
public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource);
}
public DisplayEventReceiver(Looper looper, int vsyncSource) {
......
mMessageQueue = looper.getQueue();
mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
vsyncSource);
}
复制代码
- FrameDisplayEventReceiver继承自DisplayEventReceiver ,内部有三个关键方法
onVsync(long timestampNanos, long physicalDisplayId, int frame)
- Vsync信号回调
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
......
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
复制代码
run()
- 执行doFrame
@Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame);
}
复制代码
scheduleVsync()
- 请求Vsync信号,最终调用了Native实现
public void scheduleVsync() {
......
nativeScheduleVsync(mReceiverPtr);
......
}
复制代码
3.Choreographer FrameHandler
- FrameHandler是Choreographer之中的处理Handler,接收到对应msg之后分发到不同的实现
private final class FrameHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DO_FRAME:
//开始下一帧渲染
doFrame(System.nanoTime(), 0);
break;
case MSG_DO_SCHEDULE_VSYNC:
//请求Vsync
doScheduleVsync();
break;
case MSG_DO_SCHEDULE_CALLBACK:
//处理CallBack
doScheduleCallback(msg.arg1);
break;
}
}
}
复制代码
Choreographer的处理逻辑
- 当FrameDisplayEventReceiver进入onVsync()方法之后,post了自己,最终调用到doFrame()方法之中,整体步骤如下
1.计算掉帧逻辑
- Vsync脉冲信号到来会标记StartTime,在真正执行doFrame()中,标记EndTime,两次标记的事件差就是Vsync处理时间延迟,也就是掉帧
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
......
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
//时间标记相减就是掉帧
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
}
......
}
......
}
复制代码
2.记录绘制信息
- Choreographer 中 FrameInfo 来负责记录帧的绘制信息,doFrame 执行的时候,会把每一个关键节点的绘制时间记录下来
3.执行CallBack
- 最终会根据不同类型执行不同的CallBack,具体处理的类型如下:
- Input事件的处理
- Animation动画的处理
- Traversal的处理(measure,layout,draw)
void doFrame(long frameTimeNanos, int frame) {
......
// 处理 CALLBACK_INPUT Callbacks
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
// 处理 CALLBACK_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
// 处理 CALLBACK_INSETS_ANIMATION Callbacks
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
// 处理 CALLBACK_TRAVERSAL Callbacks
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
// 处理 CALLBACK_COMMIT Callbacks
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
......
}
复制代码
CALLBACK_TRAVERSAL
- 此时结合上文的ViewRootImpl.scheduleTraversals()之中最终调用了此回调,其实最终分发到performTraversals()方法实现三大步骤
void scheduleTraversals() {
if (!mTraversalScheduled) {
//标记一次绘制请求,屏蔽短时间内的重复请求
mTraversalScheduled = true;
//往主线程Looper队列里放同步屏障消息,用来控制异步消息的执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//放入mChoreographer队列里,将mTraversalRunnable放入队列
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 真正开始执行 measure、layout、draw
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 这里把 SyncBarrier remove
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 真正开始
performTraversals();
}
}
private void performTraversals() {
// measure 操作
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// layout 操作
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
// draw 操作
if (!cancelDraw && !newSurface) {
performDraw();
}
}
复制代码