布局优化的主要思想其实很简单,就是尽量减少布局文件的层级,布局层级少了Android绘制时的工作量就少了,程序运行时的性能自然就高了。
既然知道了主要思想那么接下来我们要做的工作就明朗了。我们可以从两方面着手进行优化:
第一、优化布局方案
在写布局的过程中我们应该避免无用的控件和层级,并且结合具体的使用场景去选择ViewGroup。避免无用的控件很简单,不必多说。那如何去选择性能更好的ViewGroup呢?这里的ViewGroup也就是我们常说的五大布局——FrameLayout、LinearLayout、RelativeLayout、AbsoluteLayout、TableLayout。其中FrameLayout、LinearLayout、RelativeLayout是最常用的,因此这里只对这三种布局进行分析。
FameLayout
FrameLayout是五大布局中最简单的一个布局,在这个布局中,整个界面被当成一块空白备用区域,所有的子元素都不能被指定放置的位置,它们统统放于这块区域的左上角,并且后面的子元素直接覆盖在前面的子元素之上,将前面的子元素部分和全部遮挡。相同层级布局中 FrameLayout的效率也是最高的 占用内存相对来说也是较小的。但是FrameLayout也有其缺陷:很难去组织多个子元素位置和关系。当然如果只有一个子元素,当然是使用FrameLayout最好。这应该也是为何作为顶级View的DecorView也是一个FrameLayout的原因吧。
LinearLayout与RelativeLayout性能PK
细心的同学应该会发现:无论是Eclipse还是Android Studio,新建Blank Activity时默认的layout都是RelativeLayout。查询资料你会发现这是由 android-sdk\tools\templates\activities\BlankActivity\root\res\layout\activity_simple.xml.ftl 这个文件定义的,也就是说这是Google的选择,而非IDE的选择。而且google原文中也提到
A RelativeLayout is a very powerful utility for designing a user interface because it can eliminate nested view groups and keep your layout hierarchy flat, which improves performance. If you find yourself using several nested LinearLayout groups, you may be able to replace them with a single RelativeLayout.
大概的意思是“性能至上”, RelativeLayout 在性能上更好,因为在诸如 ListView 等控件中,使用 LinearLayout 容易产生多层嵌套的布局结构,这在性能上是不好的。而 RelativeLayout 因其原理上的灵活性,通常层级结构都比较扁平,很多使用LinearLayout 的情况都可以用一个 RelativeLayout 来替代,以降低布局的嵌套层级,优化性能。所以从这一点来看,Google比较推荐开发者使用RelativeLayout,因此就将其作为Blank Activity的默认布局了。
有一篇博客写的很不错,通过实际测试以及源码分析比较了两者的性能,总结的也不错!请移步Android中RelativeLayout和LinearLayout性能分析。关于该博文中提到的“DecorView是一个垂直方向的LinearLayout”这句话是不准确的,通过看源码我们可以知道DecorView其实是一个FrameLayout,而LinearLayout是作为它的唯一子元素,构成了我们熟知的DecorView上下两部分(上面是标题栏,下面是内容栏)的结构。而我们经常在Activity中用到的setContentView正是将我们的布局加到了下面部分的内容栏,即content。而content也是一个FrameLayout。
第二,使用<include>、<merge>标签和ViewStub
这部分内容算是比较基础,详细的使用方式可以去看下官方文档,下边主要介绍下使用时需要注意到的点。
<include>标签
<include>标签主要用于布局重用,从而避免同样的布局写重复的代码,它的使用如下:
<?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">
<include
android:id="@+id/title_bar"
layout="@layout/title_bar"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/string"/>
......
</LinearLayout>
其中layout="@layout/title_bar"
指定了另外的一个布局title_bar.xml,一个应用内很多界面都会用到titlebar,通过这样的方式,这个布局的内容就只需要写一遍就可以了。需要注意的是:1、<include>除了android:id这个属性只支持以 android:layout_开头的属性,而且只要指定了这种属性,那么也必须要指定 android:layout_width 和 android:layout_height,否则会直接报语法错误。2、如果指定了id或者
android:layout_*属性,会直接覆盖导入布局的根布局的相应属性,如果都没有指定则会使用导入布局的根布局的属性。
<merge>标签
在使用<include>标签时,可能会导致重复的布局嵌套,加深布局的层级,从而使得加载变慢,这时我们会用到<merge>标签。所以<merge>标签要和<include>标签配合使用,用来减少布局中重复的层级,而且<merge>标签只能作为<include>指定的布局的根标签使用,这样<merge>标签中包含的子元素,就直接与 <include>平级。
因此在使用<merge>标签时,我们需注意一点:<merge>标签内的子元素会按照<include> 所在布局的布局方式来显示。所以我们在使用的时候需要特别注意布局类型!
ViewStub
在开发过程中,经常会遇到这样一种情况:有些布局在正常情况下不会显示,如网络异常页、空白页等,虽然设置可见性View.GONE,但是在整个界面初始化的时候仍然会将其加载进来,所以增加了初始化时的消耗。这时我们就可以用到ViewStub,它提供了按需加载的功能,在需要的时候才会将ViewStub中的布局加载到内存,所以提高了初始化时的性能,从而也提高了程序的效率。
看源码可以知道,ViewStub继承了View,非常轻量级且宽 /高都是0,因此它本身不参与任何的布局和绘制过程。它的使用方式也很简单,下面是ViewStub的示例:
<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/layout_import"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/layout_net_error" />
其中stub_import是ViewStub的id,而layout_import是layout/layout_net_error这个布局的根元素的id。需要加载ViewStub中的布局时,可以使用以下两种方式:
( (ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
或 View errorLayout = ((ViewStub)findViewById(R.id.stub_import)).inflate();
需要注意的是:ViewStub一旦visible/inflated,就会被它内部的布局替换掉,而它自己也不再是整个布局中的一部分了。所以后面无法再使用ViewStub来控制布局了。
由于ViewStub这种使用后就置空的策略,所以当需要在运行时不止一次的显示和隐藏某个布局时,ViewStub是做不到的,这时就只有使用View的可见性来控制了。另外,目前ViewStub还不支持<merge>标签。
布局优化到这儿就结束了,如果大家还有更好的想法可以提出,我们共同学习,谢谢!