Android应用开发中View绘制的一些优化点解析

一个通常的错误观念就是使用基本的布局结构(例如:LinearLayout、FrameLayout等)能够在大多数情况下
   产生高效率 的布局。 显然,你的应用程序里添加的每一个控件和每一个布局都需要初始化、布局(layout)、
   绘制 (drawing)。举例来说:嵌入一个LinearLayout会产生一个太深的布局层次。更严重的是,嵌入几个使
   用 layout_weight属性的LinearLayout 将会导致大量的开销,因为每个子视图都需要被测量两次。这是反复解析
   布局文件时重要的一点,例如在ListView或者GridView中使用时。

 观察你的布局
 
     Android SDK 工具箱包括一个称作“ Hierarchy Viewer”的工具,它允许你去在你的应用程序运行时分析
  布局。通过使用这个工具,能帮助你发现你的布局效率上的瓶颈问题。
 
     “ Hierarchy Viewer”工具允许你在已连接的设备或模拟器中选择正在运行的进程,然后呈现出布局层次树
   (layout tree)。每个正方块下的交通灯(见下图) --- 红绿蓝表现出了在测量(measure)、布局(layout)、以及绘制
   (draw)过程中的效率值,这能帮助你定位潜在的问题。
 
        假设ListView 中的一项Item 存在如下(见图 1)布局 :

“Hierarchy Viewer”工具可以在 <sdk>/tools路径下找到。当打开它时,“ Hierarchy Viewer”工具显示了
   所有可用的设备以及运行在这些设备上的进程。点击”Load View Hierarchy”来显示某个你选择的组件的UI布局
  层次。举例来说,图2展现了图1的布局层次树。

在图2中,你可以直观看到这个三层的布局结构是存在一些问题的。点击项体现出了在每个测量(measure)、
    布局(layout)、以及绘制(draw)过程中的时间消耗(见图3)。很明显,该项(LinearLayout)花费了最长的时间去
    测量、布局、绘制,你应该花点精力去优化它们。

完成该布局文件渲染的时间分别为:
                测量过程:0.977ms
                布局过程: 0.167ms
                绘制过程:2.717ms
 
 修改布局文件
 
       由于上图中布局效率的低下是因为一个内嵌的 LinearLayout控件,通过扁平化布局文件----让布局变得
  更浅更宽,而不是变得更窄更深层次 ,这样就能提升效率了。 一个RelativeLayout 作为根节点也能提供如上
  的布局效果(即图1)。 因此,  使用RelativeLayout 改变布局的设计,你可以看到现在我们的布局层次只有2层了。
  新的布局层次树如下:

现在,完成该布局文件渲染的时间分别为:
                      测量过程:0.977ms
                      布局过程:0.167ms
                      绘制过程:2.717ms
  
      也许它只是一点点微小的改进,但这次它会被多次调用,因为是ListView会布局所有的Item,累积起来,
 改进后效果还是非常可观地。

大部分的时间差异是由于使用了带有layout_weight 属性的LinearLayout ,它能减缓测量过程的速度。这仅仅
 是一个例子,即每个布局都应该合适地被使用以及你应该认真考虑是否有必要采用“layout_weight" 属性。
 
 使用Lint工具 

一个好的实践就是在你的布局文件中使用Lint工具去寻求可能优化布局层次的方法。Lint已经取代了Layoutopt
  工具并且它提供了更强大的功能。一些Lint规则如下:

1、使用组合控件 --- 包含了一个  ImageView 以及一个 TextView 控件的 LinearLayout 如果能够作为一个
     组合控件将会被更有效的处理。
 2、合并作为根节点的帧布局(Framelayout)  ----如果一个帧布局时布局文件中的根节点,而且它没有背景图片
  或者padding等,更有效的方式是使用<merge />标签替换该< Framelayout />标签 。

3、无用的叶子节点----- 通常来说如果一个布局控件没有子视图或者背景图片,那么该布局控件时可以被移除
       (由于它处于 invisible状态)。
 
 4、无用的父节点 -----  如果一个父视图即有子视图,但没有兄弟视图节点,该视图不是ScrollView控件或者
    根节点,并且它没有背景图片,也是可以被移除的,移除之后,该父视图的所有子视图都直接迁移至之前父视图
    的布局层次。同样能够使解析布局以及布局层次更有效。
 
 5、过深的布局层次  ----内嵌过多的布局总是低效率地。考虑使用一些扁平的布局控件,例如 RelativeLayout、
      GridLayout ,来改善布局过程。默认最大的布局深度为10 。
 
 
     当使用Eclipse环境开发时,Lint能够自动解决一些问题,提供一些建议以及直接跳转到出错的代码中去核查。
  如果你没有使用Eclipse,Lint也可以通过命令行的方式运行。
  
  使用<include />标签复用布局文件
       尽管Android通过内置了各种各样的控件提供了微小、可复用的交互性元素,也许你需要复用较大的
     组件 ---- 某些特定布局文件 。为了更有效率复用的布局文件,你可以使用<include />以及<merge />
     标签将其他的布局文件加入到当前的布局文件中。

复用布局文件是一种特别强大的方法,它允许你创建可复用性的布局文件。例如,一个包含“Yse”or“No”的
     Button面版,或者是带有文字说明的 Progressbar。复用布局文件同样意味着你应用程序里的任何元素都能从
     繁杂的布局文件提取出来进行单独管理,接着你需要做的只是加入这些独立的布局文件(因为他们都是可复用地)。
     因此,当你通过自定义View创建独立的UI组件时,你可以复用布局文件让事情变得更简单。

1、创建一个可复用性的布局文件

如果你已经知道复用布局的”面貌”,那么创建、定义布局文件( 命名以”.xml”为后缀)。例如,这里是一个来自
 G- Kenya codelab 的布局文件,定义了在每个Activity中都要使用的一个自定义标题 (titlebar.xml):由于这些
  可复用性布局被添加至其他布局文件中,因此,它的每个根视图(root View)最好是精确(exactly)的。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width=”match_parent”
 android:layout_height="wrap_content"
 android:background="@color/titlebar_bg"> 

 <ImageView android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/gafricalogo" />
</FrameLayout>


 2、使用<include />标签

在需要添加这些布局的地方,使用<include />标签 。 例如,下面是一个来自G-Kenya codelab的布局文件,
  它复用了上面列出的“title bar”文件, 该布局文件如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width=”match_parent”
 android:layout_height=”match_parent”
 android:background="@color/app_bg"
 android:gravity="center_horizontal"> 

 <include layout="@layout/titlebar"/> 

 <TextView android:layout_width=”match_parent”
    android:layout_height="wrap_content"
    android:text="@string/hello"
    android:padding="10dp" /> 

 ... 

</LinearLayout>

你也可以在<include />节点中为被添加的布局文件的root View定义特别标识,重写所有layout参数即可(任何
  以“android:layout_”为前缀的属性)。例如:

<include android:id=”@+id/news_title”
   android:layout_width=”match_parent”
   android:layout_height=”match_parent”
   layout=”@layout/title”/>

3、使用<merge />标签

当在布局文件中复用另外的布局时, <merge />标签能够在布局层次消除多余的视图元素。例如,如果你的
   主布局文件是一个垂直地包含两个View的LinearLayout,该布局能够复用在其他布局中,而对任意包含两个View的
   布局文件都需要一个root View(否则, 编译器会提示错误)。然而,在该可复用性布局中添加一个LinearLayout
   作为root View,将会导致一个垂直的LinearLayout包含另外的垂直LinearLayout。内嵌地LinearLayout只能减缓
   UI效率,其他毫无用处可言。
            该复用性布局利用.xml呈现如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width=”match_parent”
  android:layout_height=”match_parent”
  android:background="@color/app_bg"
  android:gravity="horizontal"> 

 <Button
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="@string/add"/> 

 <Button
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="@string/delete"/>
</LinearLayout>

为了避免冗余的布局元素,你可以使用<merge />作为复用性布局文件地root View 。例如:
           使用<merge />标签的布局文件:

<merge xmlns:android="http://schemas.android.com/apk/res/android"> 

 <Button
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="@string/add"/> 

 <Button
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="@string/delete"/> 

</merge>

现在,当你添加该布局文件时(使用<include />标签),系统忽略< merge />节点并且直接添加两个Button去
  取代<include />节点。
  
   越少越好

为了加速视图,从那些调用频繁的活动中减少不必要的代码。在OnDraw()方法中开始绘制,它会给你最大的
   效益。特别低,你也应该减少在onDraw()方法中的内存分配,因为任何内存分配都可能导致内存回收,这将会
   引起不连贯。 在初始化或者动画之间分配对象。绝不要在动画运行时分配内存。

另一方面需要减少onDraw()方法中的开销,只在需要时才调用onDraw()方法。通常invalidate()方法会调用
  onDraw()方法,因此减少对invalidate()的不必要调用。如果可能,调用它的重载版本即带有参数的invalidate()
  方法而不是无参的invalidate()方法。该带参数的方法invalidate()能使draw过程更有效,以及减少对落在该矩形
  区域(参数指定的区域)外视图的不必要重绘 。

注,invalidate()的三个重载版本为:
            1 、public void invalidate (Rect dirty)
            2、public void invalidate (int l, int t, int r, int b)
            3、public void invalidate ()

另外的一个高代价的操作是布局过程(layout)。 任何时刻对View调用requestLayout()方法,Android UI 框架
  都需要遍历整个View树,确定每个视图它们所占用的大小。如果在measure过程中有任何冲突,可能会多次遍历
  View树。UI设计人员有时为了实现某些效果,创建了较深层次的ViewGroup。但这些深层次View树会引发效率
  问题。确保你的View树层次尽可能浅。

如果你有的UI设计是复杂地,你应该考虑设计一个自定义ViewGroup来实现layout过程。不同于内置View控件,
  自定义View能够假定它的每个子View的大小以及形状,同时能够避免为每个子View进行measure过程。 PieChart
  展示了如何继承ViewGroup类。 PieChart带有子View,但它从来没有measure它们。相反,它根据自己的布局算法
  去直接设置每个子View的大小。
         
       如下代码所示:

/**
 * Custom view that shows a pie chart and, optionally, a label.
 */
public class PieChart extends ViewGroup {
 ...
 //
 // Measurement functions. This example uses a simple heuristic: it assumes that
 // the pie chart should be at least as wide as its label.
 //
 @Override
 protected int getSuggestedMinimumWidth() {
  return (int) mTextWidth * 2;
 }
 @Override
 protected int getSuggestedMinimumHeight() {
  return (int) mTextWidth;
 } 

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  // Try for a width based on our minimum
  int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth(); 

  int w = Math.max(minw, MeasureSpec.getSize(widthMeasureSpec)); 

  // Whatever the width ends up being, ask for a height that would let the pie
  // get as big as it can
  int minh = (w - (int) mTextWidth) + getPaddingBottom() + getPaddingTop();
  int h = Math.min(MeasureSpec.getSize(heightMeasureSpec), minh); 

  setMeasuredDimension(w, h);
 } 

 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
  // Do nothing. Do not call the superclass method--that would start a layout pass
  // on this view's children. PieChart lays out its children in onSizeChanged().
 } 

 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  super.onSizeChanged(w, h, oldw, oldh); 

  //
  // Set dimensions for text, pie chart, etc
  //
  // Account for padding 

  ... 

  // Lay out the child view that actually draws the pie.
  mPieView.layout((int) mPieBounds.left,
    (int) mPieBounds.top,
    (int) mPieBounds.right,
    (int) mPieBounds.bottom);
  mPieView.setPivot(mPieBounds.width() / 2, mPieBounds.height() / 2); 

  mPointerView.layout(0, 0, w, h);
  onDataChanged();
 } 

}

使用硬件加速

Android 3.0版本后,Android 2D图形库能在大多数Android设备上使用GPU(图形处理单元)加速。GPU硬件
  加速可以极大的优化多数应用程序,但它并不是每个应用程序的最优选择。Android框架给予你是否在应用程序中
  使用硬件加速的控制力。

值得注意的是,我们必须手动在配置文件中设置应用程序API级别为11或者更高级别,即在 AndroidManifest.xml进行如下配置:

 <uses-sdk android:targetSdkVersion="11"/>

一旦你开启了硬件加速,你可能看不到效率的提升。Mobile GPUs 善于处理特定的任务,例如:伸缩、旋转、
   平移图片。它也有一些不擅长处理的任务,例如:绘制直线或曲线。常言道物尽其用,扬长避短,尽可能让GPU
   处理它擅长的任务,减少让其处理弱势任务的。

在PieChart 示例中,例如,相对来说绘制一个圆形是比较耗费资源的。每次旋转引起的重绘导致UI的迟缓。
   解决办法就是让View来呈现该圆形,并且设置该View的layer type属性为 LAYER_TYPE_HARDWARE,因此GPU
   能够缓存静态图片。示例中该View作为 PieChart类的内部类存在,减少了为了实现这个方法的代码开销。

private class PieView extends View { 

 public PieView(Context context) {
  super(context);
  if (!isInEditMode()) {
   setLayerType(View.LAYER_TYPE_HARDWARE, null);
  }
 } 

 @Override
 protected void onDraw(Canvas canvas) {
  super.onDraw(canvas); 

  for (Item it : mData) {
   mPiePaint.setShader(it.mShader);
   canvas.drawArc(mBounds,
     360 - it.mEndAngle,
     it.mEndAngle - it.mStartAngle,
     true, mPiePaint);
  }
 } 

 @Override
 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  mBounds = new RectF(0, 0, w, h);
 } 

 RectF mBounds;
}

改变之后,只有View第一次显示的时候才会调用PieChart.PieView.onDraw()方法。在应用程序的其他
   时间,绘制的图像将会作为图片缓存,重绘时GPU将任意旋转图像。

然而这只是一个折中手段。缓存图片作为硬件层导致 video memory开销,video memory却是一种受限制的
 资源。 出于这个原因,在PieChart.PieView的最终版本上,只有在用户滑动时才设置它的layer type属性为
  LAYER_TYPE_HARDWARE。在其他时间,仅仅设置它的layer type属性为 LAYER_TYPE_HARDWARE,这
  允许GPU停止缓存图片。

最后,不要忘记分析你的代码。在一个View上做的优化技术可能会在其他View上产生不好的影响。

(0)

相关推荐

  • 深入理解Android中View绘制的三大流程

    前言 最近对Android中View的绘制机制有了一些新的认识,所以想记录下来并分享给大家.View的工作流程主要是指measure.layout.draw这三大流程,即测量.布局和绘制,其中measure确定View的测量宽高,layout根据测量的宽高确定View在其父View中的四个顶点的位置,而draw则将View绘制到屏幕上,这样通过ViewGroup的递归遍历,一个View树就展现在屏幕上了. 说的简单,下面带大家一步一步从源码中分析: Android的View是树形结构的: 基本概

  • Android使用自定义View绘制渐隐渐现动画

    实现了一个有趣的小东西:使用自定义View绘图,一边画线,画出的线条渐渐变淡,直到消失.效果如下图所示: 用属性动画或者渐变填充(Shader)可以做到一笔一笔的变化,但要想一笔渐变(手指不抬起边画边渐隐),没在Android中找到现成的API可用.所以,自己做了一个. 基本的想法是这样的: 在View的onTouchEvent中记录触摸点,生成一条一条的线LineElement,放在一个List中.给每个LineElement配置一个Paint实例. 在onDraw中绘制线段. 变换LineE

  • 浅谈Android View绘制三大流程探索及常见问题

    View绘制的三大流程,指的是measure(测量).layout(布局).draw(绘制) measure负责确定View的测量宽/高,也就是该View需要占用屏幕的大小,确定完View需要占用的屏幕大小后,就会通过layout确定View的最终宽/高和四个顶点在手机界面上的位置,等通过measure和layout过程确定了View的宽高和要显示的位置后,就会执行draw绘制View的内容到手机屏幕上. 在详细介绍这三大流程之前,需要简单了解一下ViewRootImpl,View绘制的三大步骤

  • Android View如何绘制

    上文说道了Android如何测量,但是一个漂亮的控件我只知道您长到哪儿,这当然不行.只需要简单重写OnDraw方法,并在Canvas(画布)对象上调用那根五颜六色的画笔就能够画出这控件"性感"的外表.那么View又是如何进行绘制了? 要了解View如何绘制,就需要了解canvas(画布)是什么?paint(画笔)能够做什么. Ⅰ.canvas就是表示一块画布,你可以在上面画你所朝思暮想的东西.当我们重写onDraw方法的时候,就能够拿到一个Canvas对象,这个就是你的舞台,画你所思所

  • Android自定义View实现绘制虚线的方法详解

    前言 说实话当第一次看到这个需求的时候,第一反应就是Canvas只有drawLine方法,并没有drawDashLine方法啊!这咋整啊,难道要我自己做个遍历不断的drawLine?不到1秒,我就放弃这个想法了,因为太恶心了.方法肯定是有的,只不过我不知道而已. 绘制方法 最简单的方法是利用ShapeDrawable,比如说你想用虚线要隔开两个控件,就可以在这两个控件中加个View,然后给它个虚线背景. 嗯,理论上就是这样子的,实现上也很简单. <!-- drawable 文件 --> <

  • Android View 绘制流程(Draw)全面解析

    前言 前几篇文章,笔者分别讲述了DecorView,measure,layout流程等,接下来将详细分析三大工作流程的最后一个流程--绘制流程.测量流程决定了View的大小,布局流程决定了View的位置,那么绘制流程将决定View的样子,一个View该显示什么由绘制流程完成.以下源码均取自Android API 21. 从performDraw说起 前面几篇文章提到,三大工作流程始于ViewRootImpl#performTraversals,在这个方法内部会分别调用performMeasure

  • Android视图的绘制流程(上) View的测量

    综述 View的绘制流程可以分为三大步,它们分别是measure,layout和draw过程.measure表示View的测量过程,用于测量View的宽度和高度:layout用于确定View在父容器的位置:draw则是负责将View绘制到屏幕中.下面主要来看一下View的Measure过程. 测量过程 View的绘制流程是从ViewRoot的performTraversals方法开始的,ViewRoot对应ViewRootImpl类.ViewRoot在performTraversals中会调用p

  • Android应用开发中View绘制的一些优化点解析

    一个通常的错误观念就是使用基本的布局结构(例如:LinearLayout.FrameLayout等)能够在大多数情况下    产生高效率 的布局. 显然,你的应用程序里添加的每一个控件和每一个布局都需要初始化.布局(layout).    绘制 (drawing).举例来说:嵌入一个LinearLayout会产生一个太深的布局层次.更严重的是,嵌入几个使    用 layout_weight属性的LinearLayout 将会导致大量的开销,因为每个子视图都需要被测量两次.这是反复解析    布

  • Android 游戏开发中绘制游戏触摸轨迹的曲线图

    本篇文章主要来讲解怎样绘制游戏触摸轨迹的曲线图. 我们在onTouchEvent方法中,可以获取到触摸屏幕时手指触摸点的x.y坐标,如何用这些点形成一条无规则轨迹并把这条无规则轨迹曲线显示在屏幕上就是本篇文章的主旨内容. Android Path类 Android提供了一个Path类 , 顾名思义这个类可以设置曲线路径轨迹.任何无规则的曲线实际上都是由若干条线段组成,而线段的定义为两点之间最短的一条线.path类就 可以记录这两点之间的轨迹,那么若干个Path 就是我们须要绘制的无规则曲线. 下

  • Android中View绘制流程详细介绍

    创建Window Window即窗口,这个概念在AndroidFramework中的实现为android.view.Window这个抽象类,这个抽象类是对Android系统中的窗口的抽象.在介绍这个类之前,我们先来看看究竟什么是窗口呢? 实际上,窗口是一个宏观的思想,它是屏幕上用于绘制各种UI元素及响应用户输入事件的一个矩形区域.通常具备以下两个特点: 独立绘制,不与其它界面相互影响: 不会触发其它界面的输入事件: 在Android系统中,窗口是独占一个Surface实例的显示区域,每个窗口的S

  • Android编程开发中ListView的常见用法分析

    本文实例讲述了Android编程开发中ListView的常见用法.分享给大家供大家参考,具体如下: 一.ListView的使用步骤 ListView的使用通常有以下三个要素: (1)ListView中每个条目的布局; (2)填充进入ListView中的内容; (3)将内容与页面进行整合的Adapter. 因此,使用ListView也通常有以下三个步骤: (1)创建ListView条目的布局文件(或使用Android SDK提供的布局); (2)创建填充进入ListView中的内容,如字符串.图片

  • Android编程开发中的正则匹配操作示例

    本文实例讲述了Android编程开发中的正则匹配操作.分享给大家供大家参考,具体如下: 在Android开发中,可能也会遇到一下输入框的合法性验证,这时候最常用的就应该是正则表达式去做一些匹配了,下面就常用的正则匹配做一下介绍 1. 手机号码的验证 根据实际开发于2009年9月7日最新统计: 中国电信发布中国3G号码段:中国联通185,186;中国移动188,187;中国电信189,180共6个号段. 移动:134.135.136.137.138.139.150.151.157(TD).158.

  • 详解Android应用开发中Scroller类的屏幕滑动功能运用

    今天给大家介绍下Android中滑屏功能的一个基本实现过程以及原理初探,最后给大家重点讲解View视图中scrollTo 与scrollBy这两个函数的区别 .   首先 ,我们必须明白在Android View视图是没有边界的,Canvas是没有边界的,只不过我们通过绘制特定的View时对Canvas对象进行了一定的操作,例如 : translate(平移).clipRect(剪切)等,以便达到我们的对该Canvas对象绘制的要求 ,我们可以将这种无边界的视图称为"视图坐标"----

  • Android WindowManager深层理解view绘制实现流程

    目录 前言 setContentView()流程 WindowManager.addView流程 前言 又是一年一度的1024程序员节了,今天不写点什么总感觉对不起这个节日.想来想去,就写点关于View的绘制.本文不会重点讲View绘制三大回调函数:onMeasure.onLayout.onDraw,而是站在Android framework的角度去分析一下View的绘制. View是如何被渲染到屏幕中的? ViewRoot.DecorView.Activity.Window.WindowMan

  • Android程序开发中单选按钮(RadioGroup)的使用详解

    在还没给大家介绍单选按钮(RadioGroup)的使用,先给大家展示下效果图吧: xml文件 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_heig

  • Android应用开发中单元测试分析

    本文主要和大家分享如何在Android应用开发过程中如何进行单元测试,个人在做项目的过程中,觉得单元测试很有必要,以保证我们编写程序的正确性.下面我们先大概了解下单元测试,以及单元测试的作用.        单元测试(又称为模块测试)是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作.程序单元是应用的最小可测试部件.在过程化编程中,一个单元就是单个程序.函数.过程等:对于面向对象编程,最小单元就是方法,包括基类(超类).抽象类.或者派生类(子类)中的方法.单元测试是由程序员自己来完成

随机推荐