Android底层Android系统框架

《深入理解Android:卷三》深入理解控件系统读书笔记(中)

2018-08-14  本文已影响50人  affyzh

本篇文章承接上一篇《深入理解Android:卷三》深入理解控件系统读书笔记(上),从绘制原理,以及动画原理方面,继续深入了解Android的控件系统。

6.4 深入理解控件树的绘制

6.4.1 理解Canvas

Canvas的绘图指令可以分为两部分:

1. Canvas的绘制目标
  对软件Canvas来说,其绘制目标是一个建立在Surface之上的位图Bitmap

软件Canvas的绘制目标
  当通过Surface.lockCanvas()方法获取一个Canvas时会以Surface的内存创建一个Bitmap,通过Canvas所绘制的内容都会直接反映到Surface
  硬件Canvas的绘制目标有两种。一种是HardwareLayer,可以将其理解为一个纹理(GL Texture),或者更简单地认为它是一个硬件加速下的位图(Bitmap)。另一种被称为DisplayList,它并不是一块Buffer,而是一个指令序列。DisplayList会将Canvas的绘制指令编译并优化为硬件绘制指令,并且可以在需要时将这些指令回放到一个HardwareLayer上,而不需要重新使用Canvas进行绘制。
  BitmapHardwareLayer以及DisplayList都可以称为Canvas的画布

从使用角度来说,BitmapHardwareLayer十分相似。开发者可以将一个Bitmap通过Canvas绘制到另一个Bitmap上,也可以将一个HardwareLayer绘制到另一个HardwareLayer上。二者的区别仅在于使用时采用了硬件加速还是软件加速。
另外,将DisplayList回放到HardwareLayer上,与绘制一个BitmapHardwareLayer的结果并没有什么不同。只不过DisplayList并不像Bitmap那样存储了绘制的结果,而是存储了绘制的过程。


2. 坐标变换
  Canvas提供了配套使用的save()/restore()方法用以撤销不需要的变换。它们可以嵌套调用,在这种情况下restore()将会把坐标系状态返回到与其配对的save所创建的保存点。另外,也可以通过保存某个save()的返回值,并将这个返回值传递给restoreToCount()方法的方式来显示地指定一个保存点
  坐标系的变换,使得控件在onDraw()方法中使用Canvas时,使用的是控件自身的坐标系。而这个控件自身的坐标系就是通过Canvas的变换指令从窗口坐标系沿着控件树一步一步变化出来的

6.4.2 View.invalidate()与脏区域

当一个控件的内容发生变化而需要重绘的时候,它会通过invalidate()方法将其需要重绘的区域沿着控件树提交给ViewRootImpl,并保存到ViewRootImplmDirty成员中,最后通过scheduleTraversals()引发一次遍历,进而进行重绘。在回溯过程中,会将沿途的控件标记为脏,即设置PFLAG_DIRTYPFLAGE_DIRTY_OPAQUE(不透明)两者之一添加到View.mPrivateFlags成员中。如果控件时“实心”的,则将标记设为PFLAGE_DIRTY_OPAQUE,否则为PFLAGE_DIRTY。控件系统在重绘过程中区分这两种标记以决定是否为此控件绘制背景,如果是实心的就会跳过背景的绘制工作从而提高效率。
  在一个方法可以连续调用多个控件的invalidate()方法,而不用担心会由于多次重绘而产生的效率问题。另外,多次调用invalidate()方法会使得ViewRootImpl多次接收到设置脏区域的请求,ViewRootImpl会将这些脏区域累加到mDirty中,进而在随后的“遍历”中一次性地完成所有脏区域的重绘。

6.4.3 开始绘制

绘制控件树的入口就在performDraw(),其工作也很简单,一是调用draw()执行实际的绘制工作,二是在必要时,向WMS通知绘制已经完成。draw()方法中产生了硬件加速绘制和软件绘制两个分支,分支的条件为mAttachInfo.mHardwareRender()是否存在并且有效。在ViewRootImpl.setView()中会调用enableHardwareAcceleration()方法,倘若窗口的LayoutParams.flags中包含FLAG_HARDWARE_ACCELERATED标记,这个方法会通过HardwareRenderer.createGlRenderer()创建一个HardwareRender并保存在mAttachInfo中。因此mAttachInfo所保存的HardwareRenderer是否存在便成为区分使用硬件加速绘制还是软件绘制的依据。

6.4.4 软件绘制原理

软件绘制由ViewRootImpl.drawSoftware()完成,主要分为以下4步工作:

1. 纯粹的绘制:View.draw(Canvas)
  纯粹的绘制主要涉及以下4步:

2. 确定子控件的绘制顺序:dispatchDraw()
  绘制顺序依次执行以下4步:

3. 变化坐标系:View.draw(ViewGroup,Canvas,long)
  该方法的工作流程可参见以下几步:

4. 以软件方式绘制控件树的完成流程

控件树绘制的完整流程
软件绘制的流程特点

6.4.5 硬件加速绘制的原理

1. 硬件加速绘制简介
  倘若窗口使用硬件加速,则ViewRootImpl会创建一个HardwareRenderer并保存在mAttachInfo中。HardwareRenderer是用于硬件加速的渲染器,它封装了硬件加速的图形库,并以Android与硬件加速图形库的中间层的身份存在。它负责从AndroidSurface生成一个HardwareLayer,供硬件加速图形库作为绘制的输出目标,并提供一系列工厂方法用于创建硬件加速绘制过程中所需的DisplayListHardwareLayerHardwareCanvas等工具。

2. 硬件加速绘制的入口HardwareRenderer.draw()
drawSoftware()的4个主要工作作为对比来分析该实现:

3. DisplayList的创建与渲染
  总体来看,硬件加速绘制过程中的View.getDisplayList()HardwareCanvas.drawDisplayList()的组合相当于软件绘制过程中的View.draw().
  ·View.getDisplayList()·的实现体现了DisplayList的使用方法。DisplayList渲染与Surface的绘制十分相似,分为如下三个步骤:

4. 硬件加速绘制下的子控件绘制
  软件绘制时的流程:View.draw(Canvas)(自身)->dispatchDraw()->View.draw(ViewGroup, Canvas, long)-> View.draw(Canvas)(子控件)
  硬件加速绘制与软件绘制在前两步完全相同,区别在于第三步,即在View.draw(ViewGroup, Canvas, long),两者几乎完全不同。其根本原因在于硬件加速绘制希望在Canvas上绘制子控件的DisplayList,而不是使用View.onDraw()直接绘制,总结两者不同之处如下:

5. 硬件加速绘制总结

软件绘制与硬件加速绘制的递归方式的差异
硬件加速绘制的流程特点

6.4.6 使用绘图缓存

绘图缓存是指一个Bitmap或一个HardwareLayer,它保存了控件及其子控件的一个快照。绘图缓存有两种类型,即软件缓存(Bitmap)和硬件缓存(HardwareLayer),开发者可以通过View.setLayerType()LAYER_TYPE_SOFTWARELAYER_TYPE_HARDWARE决定此控件使用哪种类型的缓存。默认情况下,控件的缓存类型为LAYER_TYPE_NONE。但值得注意的是,由于硬件缓存依赖于HardwareCanvas,所以在软件绘制的情况下,缓存类型被设置为LAYER_TYPE_HARDWARE的控件仍然会选择使用软件缓存。而在硬件加速绘制的情况下,可以在硬件缓存和软件缓存中任选其一。另外,View.setLayerType()可以通过传入Paint类型的参数用于实现一些显示效果,如透明度、Xfermode以及ColorFilter

1.软件绘制下的软件缓存
  使用软件缓存进行绘制时使用View.buildDrawingCache()/getDrawingCache()canvas.drawBitmap()的组合替代无缓存模式下的View.draw(Canvas)。这种模式和硬件加速绘制时的处理如出一辙,View.buildDrawingCache()的实现方式与View.getDisplayList()方法几乎完全一致,只不过它的目标是一个Bitmap而不是DisplayList。而View.getDrawingCache()则返回mDrawingCachemUnscaleDrawingCache,前者会根据兼容模式进行放大或缩小,用于做绘制时的软件缓存,因为绘制到窗口时需要根据兼容模式进行缩放。而后者反映了控件的真实尺寸,往往被用作控件截图等用途

2. 硬件加速绘制下的绘图缓存
  绘图缓存的实现位于View.getDisplayList(),如果将DisplayList理解为一种缓存,那么硬件加速绘制下的绘图缓存则是在DisplayList上建立的另一级缓存,即二级绘图缓存。
  硬件加速时,使用软件缓存的方式与软件绘制的流程一样,只是绘制到DisplayList上。而使用硬件缓存时,HardwareLayer就是我们所说的硬件缓存,其处理在View.getHardwareLayer()

硬件加速绘制启用绘图缓存后的特点

3. 绘图缓存的利弊
  使用绘图缓存的原则:

6.4.7 控件动画

控件系统存在三种方式实现控件的动画。
1.ValueAnimator,ObjectAnimator,ViewPropertyAimator。当动画运行时,ValueAnimator会将AnimationHandler不断地抛给Choreographer,并在VSYNC事件到来时修改指定的控件属性,控件属性的变化引发invalidate()操作进而进行重绘。
2.LayoutTransition,用于ViewGroup中。
3.View.startAnimation(),与控件绘制内部过程联系紧密,因此针对此方法展开分析动画的实现原理

1. 启动动画
  从startAnimation()方法启动的动画依托于Animation类的子类。启动动画时首先将给定的Animaiton通过setAnimaiton()保存到mCurrentAnimaiton成员中,再通过invalidate()方法触发一次重绘

2. 计算动画变换
  既然动画是以坐标系变换的方式产生效果的,因此动画计算的代码位于View.draw(ViewGroup,Canvas,long)中:

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
      
       .........

        Transformation transformToApply = null;
        //获取startAnimation()所给予的Animation对象
        final Animation a = getAnimation();
        if (a != null) {
            //通过drawAnimation()方法计算当前时间点的变换(Transformation)。结果保存在parent.mChildTransformation中
            more = drawAnimation(parent, drawingTime, a, scalingRequired);
            concatMatrix = a.willChangeTransformationMatrix();
            if (concatMatrix) {
                mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
            }
            transformToApply = parent.getChildTransformation();
        } else {
        //在介绍绘制原理时提到,把transformToApply应用到坐标系变换中
        .............
        }
}

drawAnimation()中,通过Animation.getTransformation()计算当前时间点的变换,并将其保存到父控件的mChildTransformation成员中,然后在View.draw(ViewGroup, Canvas, long)方法中将这个变换以坐标系变换的方式应用到Canvas或者DisplayList中,从而对最终的绘制结果产生影响。倘若动画还将继续,则调用invalidate()以便在下次VSYNC时间到来时进行下一帧的计算与绘制

3. 动画的结束
  动画的结束借由parent.finishAnimatingView()实现,也就是由父控件完成。交给父控件来完成,是因为在执行动画将这个控件从父控件中移除时,ViewGroup会将其从mChildren中移除,但会同时将其放置到mDisappearingChildren数组中,并等待动画结束。由于mDisappearingChildren中的控件依然会得到绘制,因此在执行了ViewGroup.removeView()之后,用户仍然可以看到动画中的控件,直到动画结束后才会消失。另外,LayoutTransition也依赖于这一机制,使得其移出动画被用户看到

上一篇 下一篇

猜你喜欢

热点阅读