Android性能优化之优化布局层次结构

前言

Android系统允许我们在布局中嵌套布局。然而,嵌套的布局会增加UI布局阶段花费的时间,从而降低UI的性能。布局层次结构越浅,UI布局阶段花费的时间就越少,从而UI的性能就越好。因此,在满足需求的前提下,我们应该尽量地优化布局层次结构。

检查布局

你可以使用一些工具来检查你的布局是否需要优化。通常,可以使用以下两种工具来检查布局:

  • Hierarchy Viewer
  • Lint

其中,Hierarchy Viewer允许你在应用运行时检查布局层次结构,Lint允许你检查XML布局文件中可能的布局层次结构优化。

使用Hierarchy Viewer

Hierarchy Viewer是一个内置在Android Device Monitor中的工具,它允许你在应用运行时查看布局层次结构以及每个View的属性和布局速度。使用这个工具可以帮助你发现布局层次结构导致的性能瓶颈,从而帮助你优化布局层次结构。

具体如何使用Hierarchy Viewer可以阅读 Android性能优化工具之HierarchyViewer 这篇文章。

使用Lint

Lint是一个静态代码检查工具,它可以帮助你检查可能影响应用质量和性能的代码问题。该工具会报告检测到的每个问题并提供问题的描述信息和严重级别,以便你可以快速地确定需要优先进行哪些改进。在使用Android Studio时,配置的Lint和IDE检查会在你编写代码时自动运行。当然,你也可以手动运行Lint。

具体如何使用Lint可以阅读 Android静态代码检查工具Lint 这篇文章。

下面是一些Lint检查的例子:

  • 检查多余的ViewGroup。一个没有子View又没有背景的ViewGroup可以删除。一个只有一个子View又没有背景的、不是根布局或者ScrollView的ViewGroup可以删除,并将它的子View直接移动到上一层的ViewGroup中。
  • 检查可以优化的LinearLayout。一个包含一个ImageView和一个TextView的LinearLayout可以修改为更有效率的使用drawableXXX属性的TextView。
  • 检查布局深度。布局层次结构过深不利于UI的性能。默认最大的深度为10。

优化布局层次结构

优化布局层次结构的目标是:使布局变得浅而宽,而不是窄而深。常用的优化策略有以下几种:

  • 删除多余的嵌套布局。
  • 使用RelativeLayout。
  • 使用merge标签。

删除多余的嵌套布局

开发人员有时会在布局中嵌套多余的布局。例如以下几种场景:

  • 一个ViewGroup中包含了一个只有一个子View、没有背景、不是ScrollView的ViewGroup,这个子ViewGroup是多余的。
  • 一个垂直的LinearLayout中又包含了一个垂直的LinearLayout,这个子LinearLayout是多余的。
  • 一个RelativeLayout中又包含了一个RelativeLayout,这个子RelativeLayout是多余的。

使用RelativeLayout

使用LinearLayout来布局有时会加深布局层次结构。例如,考虑如下图所示的布局:

如果使用LinearLayout来布局,那么需要嵌套一层垂直的LinearLayout。示例代码如下所示:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_item"
    android:layout_width="match_parent"
    android:layout_height="@dimen/relative_item_height"
    android:padding="@dimen/relative_item_padding"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/img_album"
        android:layout_width="@dimen/relative_img_album_size"
        android:layout_height="@dimen/relative_img_album_size"
        android:layout_marginRight="@dimen/relative_img_album_margin_right"
        android:src="@mipmap/ic_launcher" />

    <LinearLayout
        android:id="@+id/ll_item_info"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_song_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/relative_tv_song_title_margin_top"
            android:textColor="@color/black_333333"
            android:textSize="18sp"
            android:text="@string/relative_song_title" />

        <TextView
            android:id="@+id/tv_artist_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/relative_tv_artist_name_margin_top"
            android:textColor="@color/gray_7d7d7d"
            android:textSize="16sp"
            android:text="@string/relative_artist_name" />
    LinearLayout>
LinearLayout>

使用Hierarchy Viewer来查看布局层次结构,如下图所示:

如果使用RelativeLayout来布局,那么不需要嵌套一层子布局。示例代码如下所示:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rl_item"
    android:layout_width="match_parent"
    android:layout_height="@dimen/relative_item_height"
    android:padding="@dimen/relative_item_padding">

    <ImageView
        android:id="@+id/img_album"
        android:layout_width="@dimen/relative_img_album_size"
        android:layout_height="@dimen/relative_img_album_size"
        android:layout_marginRight="@dimen/relative_img_album_margin_right"
        android:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/tv_song_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/img_album"
        android:layout_marginTop="@dimen/relative_tv_song_title_margin_top"
        android:textColor="@color/black_333333"
        android:textSize="18sp"
        android:text="@string/relative_song_title" />

    <TextView
        android:id="@+id/tv_artist_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/img_album"
        android:layout_below="@id/tv_song_title"
        android:layout_marginTop="@dimen/relative_tv_artist_name_margin_top"
        android:textColor="@color/gray_7d7d7d"
        android:textSize="16sp"
        android:text="@string/relative_artist_name" />
RelativeLayout>

使用Hierarchy Viewer查看此时的布局层次结构,如下图所示:

可以看到,使用RelativeLayout来布局时少了一层布局。

使用merge标签

产生多余的嵌套布局的另一个常见原因是使用include标签。使用include标签可以重用那些公共的布局。

首先,如果你知道某个布局是可重用的,那么你可以创建一个新的XML文件来定义此布局。例如:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_common"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/common_tv_text_view" />

    <Button
        android:id="@+id/btn_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="@string/common_btn_button" />
LinearLayout>

该公共的布局在垂直方向上放置了一个TextView和一个Button。

然后,在你想要包含公共布局的布局文件中,使用include标签来包含公共布局。例如:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_merge_demo"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="@dimen/merge_layout_padding"
    android:orientation="vertical">

    <include layout="@layout/layout_common" />

    <TextView
        android:id="@+id/tv_hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
LinearLayout>

使用Hierarchy Viewer查看布局层次结构,如下图所示:

对于该例,主布局是一个垂直的LinearLayout,同时公共布局也是一个垂直的LinearLayout,这样就产生了多余的嵌套布局。

为了避免产生一个多余的嵌套布局,你可以使用 merge 标签来作为可重用的公共布局的根标签。例如:


<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <TextView
        android:id="@+id/tv_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/common_tv_text_view" />

    <Button
        android:id="@+id/btn_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAllCaps="false"
        android:text="@string/common_btn_button" />
merge>

使用Hierarchy Viewer查看此时的布局层次结构,如下图所示:

可以看到,主布局中不再产生多余的嵌套布局了。这是因为,当你使用merge标签时,系统会忽略merge标签,并将所有的子View直接放置在主布局中。

总结

嵌套的布局会增加UI布局阶段花费的时间,从而降低UI的性能。布局层次结构越浅,UI布局阶段花费的时间就越少,从而UI的性能就越好。因此,在满足需求的前提下,我们应该尽量地优化布局层次结构。

参考

  • https://developer.android.com/training/improving-layouts/optimizing-layout.html
  • https://developer.android.com/training/improving-layouts/reusing-layouts.html
  • https://developer.android.com/topic/performance/rendering/optimizing-view-hierarchies.html

你可能感兴趣的:(Android性能)