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

布局优化

2018-01-09 11:46 477 查看
        系统在渲染UI界面的时候将消耗大量的资源,一个好的UI不仅应该具有良好的视觉效果,更应该具有良好的使用体验,因此布局优化显的很重要。

1.AndroidUI渲染机制

        人眼所感觉的流程画面,需要画面的帧数达到40帧每秒到6帧每秒,相信玩过PC游戏的朋友应该对帧数的概念非常清楚,最佳fps大概在60fps左右,这也是评估一个显卡性能高低的标准之一。在Android中,系统通过VSYNC信号出发对UI的渲染、重绘,其间隔时间时16ms。这个16ms其实就是1000ms中显示60帧画面的单位时间,即1000/60.如果系统每次渲染时间都保持在16ms之间,那么我们看见的UI界面时非常流畅的,但者也需要将所有程序的逻辑都保证在16ms之内。如果不能在16ms内完成绘制,那么就会造成丢帧现象,即当前该重绘的帧未完成的逻辑阻塞,例如一次绘制认为耗时20ms,那么在16ms系统发出VSYNC信号就无法绘制,该帧就被丢弃,等下次信号才开始绘制,导致16*2ms内显示同一帧的画面,这就是画面卡顿的原因。

      Android系统提供了检查UI渲染时间的工具,打开“开发者选项”,选择“Profile GPU Rendering”,并选中“On screen as bars”的选项,这时候在屏幕上将显示一些条形图,如图所示。



        每一条柱状线都包含三部分,蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代码CPU等待GPU处理的时间。中间的绿色横线代表VSYNC时间16ms,需要尽量将所有条形图都控制在这条绿线之下。

2.避免 Overdraw

        Overdraw,过渡绘制会浪费很多的CPU、GPU资源,例如系统默认会绘制Activity的背景,而如果再给布局绘制了重叠的背景,那么默认Activity的背景就属于无效的过渡绘制——Overdraw。Android系统再开发者选项中提供了这样一个检测工具——“Enable GPU Overdraw”。激活后,可以通过界面上的颜色来判断Overdraw的次数,这里借助Google开发者博客上的一张图来演示,如图所示。



        通过这个工具可以查看当前区域中的绘制次数,从而尽量优化绘制层次,尽量增大蓝色的区域、减少红色的区域。

3.优化布局层级

       在Android中,系统对View进行测量、布局和绘制时,都是通过对View数的遍历来进行操作的。如果一个View树的高度太高,就会严重影响测量、布局和绘制的速度,因此优化布局的第一个方法就是降低View树的高度,Google也在其API文档中建议View树的高度不宜超过10层。

        不知道是否有读者注意到,在早期的Android版本中,Google使用LinearLayout作为默认创建XML文件的根布局,而现在版本的Android中,Google已经使用RelativeLayout来替代LinearLayout作为默认的根布局,其原因就是通过扁平的RelativeLayout来降低通过LinearLayout嵌套所产生布局树的高度,从而提高UI渲染的效率。

4.避免嵌套过多无用布局

        嵌套布局会让View树的高度变得越来越高,因此在布局时,需要根据自身布局的特点来选择不同的Layout组件,从而避免通过某一种Layout组件来实现功能的局限性,从而造成嵌套过多的情况的发生。

 4.1 使用<include>标签重用Layout

        在一个应用程序界面中,为了风格上的统一,很多界面都会存在一些共通的UI,比如一个应用的Topbar、Bottombar等。对于这些共通的UI,如果用户在每个界面中都来赋值一段这样的代码,不仅不利于后期代码的维护,更增加了程序的冗余度。因此,可以使用<include>标签来定义这样一个共通UI。

        下面我们就演示下如何使用<include>标签。为了能够在不同的Layout组件中使用共通UI,所以最好不要在共通UI中写太多某个特定Layout组件中才有的属性。请看一个非常简单的布局文件,代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="0dp"
android:textSize="30sp"
android:gravity="center"
android:text="this is a common ui"
/>


        代码中只有一个TextView,并居中显示一段文字,但更多的时候,我们都会通过相应的Layout组件将若干个控件组合成一个共通UI。为了简化操作,这里暂且把他作为一个共通UI。在代码中,将layout_width和layout_height设置为0dp,这样就迫使开发者在使用对宽高进行复制,否则将无法看见这个界面。

        那么如何使用这个共通的UI呢?非常简单,只需要在使用该共通UI布局文件中通过<include>标签的layout属性添加这个共通UI的ID的引用即可,代码如下所示。

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="test.chenj.study_chapter7_7.Main7Activity">
<TextView
android:text="hello world"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<include layout="@layout/common_ui"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"/>
</RelativeLayout>


        通过上面的代码我们可以发现,在<include>标签中,同样可以使用Layout组件的一些属性来控制引用的布局。不这里有一点需要非常注意,如果你需要在<include>标签中覆盖类似原布局android:layout_XXXX的属性,就必须在<include>标签中同时指定android:layout_width和android:layout_height属性。实现效果如下图所示。 



4.2 使用<ViewStub>实现View的延迟加载

        除了把一个View作为共通UI,并通过<include>标签来进行引用之外,还可以使用<ViewStub>标签来实现对一个View的引用冰实现延迟加载。<ViewStub>是一个非常轻量级的组件,它不仅不可视,而且大小为0。下面同样通过一个实例来演示下如何使用<ViewStub>。

        首先创建一个布局,这个布局在初始化加载时不需要显示,只在某些情况下显示出来,例如查看用户信息时,只有点击某个按钮时,用户详细信息才显示出来。我们写一个简单的布局文件,代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv"
android:text="not often use layout"
android:textSize="30sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>


        接下来,与使用<include>标签类似,在主布局中的<ViewStub>中的layout属性属性来引用上面的布局,代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:text="Visible"
android:onClick="btnVisible"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<Button
android:id="@+id/button2"
android:text="Inflate"
android:onClick="btnInflate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/button"
android:layout_toRightOf="@+id/button" />

<ViewStub
android:id="@+id/not_often_use"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/not_often_use"/>
</RelativeLayout>


        运行程序后,我们发现,<ViewStub>标签中引用的布局的确没有显示出来,那么如何重新加载显示的布局呢?

        首先,通过普通的findViewById()方法找到<ViewStub>组件,这点与一般的组件基本相同,代码如下所示。

mViewStub = (ViewStub)findViewById(R.id.not_often_use);


        接下来,有两种方式来重新显示下这个View。

VISIBLE
        通过调用ViewStub的setVisibility()方法来显示这个View,代码如下所示。

mViewStub.setVisibility(View.VISIBLE);

inflate
        通过调用ViewStub的inflate()方式来显示这个View,代码如下所示。

View inflateView = mViewStub.inflate();

        这两种方式都可以让ViewStub重新展开,显示引用的布局,而唯一的区别就是inflate()方法可以返回引用的布局,从而可以再通过View.findViewById()方法来找到对应的控件,代码如下所示。

View inflateView = mViewStub.inflate();TextView textView = (TextView)inflateView.findViewById(R.id.tv);
textView.setText("Haha!");

        而不管使用哪种方式,一旦<ViewStub>被设置为可见或者是被inflate了,<ViewStub>就不存在了,取而代之的是被inflate的Layout,并将这个Layout的ID重新设置为<ViewStub>中通过android:inflatedId属性所制定的ID,这也是为什么两次调用inflate方法会报错的原因。

        整个程序的运行效果如下图所示。



        看到这里,由朋友可能由疑问,<ViewStub>标签与设置View.GONE这种方式来隐藏一个View有什么区别呢?的确,它们的共同点都是初始时不会显示,但是<ViewStub>标签只会在显示时,采取渲染这个布局,而View.GONE,在初始化布局树的时候就已经添加在布局树上了,相比之下<ViewStub>标签的布局具有=更高的效率。

5.Hierarchy Viewer

        无论时那本讲解布局优化的参考书,它们都不得不提到Hierarchy Viewer。不过,通常情况下,Hierarchy Viewer无法在真机上使用,它只能在工厂的Demo机和模拟机上使用,即非加密过的设备。Google的大神——Romaine Guy提供了一个开源项目View Server,通过这个程序可以让普通手机也能使用Hierarchy Viewer,有兴趣的朋友可以去如下所示的网址了解下。

https://github.com/romainguy/ViewServer

       通过模拟器看的方式再此就不演示了,虚拟机比较吃电脑内存内存不够。本例直接采用真机,连接电脑进行测试。本人已经实测了真机可以连接Hierarchy Viewer,可以参考博客:使用ViewServer让Android真机连接Hierarchy Viewer的步骤

        为了测试这个工具,我写了个非常冗余的布局文件,代码如下所示。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="test.chenj.study_chapter7_7.Main8Activity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:text="hierarchyviewer"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

        可以发现,使用三层LinearLayout嵌套,只装载了一个Button,很显然这些LinearLayout都是冗余的。下面利用hierarchyviewer工具,使用前面所说的方法打开这个布局,其显示结果就是下图所示的内容。



        通常情况下,重点关注ID为content的FrameLayout的分支,这也是setContentView()所设置的内容,如下图所示。

       


       在这里可以看见三层LinearLayout,而且这三层LinearLayout都没有任何分支。这说明了这些LinearLayout都是可以直接去掉的,这与我们的分析也是一样的。

        当点击其中一个View的时候,可以显示该View的绘制情况。不过,第一次点击的时候,各种显示的时间都是NA,需要点击菜单中的“Profile Node"按钮重新进行计算,才能获取绘制信息。此时就可以知道每个View所绘制的时长,并且系统在下方也给出了三个不同颜色的小圆点,用来表示绘制的效率,绿、黄、红分别代表好、中、坏三种不同的绘制效率。



       通过hierarchyviewer工具,就可以很快地在视图中找到冗余地布局,从而有目的地优化布局。同时,hierarchyviewer工具还可以显示很多有用的信息,如下图所示。



      总之,hierarchyviewer是进行布局优化的一个非常有用的工具,大家可以在官方API文档中找到更多更详细的使用教程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android性能优化