在Android layout中的各种问题一直是让人很头疼。其中,我们只要稍微不留神,自己设计的layout在性能上的表现总会不尽人意。

悲剧回顾:

记得之前有一个项目,前期的layout设计几乎到处都是里三层外三层的各种嵌套。这样的布局实现效果与美工的效果图虽然相差无几,但到了后期,令人难以接受的悲剧发生了——一些配置较低的手机只要运行在layout嵌套较深的界面上,总会出现卡顿现象,若有输入控件,激活输入法时,也会引起程序crash……

悲剧之由来:

我们得始终坚信:有果必有因。

1. 初学时养成的潜意识:不考虑性能,先看效果。大家可能是这样学习的,想快点掌握其实现方法,性能或优化等方面的问题,留着以后再考虑。可是,何时才是“以后”呢?

2. 没有深入理解各种layout运用:怎么方便就怎么来,例如使用LinearLayout进行多层嵌套即可轻松实现复杂的布局。

3. 没太留意用户体验感受:随着时间推移,手机配置越来越高,人们已经很难从感知上去判别layout的加载速度会带来什么样的体验。有时候,即使能感觉到有一点点的有问题,也会轻易放过自己:没关系。

4. 有意识,但不熟悉优化:有时候知道会有问题,但不知道如何判断其优劣,不清楚如何下手,缺少思路与方法等。

Layout优化大致思路:

1. 尽可能消耗较少的系统资源:包括时间,内存,电量等。

2. 尽可能流畅,不给人卡顿,加载慢,等待时间过长,干等无操作的尴尬体验等。

如何对症下药?

我们大概清楚上述的因果后,就得对具体案例进行对症下药,加深理解,进而举一反三。这回我们不能为所欲为了!

那么如何优化呢?下面是一个简单的例子,演示优化步骤及思路:

1. 优化前的一个3层级的layout:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center" >

        <TextView
            android:id="@+id/account"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:gravity="center_vertical"
            android:text="账号:" />

        <EditText
            android:id="@+id/accountInput"
            android:layout_width="200dp"
            android:layout_height="60dp"
            android:gravity="center_vertical"
            android:hint="请输入账号" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:gravity="center" >

        <TextView
            android:id="@+id/pwd"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:gravity="center_vertical"
            android:text="密码:" />

        <EditText
            android:id="@+id/pwdInput"
            android:layout_width="200dp"
            android:layout_height="60dp"
            android:gravity="center_vertical"
            android:hint="请输入6位以上的密码" />
    </LinearLayout>

</LinearLayout>

其实,在手机上运行这个layout,我们的感官并不能觉察到有什么不妥或不好的体验。所以,我们得通过第三方工具Hierarchy View查看其性能参数:

从上图很直观地看到:这是一个4层级的layout;在id为content的节点上读取其性能数据:
Measure:0.562ms
Layout:0.287ms
Draw:2.212ms

同时,也计算了一下setContentView(R.layout.nested_layout)的时间:42ms

06-16 15:21:35.869: D/MainActivity(4739): setContentView(R.layout.nested_layout), takeTime:42ms

计算代码如下:

    private static final String TAG = "MainActivity";

    private long start = 0;

    private long takeTime = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        start = System.currentTimeMillis();

        setContentView(R.layout.nested_layout);

        takeTime = System.currentTimeMillis() - start;

        Log.d(TAG, "setContentView(R.layout.nested_layout), takeTime:" + takeTime + "ms");

    }

就是这么一个简单的UI布局,上面的性能指示器就有3个为红色,暂且(在没有对比之前)认为效果并不是很好。
具体分析:
1. 这里使用了3个容器(LinearLayout),都是用于控制子元素的显示方向;
2. 想想上面的优化思路,尽可能减少时间,内存的开销:在实现同样效果的情况下,容器越多,加载实例化容器,计算,布局,绘制的操作相应地会消耗更多的时间与内存。
3. 如果我们使用较少的容器实现一样的效果,这不就是优化性能了吗?
4. 我们的RelativeLayout不就可以做得到吗?

2. 采用RelateLayout优化后的layout

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center" >

        <TextView
            android:id="@+id/account"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:layout_alignParentLeft="true"
            android:gravity="center_vertical"
            android:text="账号:" />

        <EditText
            android:id="@+id/accountInput"
            android:layout_width="200dp"
            android:layout_height="60dp"
            android:layout_toRightOf="@id/account"
            android:gravity="center_vertical"
            android:hint="请输入账号" />

        <TextView
            android:id="@+id/pwd"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:layout_below="@id/account"
            android:gravity="center_vertical"
            android:text="密码:" />

        <EditText
            android:id="@+id/pwdInput"
            android:layout_width="200dp"
            android:layout_height="60dp"
            android:layout_below="@id/accountInput"
            android:layout_toRightOf="@id/pwd"
            android:gravity="center_vertical"
            android:hint="请输入6位以上的密码" />
    </RelativeLayout>

</LinearLayout>

同样使用工具查看:

很明显,我们减少了一个容器,其中的2个LinearLayout被一个RelateLayout代替,性能指示器也没有出现红色。在同样的节点处性能参数如下:
Measure:0.443ms
Layout:0.060ms
Draw:1947ms

同样,计算setContentView(R.layout.optimized_layout)花费的时间:36ms

各指标都比原来提高了。

事情还没完,是不是总觉得夹在FrameLayout与RelateLayout之间的LinearLayout有点多余呢?我RelateLayout干嘛要经过你LinearLayout呢,如果真的非要经过你,你能说说你在这里的有用之处吗?

3. 去除没必要层级:

再请来一个大神对第2步的layout进行检查与评判——Lint:


从上图红圈中的提示内容可知:id为LinearLayout1的容器是没有必要存在的。所以这次的优化得去掉这没必要的层级。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:gravity="center"
 android:paddingBottom="@dimen/activity_vertical_margin"
 android:paddingLeft="@dimen/activity_horizontal_margin"
 android:paddingRight="@dimen/activity_horizontal_margin"
 android:paddingTop="@dimen/activity_vertical_margin" >

<TextView
 android:id="@+id/account"
 android:layout_width="wrap_content"
 android:layout_height="60dp"
 android:layout_alignParentLeft="true"
 android:gravity="center_vertical"
 android:text="账号:" />

<EditText
 android:id="@+id/accountInput"
 android:layout_width="200dp"
 android:layout_height="60dp"
 android:layout_toRightOf="@id/account"
 android:gravity="center_vertical"
 android:hint="请输入账号" />

<TextView
 android:id="@+id/pwd"
 android:layout_width="wrap_content"
 android:layout_height="60dp"
 android:layout_below="@id/account"
 android:gravity="center_vertical"
 android:text="密码:" />

<EditText
 android:id="@+id/pwdInput"
 android:layout_width="200dp"
 android:layout_height="60dp"
 android:layout_below="@id/accountInput"
 android:layout_toRightOf="@id/pwd"
 android:gravity="center_vertical"
 android:hint="请输入6位以上的密码" />

</RelativeLayout>

再看层级图:

清楚地看到,性能指示器一片绿,相比于优化前:我们原来需要解掉3件“衣服”,现在只需要解掉一件就可以看到你想要的,你说这速度能不快吗?到此,前面的暂且可以去掉了!

小结:

1. 性能指示器,优化layout的参考方向。

2. 有效利用Lint工具,进行优化,他不仅仅应用于layout的优化,还包括code的优化,总之其功能非常强大!