您的位置:首页 > 移动开发 > Android开发

【终极奥义·真】android-性能优化-第一章-布局优化

2017-06-19 14:55 316 查看
Android性能优化,从简单的入手,需要先了解Android的布局优化

首先需要理解Android布局显示的大致原理

         1、首先Android会把XML布局文件转换成GPU可以识别和绘制的对象。

         2、然后CPU会将XML中的UI组件经过计算之后,交给GPU进行栅格化渲染。

         3、最后等到GPU栅格化渲染完成之后,硬件会将效果展示在屏幕上。

那么问题来了,什么是栅格化呢?

Resterization 栅格化是绘制那些Button,Shape,Path,String,Bitmap等组件最基础的操作,它把那些组件拆分到不同的像素上进行显示,如上图所示,手机的屏幕其实都是有一个一个小格子组成的图片,而当这些小格子非常非常小的时候,那么就看起来像上图第一个@中所示一样了。这是一个很费时的操作,因此这也是为什么要引入GPU原因。


        每次从CPU转移到GPU是一件很麻烦的事情,所幸的是OpenGL ES可以把那些需要渲染的文理Hold在GPU的Memory里面,在下次需要渲染的时候直接进行操作。Android里面那些由主题所提供的资源,例如Bitmap,Drawables都是一起打包到统一的Texture文理当中,
然后再传递到GPU里面,这意味着每次你需要使用这些资源的时候,都是直接从文理里面进行获取渲染的。当然随着UI组件的越来越丰富,有了更多演变的形态。例如显示图片的时候,需要先经过CPU的计算加载到内存中,然后传递到GPU进行渲染。文字的显示更加复杂,需要先经过CPU换算成纹理,然后再交给GPU进行渲染,回到CPU绘制单个字符的时候,再重新引用经过GPU渲染的内容。动画则是一个更加复杂的流程。

  为了能够使得APP流畅,我们需要在每一帧16ms以内完成所有的CPU与GPU计算,绘制,渲染等等操作。也就是帧率为60fps,为什么帧率要为60fps呢,因为人眼与大脑之间的协作无法感知超过60fps的画面更新。开发app的性能目标就是保持60fps,这意味着每一帧你只有16ms=1000/60的时间来处理所有的任务。这里需要了解下刷新率和帧率:

  Refresh Rate:代表了屏幕在一秒内刷新屏幕的次数,这取决于硬件的固定参数,例如60HZ。
  Frame Rate:代表了GPU在一秒内挥之操作的帧数,例如30fps,60fps。
  此外这里引入了VSYNC的机制,Android就是通过VSYNC信号来同步UI绘制和动画,使得它们可以获得一个达到60fps的固定的帧率。GPU会获取图形数据进行渲染,然后硬件负责把渲染后的内容呈现到屏幕上,他们两者不停地进行协作。不幸的是,刷新频率和帧率并不是总能保持相同的节奏。如果发生帧率与刷新频率不一致的情况,就会容易出现显示内容发生断裂,重叠。

  帧率超过刷新率只是理想的状况,在超过60fps的情况下,GPU所产生的帧数据会因为等待VSYNC的刷新信息而被Hold住,这样能够保持每次刷新都有实际的新的数据可以显示。



  但是我们遇到更多的情况是帧率小于刷新频率。在这种情况下,某些帧显示的画面内容就会与上一帧的画面相同。糟糕的事情是,帧率从超过60fps突然掉到60fps以下,这样就会发生LAG,JANK,HITCHING等卡顿停顿的不顺滑的情况,这也是用户感受不好的原因所在。

  在某个View第一次需要被渲染时,DisplayList会因此而被创建,当这个View要显示到屏幕上时,我们会执行GPU的绘制指令来进行渲染。如果你在后续有执行类似移动这个view的位置等操作而需要再次渲染这个View时,我们就仅仅需要额外操作一次渲染指令就够了。然而如果修改了View中的某些可见组件,那么之前的DisplayList就无法继续使用了,我们需要回头重新创建一个DisplayList并且重新执行渲染指令并更新到屏幕上。

  需要注意的是:任何时候View中的绘制内容发生变化时,都会重新执行创建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。这个流程的表现性能取决于View的复杂程度,View的状态变化以及渲染管道的执行性能。

  所以我们需要尽量减少Overdraw。

  Overdraw(过度绘制):描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,就会导致某些像素区域被绘制了多次,浪费大量的CPU以及GPU资源。(可以通过开发者选项,打开Show GPU Overdraw的选项,观察UI上的Overdraw情况)。



  蓝色、淡绿、淡红,深红代表了4种不同程度的Overdraw的情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。

如果你没有太多时间看上面的东西,其实可以从这里开始看的哦(别打我 >.<!)
       好啦,现在我们对原理有了初步的认识了,然后就是在开发中的问题了。

Android开发中常用的布局优化方法:

       综上所述,布局优化的基本原则就是尽量少的嵌套,减少层级以减少android性能消耗,从而达到应用性能的最优。

       要想优化自己的布局,首先得知道怎么查看自己的布局层级情况,所以这个需要介绍的就是

【第一式·Android Studio之hirerchy】

      1、 Tool--Android--Android
Device Monitor打开DDMS窗口,点击dump-view-hirerchy for UI
automator,如图:

 


          通过hirerchy工具我们能够清楚的看到我们应用测层级关系和组件信息,然后我们就需要通过分析查看并简化我们复杂层级

           so,我们需要进入下一阶段的学习了。

【第二式·合理使用layout】

常用布局大概是 RelativeLayout、Framlayout 、LinearLayout

 RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,也会调用子View2次onMeasure
 RelativeLayout的子View如果高度和RelativeLayout不同,则会引发效率问题,当子View很复杂时,这个问题会更加严重。如果可以,尽量使用padding代替margin。
在不影响层级深度的情况下,使用LinearLayout和FrameLayout而不是RelativeLayout。
最后再思考一下文章开头那个矛盾的问题,为什么Google给开发者默认新建了个RelativeLayout,而自己却在DecorView中用了个LinearLayout。因为DecorView的层级深度是已知而且固定的,上面一个标题栏,下面一个内容栏。采用RelativeLayout并不会降低层级深度,所以此时在根节点上用LinearLayout是效率最高的。而之所以给开发者默认新建了个RelativeLayout是希望开发者能采用尽量少的View层级来表达布局以实现性能最优,因为复杂的View嵌套对性能的影响会更大一些。
能用两层LinearLayout,尽量用一个RelativeLayout,在时间上此时RelativeLayout耗时更小。另外LinearLayout慎用layout_weight,也将会增加一倍耗时操作。由于使用LinearLayout的layout_weight,大多数时间是不一样的,这会降低测量的速度。这只是一个如何合理使用Layout的案例,必要的时候,你要小心考虑是否用layout
weight。总之减少层级结构,才是王道,让onMeasure做延迟加载,用viewStub,include等一些技巧。

此结论来自:http://blog.csdn.net/hejjunlin/article/details/51159419
【第三式·抽象布局标签】

1、include标签 
      在我们开发过程中,经常会遇到同个布局,被多个界面使用的情况,这种情况就需要我们引入 include 标签,达到布局的复用。 

2、merge标签

       顾名思义就是合并、融合的意思,它的主要作用是:

    (1) 自定义View中使用,父元素尽量是FrameLayout,当然如果父元素是其他布局,而且不是太复杂的情况下也是可以使用的 

    (2) Activity中的整体布局,根元素需要是FrameLayout

3、viewstub标签

我们先看看官方的说明: 
ViewStub is a lightweight view with no dimension and doesn’t draw anything or participate in the layout. As such, it’s cheap to inflate and cheap to leave in a view hierarchy. Each ViewStub simply needs to include the android:layout
attribute to specify the layout to inflate.

ViewStub就是一个宽高都为0的一个View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才 会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过android:layout属性来设置。例如我们通过一个 ViewStub来惰性加载一个消息流的评论列表,因为一个帖子可能并没有评论,此时我可以不加载这个评论的ListView,只有当有评论时我才把它加 载出来,这样就去除了加载ListView带来的资源消耗以及延时。

其实就是一个轻量级的页面,我们通常使用它来做预加载处理,来改善页面加载速度和提高流畅性,ViewStub本身不会占用层级,它最终会被它指定的层级取代。
       还是说说演出项目吧,还说?对了,实践才能发现问题嘛,在哪儿发现问题就在那儿改进。由于项目中用到了比较多的动画,而且嵌套布局比较复杂,所以在Android低端机上进行页面切换时候经常让人感觉卡卡的,不怎么流畅,因为页面切换动画和标题旋转动画是同时进行的,所以为了达到更好的体验就需要使用一种方法,在动画进行时尽量的减少其他操作,特别是页面加载重绘。赶紧想办法,起初我想先将要加载的页面所有的组件都初始为gone显示状态,整个页面只留一下加载滚动条,后来发现这是不行滴,因为在Android的机制里,即使是将某一个控件的visibility属性设置为不可见的gone,在整个页面加载过程中还是会加载此控件的。再后来就用到了ViewStub,在做页面切换动画时,只在页面中放一个loading加载图标和一个ViewStub标签

实例:类似这样,我先自定义一个main.xml,里面会用到merge和viewstub标签(测试例子可以略过,只是本人为了测试能否实现,具体情况大家自己试试就行了,熟能生巧哈)
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<merge
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.heli.hlapp.testrxjava.MainActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="botton"
android:orientation="horizontal">

<Button
android:id="@+id/main_touchmeBtn"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="TouchMe!~" />
<Button
android:id="@+id/main_fightingBtn"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="开打开打~!" />
<Button
android:id="@+id/main_firstBtn"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="EQR一套过去" />
<Button
android:id="@+id/visible"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text=" 显示 隐藏" />
</LinearLayout>

<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/replace_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>

</merge>
</layout>

综上所述,具体怎么使用需要结合自己项目特点,合理使用才能发挥它们最大的效率,没有最好的方法,只有适合自己的方法,本文有部分内容未做细节深究,后续会逐步加上。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息