Android绘制

·  阅读 640

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)

硬件加速的层级控制

层级开启方式开启后的影响关闭后的影响是否默认开启
Applicationandroid:hardwareAccelerated="true|false"同时控制了Activity/Window/View的开启
Activity/View可以选择关闭硬件加速
同时控制了Activity/Window的关闭
Activity/Window可以单独开启硬件加速
Activityandroid:hardwareAccelerated="true|false"同时控制了Window/View的开启
View可以选择关闭硬件加速
同时控制了View的关闭
WindowWindowManager.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的完整流程如下:

  1. drawBackground()(绘制背景)

  2. canvas.getSaveCount()(记录Canvas状态,为绘制渐变做准备(先保存Canvas坐标,因为要改变))

  3. onDraw()(绘制)

  4. dispathDraw()(分发Draw,绘制子布局调用drawChild()方法)

    1. 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);
      复制代码
  5. 绘制边缘渐变效果

  6. onDrawForeground()(绘制前景)

    前景和mOverlay 是不同的,前景会重新调整尺寸至和View大小一致

  7. drawDefaultFocusHighlight()(绘制默认高亮效果)

但是draw有两条不同的处理分支:

  • 默认的处理是1,3,4,6,7
  • 带边缘绘制的是1,2,3,4,5,6,7

View.draw().PNG

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绘制

  1. **dispatchDraw()**会在内部遍历所有的child

  2. 调用**drawChild(Canvas canvas, View child, long drawingTime)**方法

  3. 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.判断是否绘制完成
  • 根据以下条件判断是否绘制完成(或者关系)
  1. 绘制缓存失效
  2. 渲染节点还没有DisplayList
  3. 渲染节点有DisplayList,但是需要更新
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0|| !renderNode.hasDisplayList() || (mRecreateDisplayList)) {
        //执行绘制逻辑
}
复制代码
3.根据状态执行不同的绘制策略
  • 根据以下条件判断是否需要构建DisplayList

    //如果有DisplayList且该DisplayList无需更新,则说明该View不需要重新走Draw过程
    renderNode.hasDisplayList() && !mRecreateDisplayList
    复制代码
3.1.情况1:不需要重新Draw
  1. 标记该View已经绘制完成且缓存是有效的

    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    复制代码
  2. 继续查看子布局是否需要构建DisplayList,即调用dispatchGetDisplayList()方法

dispatchGetDisplayList()
  • 此方法用于使ViewGroup的子级还原或重新创建其显示列表,通过遍历子布局,并调用它们的重建方法
  1. 通过遍历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);
        	}
    }
    复制代码
  2. 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
  1. 根据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);
    复制代码
  2. 根据layerType(离屏缓存类型),执行不同的绘制逻辑

    1. 如果是软件绘制缓存,则构建缓存,并写入至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()方法下文解析

    2. 如果没有设置软件绘制缓存会通过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);
      }
      复制代码
  3. 最终结束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之后加入了硬件加速,视图绘图缓存在很大程度上被淘汰了。借助硬件加速,中间缓存层在很大程度上是不必要的,并且由于创建和更新硬件的成本很高,容易导致性能的损失
  1. 如果缓存标记为失效或者缓存为空,则通过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实现,用于启用跟踪
  1. 根据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);
    复制代码
  2. 尝试从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;
    }
    复制代码
  3. 给FLAG设置标记,表示软件绘制缓存生效

    if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
          mLayerType != LAYER_TYPE_NONE) {
          //设置标记,说明软件绘制缓存已生效
          mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
    }
    复制代码
  4. 通过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);
    }
    复制代码
  5. 将绘制完毕的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)

  1. 开始执行动画则添加PFLAG_ANIMATION_STARTED的FLAG

    protected void onAnimationStart() {
        mPrivateFlags |= PFLAG_ANIMATION_STARTED;
    }
    复制代码
  2. 如果动画没有结束,根据是否改变界限(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()

  • 表示重新请求布局
  1. 清空测量缓存

    //清空测量缓存
    if (mMeasureCache != null) mMeasureCache.clear();
    复制代码
  2. 添加强制布局标记和重新绘制标记

    //添加强制layout 标记,该标记触发layout
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    //添加重绘标记
    mPrivateFlags |= PFLAG_INVALIDATED;
    复制代码
  3. 通过mParent不断调用requestLayout(),最终在ViewRootImpl requestLayout()中调用scheduleTraversals()方法开启三大流程(会标记layout请求)

    //父布局继续调用requestLayout
    mParent.requestLayout();
    
    public void requestLayout() {
       //是否正在进行layout过程
       if (!mHandlingLayoutInLayoutRequest) {
           //检查线程是否一致
           checkThread();
           //标记有一次layout的请求
           mLayoutRequested = true;
           //开启View 三大流程
           scheduleTraversals();
       }
    }
    复制代码

总结如下:

  1. requestLayout 最终将会触发Measure、Layout 过程
  2. 由于没有设置重绘区域,因此Draw 过程将不会触发
  3. 由于设置的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的尺寸或者内容没有发生改变
  1. 如果设置了跳过绘制则直接return

    if (skipInvalidate()) {return;}
    复制代码
  2. 通过FLAG确定当前view是否已经测量和布局过

    1. 如果经历过则清除绘制标记
      1. 设置需要绘制标记,加上绘制失效标记和清除绘制缓存标记
    //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;
    }
    复制代码
  3. 记录重新绘制的区域至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)

  • 此方法实现软件绘制的绘制刷新
  1. 根据传入的失效区域进行判断,对失效区域做出修正

    1. 如果超出父布局区域则扩大失效区域
    2. 如果没有超出父布局区域则取相交区域
    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;
    }
    复制代码
  2. 如果父布局mParent不为空则也一致调用invalidateChildInParent()方法,直到ViewRootImpl 的实现

    1. 如果失效区域为空,则刷新整个窗口
    2. 如果不为空则对失效区域取并集,最终调用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()都会最终执行此方法,也就是开启三大步骤的入口
  1. 标记绘制请求,屏蔽短时间内的重复请求
  2. 向主线程Looper队列之中发送同步屏障消息,提高异步消息优先级
  3. 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();
      }
}
复制代码
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改