Android中View绘制优化

Android中View绘制优化

译序



         最近一直在做锁屏界面,之前也写过关于锁屏界面的一些简单原理,未曾想自己真正去深入理解锁屏时,才

  发觉锁屏框架真是又大又复杂,主要体现在如下两个方面:

 

            1、界面的组成以及更新机制;

            2、对电源管理的控制,在锁屏界面会禁用系统的电源管理,自己接管屏幕亮度的控制。

 

       当然还有更多的逻辑细节处理,只能耐着性子去研究了。。

 

       通过对本次锁屏界面的处理,才发现自己对View绘制还是不熟透,很多东西也没有去潜心研究,导致自己在

  真正做项目时候才手忙脚乱的。因此,借着这次机会,也把Android 4.0 developer这些先进的知识(山人一直

 沉浸在Android 2.2中)给过了一下,真是妙处多多。

 

 

    开头: 为了避免歧义,先将Android “Layout”一次的意思进行说明,主要有如下三个方面:

                   1、统称,即如何摆放UI,UI呈现效果等;

                   2、布局文件  ,即/res/layout/xxx.xml  ;

                   3、布局过程  ,Android绘制过程中的 layout过程;

                   4、一些布局控件,例如LinearLayout、FrameLayout等 ;



  正文:


                                改善布局效率(Layout Performace)

 


   
 本文翻译地址:http://developer.android.com/training/improving-layouts/index.html

 


         布局是Android应用程序重要的一部分,它与用户体验有着直接联系。如果一个布局是糟糕的,它将产生一个

  消耗内存与低效UI应用程序。 Android SDK 及它包含的工具都能帮助你定位在布局过程中隐藏的问题,通过对

  这些课程的学习,你能够以很小的内存代价去实现流畅的平滑界面。

 

  课程如下:

 

  1 、优化布局层次

            同样地,一个复杂的网页会延长加载时间,你的布局层次如果太复杂也能引发一些效率问题。本课程

      告知你如何利用 SDK的工具去观察你的布局以及发现布局过程的瓶颈问题。

 

  2、使用<include />标签复用布局文件

            如果应用程序的UI在多处重复某些布局结构,本课程向你展示如何创建高效、可重用的布局结构,然后

      以合适的 UI布局文件包含它们。

               

  3、按需加载View视图

           除了简单地在另外的布局文件中包括一个布局组件,你可能想在需要的时候才将视图显现出来,有的时候

     是在Activity运行之后。本课程告诉你如何改进布局初始化行为---- 按需加载布局文件的某个视图。

                  

 4、如何使ListView流畅滑动

          如果你构建了一个ListView实例呈现那些包含复杂或者大容量数据的列表项,这可能会影响ListView的流畅

     滑动。本课程提供了一些如何让滑动过程更加流畅的建议。

                  

                

   译一 :


                                     优化布局层次

 


 本文地址: http://developer.android.com/training/improving-layouts/optimizing-layout.html

 

         一个通常的错误观念就是使用基本的布局结构(例如:LinearLayout、FrameLayout等)能够在大多数情况下

   产生高效率 的布局。 显然,你的应用程序里添加的每一个控件和每一个布局都需要初始化、布局(layout)、

   绘制 (drawing)。举例来说:嵌入一个LinearLayout会产生一个太深的布局层次。更严重的是,嵌入几个使

   用 layout_weight属性的LinearLayout 将会导致大量的开销,因为每个子视图都需要被测量两次。这是反复解析

   布局文件时重要的一点,例如在ListView或者GridView中使用时。


 观察你的布局

 

     Android SDK 工具箱包括一个称作“ Hierarchy Viewer”的工具,它允许你去在你的应用程序运行时分析

  布局。通过使用这个工具,能帮助你发现你的布局效率上的瓶颈问题。

 

     “ Hierarchy Viewer”工具允许你在已连接的设备或模拟器中选择正在运行的进程,然后呈现出布局层次树

   (layout tree)。每个正方块下的交通灯(见下图) --- 红绿蓝表现出了在测量(measure)、布局(layout)、以及绘制

   (draw)过程中的效率值,这能帮助你定位潜在的问题。

 

        假设ListView 中的一项Item 存在如下(见图 1)布局 :


                                 

                                        图1:ListView某项Item的布局效果图


     “Hierarchy Viewer工具可以在 <sdk>/tools路径下找到。当打开它时,“ Hierarchy Viewer”工具显示了

   所有可用的设备以及运行在这些设备上的进程。点击”Load View Hierarchy”来显示某个你选择的组件的UI布局

  层次。举例来说,图2展现了图1的布局层次树。


                             

                                           图2:使用LinearLayout的布局树


      在图2中,你可以直观看到这个三层的布局结构是存在一些问题的。点击项体现出了在每个测量(measure)、

    布局(layout)、以及绘制(draw)过程中的时间消耗(见图3)。很明显,该项(LinearLayout)花费了最长的时间去

    测量、布局、绘制,你应该花点精力去优化它们。


                                                          

                                                      图3: 某个LinearLayout的绘制时间


     完成该布局文件渲染的时间分别为:

                测量过程:0.977ms

                布局过程: 0.167ms

                绘制过程:2.717ms

 

 修改布局文件

 

       由于上图中布局效率的低下是因为一个内嵌的 LinearLayout控件,通过扁平化布局文件----让布局变得

  更浅更宽,而不是变得更窄更深层次 ,这样就能提升效率了。 一个RelativeLayout 作为根节点也能提供如上

  的布局效果(即图1)。 因此,  使用RelativeLayout 改变布局的设计,你可以看到现在我们的布局层次只有2层了。

  新的布局层次树如下:


                         

                                    图4:使用RelativeLayout的布局树

 

         现在,完成该布局文件渲染的时间分别为:

                      测量过程:0.977ms

                      布局过程:0.167ms

                      绘制过程:2.717ms

   

      也许它只是一点点微小的改进,但这次它会被多次调用,因为是ListView会布局所有的Item,累积起来,

 改进后效果还是非常可观地。


      大部分的时间差异是由于使用了带有layout_weight 属性的LinearLayout ,它能减缓测量过程的速度。这仅仅

 是一个例子,即每个布局都应该合适地被使用以及你应该认真考虑是否有必要采用“layout_weight" 属性。

 

 使用Lint工具 (译者注:ADT插件更新到最新的16.0后的工具)

 

       关于Lint的使用更多请看:Android Lint(官方代码优化利器)

     

        一个好的实践就是在你的布局文件中使用Lint工具去寻求可能优化布局层次的方法。Lint已经取代了Layoutopt 

  工具并且它提供了更强大的功能。一些Lint规则如下:


  1、使用组合控件 --- 包含了一个  ImageView 以及一个 TextView 控件的 LinearLayout 如果能够作为一个

     组合控件将会被更有效的处理。

           关于这点,更多请看:  http://stackoverflow.com/questions/8318765/how-do-i-use-a-compound-drawable-instead-of-a-linearlayout-that-contains-an-imag

 

 2、合并作为根节点的帧布局(Framelayout)  ----如果一个帧布局时布局文件中的根节点,而且它没有背景图片

  或者padding等,更有效的方式是使用<merge />标签替换该< Framelayout />标签 。


          关于这点,更多请看 : <<android merge之布局>>

                                                    <<Android里merge和include标签的使用>>

                                        

 3、无用的叶子节点----- 通常来说如果一个布局控件没有子视图或者背景图片,那么该布局控件时可以被移除

       (由于它处于 invisible状态)。

 

 4、无用的父节点 -----  如果一个父视图即有子视图,但没有兄弟视图节点,该视图不是ScrollView控件或者

    根节点,并且它没有背景图片,也是可以被移除的,移除之后,该父视图的所有子视图都直接迁移至之前父视图

    的布局层次。同样能够使解析布局以及布局层次更有效。

 

 5、过深的布局层次  ----内嵌过多的布局总是低效率地。考虑使用一些扁平的布局控件,例如 RelativeLayout、

      GridLayout ,来改善布局过程。默认最大的布局深度为10 。

 

 

     当使用Eclipse环境开发时,Lint能够自动解决一些问题,提供一些建议以及直接跳转到出错的代码中去核查。

  如果你没有使用Eclipse,Lint也可以通过命令行的方式运行。更多关于Lint的可用信息请参看:《Android Lint》 


Android中View绘制优化

译二:


                                          使用<include />标签复用布局文件



  翻译地址:http://developer.android.com/training/improving-layouts/reusing-layouts.html#Merge



           尽管Android通过内置了各种各样的控件提供了微小、可复用的交互性元素,也许你需要复用较大

     组件 ---- 某些特定布局文件 。为了更有效率复用的布局文件,你可以使用<include />以及<merge />

     标签将其他的布局文件入到当前的布局文件中。


          复用布局文件是一种特别强大的方法,它允许你创建可复用性的布局文件。例如,一个包含“Yse”or“No”的

     Button面版,或者是带有文字说明的 Progressbar。复用布局文件同样意味着你应用程序里的任何元素都能从

     繁杂的布局文件提取出来进行单独管理,接着你需要做的只是加入这些独立的布局文件(因为他们都是可复用地)。

     因此,当你通过自定义View创建独立的UI组件时,你可以复用布局文件让事情变得更简单。

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


        如果你已经知道复用布局的”面貌”,那么创建、定义布局文件( 命名以”.xml”为后缀)。例如,这里是一个来自

 G- Kenya codelab 的布局文件,定义了在每个Activity中都要使用的一个自定义标题 (titlebar.xml):由于这些

  可复用性布局被添加至其他布局文件中,因此,它的每个根视图(root View)最好是精确(exactly)的。

[java]  view plain copy print ?
  1. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width=”match_parent”  
  3.     android:layout_height="wrap_content"  
  4.     android:background="@color/titlebar_bg">  
  5.   
  6.     <ImageView android:layout_width="wrap_content"  
  7.                android:layout_height="wrap_content"   
  8.                android:src="@drawable/gafricalogo" />  
  9. </FrameLayout>  


 2、使用<include />标签


          在需要添加这些布局的地方,使用<include />标签 。 例如,下面是一个来自G-Kenya codelab的布局文件,

  它复用了上面列出的“title bar”文件, 该布局文件如下:

[java]  view plain copy print ?
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:orientation="vertical"   
  3.     android:layout_width=”match_parent”  
  4.     android:layout_height=”match_parent”  
  5.     android:background="@color/app_bg"  
  6.     android:gravity="center_horizontal">  
  7.   
  8.     <include layout="@layout/titlebar"/>  
  9.   
  10.     <TextView android:layout_width=”match_parent”  
  11.               android:layout_height="wrap_content"  
  12.               android:text="@string/hello"  
  13.               android:padding="10dp" />  
  14.   
  15.     ...  
  16.   
  17. </LinearLayout>  

       
          你也可以在<include />节点中为被添加的布局文件的root View定义特别标识,重写所有layout参数即可(任何

  以“android:layout_”为前缀的属性)。例如:

[java]  view plain copy print ?
  1. <include android:id=”@+id/news_title”  
  2.          android:layout_width=”match_parent”  
  3.          android:layout_height=”match_parent”  
  4.          layout=”@layout/title”/>  

 3、使用<merge />标签


        当在布局文件中复用另外的布局时, <merge />标签能够在布局层次消除多余的视图元素。例如,如果你的

   主布局文件是一个垂直地包含两个View的LinearLayout,该布局能够复用在其他布局中,而对任意包含两个View的

   布局文件都需要一个root View(否则, 编译器会提示错误)。然而,在该可复用性布局中添加一个LinearLayout

   作为root View,将会导致一个垂直的LinearLayout包含另外的垂直LinearLayout。内嵌地LinearLayout只能减缓

   UI效率,其他毫无用处可言。

            该复用性布局利用.xml呈现如下:

[java]  view plain copy print ?
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.         android:orientation="vertical"   
  3.         android:layout_width=”match_parent”  
  4.         android:layout_height=”match_parent”  
  5.         android:background="@color/app_bg"  
  6.         android:gravity="horizontal">  
  7.   
  8.     <Button  
  9.         android:layout_width="fill_parent"   
  10.         android:layout_height="wrap_content"  
  11.         android:text="@string/add"/>  
  12.   
  13.     <Button  
  14.         android:layout_width="fill_parent"   
  15.         android:layout_height="wrap_content"  
  16.         android:text="@string/delete"/>  
  17. </LinearLayout>  


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

[java]  view plain copy print ?
  1. <merge xmlns:android="http://schemas.android.com/apk/res/android">  
  2.   
  3.     <Button  
  4.         android:layout_width="fill_parent"   
  5.         android:layout_height="wrap_content"  
  6.         android:text="@string/add"/>  
  7.   
  8.     <Button  
  9.         android:layout_width="fill_parent"   
  10.         android:layout_height="wrap_content"  
  11.         android:text="@string/delete"/>  
  12.   
  13. </merge>  


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

  取代<include />节点。


   另外的,按需加载View视图 ,请看:

                http://developer.android.com/training/improving-layouts/loading-ondemand.html

   如何使ListView流畅滑动 ,请看:

                http://developer.android.com/training/improving-layouts/smooth-scrolling.html



Android中View绘制优化三

译三:

                                                优化视图




  
关于如何设计自定义View以及响应触摸时间等,请看Android developer :

           地址:http://developer.android.com/training/custom-views/index.html


  本文翻译地址:Optimizing the View



     
   通过前面的学习,现在该设计良好的View能够响应手势以及状态之间进行转换,除此之外你必须确保View

   运行的流畅快速。为了避免迟缓的UI效果或者运行的停顿,必须确保你的动画一直运行在每秒60帧。



 越少越好


        为了加速视图,从那些调用频繁的活动中减少不必要的代码。在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的大小。
          

       如下代码所示:

[java]  view plain copy print ?
  1. /** 
  2.  * Custom view that shows a pie chart and, optionally, a label. 
  3.  */  
  4. public class PieChart extends ViewGroup {  
  5.     ...  
  6.     //  
  7.     // Measurement functions. This example uses a simple heuristic: it assumes that  
  8.     // the pie chart should be at least as wide as its label.  
  9.     //  
  10.     @Override  
  11.     protected int getSuggestedMinimumWidth() {  
  12.         return (int) mTextWidth * 2;  
  13.     }  
  14.     @Override  
  15.     protected int getSuggestedMinimumHeight() {  
  16.         return (int) mTextWidth;  
  17.     }  
  18.   
  19.     @Override  
  20.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  21.         // Try for a width based on our minimum  
  22.         int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();  
  23.   
  24.         int w = Math.max(minw, MeasureSpec.getSize(widthMeasureSpec));  
  25.   
  26.         // Whatever the width ends up being, ask for a height that would let the pie  
  27.         // get as big as it can  
  28.         int minh = (w - (int) mTextWidth) + getPaddingBottom() + getPaddingTop();  
  29.         int h = Math.min(MeasureSpec.getSize(heightMeasureSpec), minh);  
  30.   
  31.         setMeasuredDimension(w, h);  
  32.     }  
  33.   
  34.     @Override  
  35.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  36.         // Do nothing. Do not call the superclass method--that would start a layout pass  
  37.         // on this view's children. PieChart lays out its children in onSizeChanged().  
  38.     }  
  39.       
  40.     @Override  
  41.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  42.         super.onSizeChanged(w, h, oldw, oldh);  
  43.   
  44.         //  
  45.         // Set dimensions for text, pie chart, etc  
  46.         //  
  47.         // Account for padding  
  48.   
  49.         ...  
  50.   
  51.         // Lay out the child view that actually draws the pie.  
  52.         mPieView.layout((int) mPieBounds.left,  
  53.                 (int) mPieBounds.top,  
  54.                 (int) mPieBounds.right,  
  55.                 (int) mPieBounds.bottom);  
  56.         mPieView.setPivot(mPieBounds.width() / 2, mPieBounds.height() / 2);  
  57.   
  58.         mPointerView.layout(00, w, h);  
  59.         onDataChanged();  
  60.     }  
  61.   
  62. }  


使用硬件加速


          Android 3.0版本后,Android 2D图形库能在大多数Android设备上使用GPU(图形处理单元)加速。GPU硬件

  加速可以极大的优化多数应用程序,但它并不是每个应用程序的最优选择。Android框架给予你是否在应用程序中

  使用硬件加速的控制力。


       <<如何运用硬件加速>>篇展示了如何在Application、Activity、Window级别中使用硬件加速。值得注意的是

  我们必须手动在配置文件中设置应用程序API级别为11或者更高级别,即在 AndroidManifest.xml进行如下配置:
  <uses-sdk android:targetSdkVersion="11"/>


         一旦你开启了硬件加速,你可能看不到效率的提升。Mobile GPUs 善于处理特定的任务,例如:伸缩、旋转、

   平移图片。它也有一些不擅长处理的任务,例如:绘制直线或曲线。常言道物尽其用,扬长避短,尽可能让GPU

   处理它擅长的任务,减少让其处理弱势任务的。


         在PieChart 示例中,例如,相对来说绘制一个圆形是比较耗费资源的。每次旋转引起的重绘导致UI的迟缓。

   解决办法就是让View来呈现该圆形,并且设置该View的layer type属性为 LAYER_TYPE_HARDWARE,因此GPU

   能够缓存静态图片。示例中该View作为 PieChart类的内部类存在,减少了为了实现这个方法的代码开销。

[java]  view plain copy print ?
  1. private class PieView extends View {  
  2.   
  3.     public PieView(Context context) {  
  4.         super(context);  
  5.         if (!isInEditMode()) {  
  6.             setLayerType(View.LAYER_TYPE_HARDWARE, null);  
  7.         }  
  8.     }  
  9.       
  10.     @Override  
  11.     protected void onDraw(Canvas canvas) {  
  12.         super.onDraw(canvas);  
  13.   
  14.         for (Item it : mData) {  
  15.             mPiePaint.setShader(it.mShader);  
  16.             canvas.drawArc(mBounds,  
  17.                     360 - it.mEndAngle,  
  18.                     it.mEndAngle - it.mStartAngle,  
  19.                     true, mPiePaint);  
  20.         }  
  21.     }  
  22.   
  23.     @Override  
  24.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  25.         mBounds = new RectF(00, w, h);  
  26.     }  
  27.   
  28.     RectF mBounds;  
  29. }  


          改变之后,只有View第一次显示的时候才会调用PieChart.PieView.onDraw()方法。在应用程序的其他

   时间,绘制的图像将会作为图片缓存,重绘时GPU将任意旋转图像。


         然而这只是一个折中手段。缓存图片作为硬件层导致 video memory开销,video memory却是一种受限制的

 资源。 出于这个原因,在PieChart.PieView的最终版本上,只有在用户滑动时才设置它的layer type属性为

  LAYER_TYPE_HARDWARE。在其他时间,仅仅设置它的layer type属性为 LAYER_TYPE_HARDWARE,这

  允许GPU停止缓存图片。



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


本文转载地址:http://blog.csdn.net/qinjuning



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值