Android Performance: Nested Hierarchies

  如果View进行了过多的嵌套,具有较深的层级关系,也会影响性能。下面例子使用Hierarchy Viewer比较了不同层级对性能的影响,Hierarchy Viewer能够可视化地显示布局中View的层级关系,用来对UI进行Debug和优化。代码可以在这里找到,运行后点击“COMPARE LAYOUTS”按钮,界面如图1所示。

图1

图1

其中包含了两个看似完全一样的布局,具体定义在activity_compare_layouts.xml

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

    <!-- Version 1. Uses nested LinearLayouts -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/activity_vertical_margin">

        <ImageView
            android:id="@+id/chat_author_avatar1"
            android:layout_width="@dimen/avatar_dimen"
            android:layout_height="@dimen/avatar_dimen"
            android:layout_margin="@dimen/avatar_layout_margin"
            android:src="@drawable/joanna"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/line1_text" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/line2_text"/>
        </LinearLayout>
    </LinearLayout>


    <!-- Version 2: uses a single RelativeLayout -->
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/activity_vertical_margin">

        <ImageView
            android:id="@+id/chat_author_avatar2"
            android:layout_width="@dimen/avatar_dimen"
            android:layout_height="@dimen/avatar_dimen"
            android:layout_margin="@dimen/avatar_layout_margin"
            android:src="@drawable/joanna"/>


        <TextView
            android:id="@+id/rl_line1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/chat_author_avatar2"
            android:text="@string/line1_text" />

        <TextView
            android:id="@+id/rl_line2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/rl_line1"
            android:layout_toRightOf="@id/chat_author_avatar2"
            android:text="@string/line2_text" />
    </RelativeLayout>
</LinearLayout>

从中可以看到,根部的LinearLayout中包含了两栏内容,分别使用LinearLayout嵌套和单个RelativeLayout实现了相同的视觉效果。

  点击Android Studio工具栏的“Android Monitor”按钮,打开Android Monitor后,点击右上角“Open Perspective”按钮,打开Hierarchy Viewer,如图2所示。

图2

图2

  在左侧“Windows”下选择“CompareLayoutActivity”,在右边就会显示出该Activity的层级关系,找到并点击对应activity_compare_layouts.xml根部LinearLayout的方块,可见它后面连接了两个方块,分别是LinearLayout中的两栏内容,一个使用嵌套LinearLayout,一个使用RelativeLayout,如图3。

图3

图3

  选中activity_compare_layouts.xml根部LinearLayout对应的方块后,点击Hierarchy Viewer右上角三色圆圈的按钮(Obtain layout times for tree rooted at selected node),结果如图4。

图4

图4

可以看到嵌套在根LinearLayout下的LinearLayout和RelativeLayout方块下面出现了三个彩色的圆圈。三个圆圈分别代表Measure、Layout和Draw三个阶段,颜色代表对应阶段的性能:

  • 绿色代表该View在该阶段的耗时短于所选树中50%的View;
  • 黄色代表该View在该阶段的耗时长于所选树中50%的View;
  • 红色代表该View在该阶段的耗时在所选树中是最长的。

  可见使用单个RelativeLayout的实现的性能要高于使用嵌套LinearLayout。尽可能减少View的嵌套,降低层级关系,可以提高性能。以chat_item.xml为例,它也使用了多个LinearLayout进行嵌套:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:paddingBottom="@dimen/chat_padding_bottom">

    <ImageView
        android:id="@+id/chat_author_avatar"
        android:layout_width="@dimen/avatar_dimen"
        android:layout_height="@dimen/avatar_dimen"
        android:layout_margin="@dimen/avatar_layout_margin" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#78A"
            android:orientation="horizontal">

            <TextView xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:padding="@dimen/narrow_space"
                android:gravity="bottom"
                android:id="@+id/chat_author_name" />

            <TextView xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentRight="true"
                android:textStyle="italic"
                android:padding="@dimen/narrow_space"
                android:id="@+id/chat_datetime" />
        </RelativeLayout>

        <TextView xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="@dimen/narrow_space"
            android:id="@+id/chat_text" />
    </LinearLayout>

</LinearLayout>

而使用RelativeLayout,只保留一个层级就可以实现相同的效果,如这里所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/chat_author_avatar"
        android:layout_width="@dimen/avatar_dimen"
        android:layout_height="@dimen/avatar_dimen" />

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/chat_author_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/chat_author_avatar"
        android:paddingLeft="@dimen/narrow_space" />

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/chat_datetime"
        android:layout_alignParentRight="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingRight="@dimen/narrow_space"
        android:textStyle="italic" />

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/chat_text"
        android:layout_toRightOf="@id/chat_author_avatar"
        android:layout_below="@+id/chat_datetime"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="@dimen/narrow_space"
        android:paddingBottom="@dimen/chat_padding_bottom" />
</RelativeLayout>