安卓性能优化——View的绘制流程

Android目前有两种绘制模型:基于软件的绘制模型和硬件加速的绘制模型(从Android 3.0开始全面支持)。

软件绘制

  在基于软件的绘制模型下,CPU主导绘图,视图按照两个步骤绘制:

  • 让View层次结构失效
  • 绘制View层次结构

  当应用程序需要更新它的部分UI时,都会调用内容发生改变的View对象的invalidate()方法。无效(invalidation)消息请求会在View对象层次结构中传递,以便计算出需要重绘的屏幕区域(脏区)。然后,Android系统会在View层次结构中绘制所有的跟脏区相交的区域。不幸的是,这种方法有两个缺点:

  • 绘制了不需要重绘的视图(与脏区域相交的区域)
  • 掩盖了一些应用的bug(由于会重绘与脏区域相交的区域)

注意:在View对象的属性发生变化时,如背景色或TextView对象中的文本等,Android系统会自动的调用该View对象的invalidate()方法。

 

硬件绘制

    在基于硬件加速的绘制模式下,GPU主导绘图,绘制按照三个步骤绘制:

  • 让View层次结构失效
  • 记录、更新显示列表
  • 绘制显示列表

这种模式下,Android系统依然会使用invalidate()方法和draw()方法来请求屏幕更新和展现View对象。但Android系统并不是立即执行绘制命令,而是首先把这些View的绘制函数作为绘制指令记录一个显示列表中,然后再读取显示列表中的绘制指令调用OpenGL相关函数完成实际绘制。另一个优化是,Android系统只需要针对由invalidate()方法调用所标记的View对象的脏区进行记录和更新显示列表。没有失效的View对象则能重放先前显示列表记录的绘制指令来进行简单的重绘工作。

使用显示列表的目的是,把视图的各种绘制函数翻译成绘制指令保存起来,对于没有发生改变的视图把原先保存的操作指令重新读取出来重放一次就可以了,提高了视图的显示速度。而对于需要重绘的View,则更新显示列表,以便下次重用,然后再调用OpenGL完成绘制。

硬件加速提高了Android系统显示和刷新的速度,但它也不是万能的,它有三个缺陷:

  • 兼容性(部分绘制函数不支持或不完全硬件加速,参见文章尾)
  • 内存消耗(OpenGL API调用就会占用8MB,而实际上会占用更多内存)
  • 电量消耗(GPU耗电)

 

系统侧绘制

  Android应用程序在图形缓冲区中绘制好View层次结构后,这个图形缓冲区会被交给SurfaceFlinger服务,而SurfaceFlinger服务再使用OpenGL图形库API来将这个图形缓冲区渲染到硬件帧缓冲区中。

 

一般View的绘制流程

  • 当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由Android framework 处理。绘制是从根节点开始,对布局树进行 measure 和 draw 。整个 View 树的绘图流程在ViewRoot.java类的performTraversals()展开,该方法所做 的工作可简单概况为是否需要重新计算视图大小(measure)、是否需要重新安置视图的位置(layout)、以及是否需要重绘(draw)。
  • 测量(measure)
    • MeasureSpec:测量规格,是一个大小与模式的组合值。一个MeasureSpec封装了从父容器传递给子容器的布局要求。这个MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过计算得出一个针对子View的测量要求。
    • MeasureSpec的值是一个整型(32)将size和mode打包成一个int型,其中最高两位是mode,后30位是size。
      • mode模式
        • 最高两位是00的时候表示"未指定模式",即MeasureSpec.UNSPECIFIED。在未指定控件尺寸时使用。
        • 最高两位是01的时候表示"'精确模式",即MeasureSpec.EXACT。在控件大小确定的情况下使用。
        • 最高两位是11的时候表示"最大模式",即MeasureSpec.AT_MOST。控件大小采用不超过这个值的最大允许值。
    • 测量是多次测量。首先是控件向父容器声明宽高,父容器收到申请后在View底层调用measure(此方法是final的,不能重写)方法将申请打包为对孩子宽高的期望,然后由onMeasure执行测量工作。onMeasure方法的默认实现则是调用setMeasuredDimension方法,这个方法就是给期望设值,如果这两个值一旦设置了,那么意味着对于这个View的测量结束了。
  • 排版(layout)
    • 排版是多次排版。排版是在View底层调用layout方法,然后由onLayout执行排版工作,onLayout是空实现。排版就是确定View具体放在哪个位置,与l、t、r、b四个参数有关。
  • 绘制(draw)
    • 绘制是在View底层调用draw方法,然后由onDraw执行绘制工作。
    • 通过View类的源码得知,绘制分6个步骤
      • Draw the background:绘制背景
      • If necessary, save the canvas' layers to prepare for fading
      • Draw view's content:对View内容绘制
      • Draw children:对当前View的所有子View绘制
      • If necessary, save the canvas' layers to prepare for fading
      • Draw decorations(scrollbars for instance):对View的滚动条绘制
    • 当需要重新绘制时,在主线程调用invalidate方法;在子线程调用postInvalidate方法

ViewGroup绘制流程

  • 在重写的onMeasure()中
    • 找到容器里的孩子:childAt=getChildAt()
    • 对孩子进行测量:childAt.measure()参数为0、0表示不知道控件宽高让系统测量
    • 获取测量后的宽高:getMeasuredWidth()
    • 对容器测量:super.onMeasure(widthMeasureSpec, heightMeasureSpec)
  • 在重写的onLayout()中找到孩子,对孩子进行排版
  • 当定义类继承View时,只需重写onMeasure方法和OnDraw方法;当继承ViewGroup时,只需要重写OnMeasure方法和onLayout方法
  • 如果改变了排版,需要在更改后请求重新排版,调用requestLayout方法。

 

文字绘制流程

先看下图:

在Android中,文字的绘制都是从Baseline处开始的,Baseline往上至字符最高处的距离我们称之为ascent(上坡度),Baseline往下至字符最底处的距离我们称之为descent(下坡度),而leading(行间距)则表示上一行字符的descent到该行字符的ascent之间的距离。

top和bottom文档描述地很模糊,其实这里我们可以借鉴一下TextView对文本的绘制,TextView在绘制文本的时候总会在文本的最外层留出一些内边距,为什么要这样做?因为TextView在绘制文本的时候考虑到了类似读音符号,而top的意思其实就是除了Baseline到字符顶端的距离外还应该包含这些符号的高度,bottom的意思也是一样,一般情况下我们极少使用到类似的符号所以往往会忽略掉这些符号的存在,但是Android依然会在绘制文本的时候在文本外层留出一定的边距,这就是为什么top和bottom总会比ascent和descent大一点的原因。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值