Android Performance: Nested Hierarchies
Author: nex3z
2016-07-13
如果View进行了过多的嵌套,具有较深的层级关系,也会影响性能。下面例子使用Hierarchy Viewer比较了不同层级对性能的影响,Hierarchy Viewer能够可视化地显示布局中View的层级关系,用来对UI进行Debug和优化。代码可以在这里找到,运行后点击“COMPARE LAYOUTS”按钮,界面如图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">
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"/>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/line1_text" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/line2_text"/>
<!-- 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">
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"/>
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" />
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" />
<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 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
在左侧“Windows”下选择“CompareLayoutActivity”,在右边就会显示出该Activity的层级关系,找到并点击对应activity_compare_layouts.xml根部LinearLayout的方块,可见它后面连接了两个方块,分别是LinearLayout中的两栏内容,一个使用嵌套LinearLayout,一个使用RelativeLayout,如图3。
图3
选中activity_compare_layouts.xml根部LinearLayout对应的方块后,点击Hierarchy Viewer右上角三色圆圈的按钮(Obtain layout times for tree rooted at selected node),结果如图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">
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" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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: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" />
<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 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>
<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">
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 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>
<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>