Android性能优化-视图布局

摘要:优化Android视图布局。

1. 简介

  应用程序布局直接影响用户体验。如果实现不够理想,那么会导致严重内存问题并且拖慢加载过程。Android SDK中包含了相关工具用来定位布局的性能问题,通过这些工具你能够以最小的内存实现平滑滚动。
  以下从四部分描述如何进行布局优化,分别为减少布局层次、 使用实现布局复用、 使用ViewStub动态加载视图、缓存视图对象。

2. 减少布局层次

  如果复杂的网页会拖长加载时间一样,越复杂的布局加载时间越长,带来的用户体验也越差。因为每个视图都需要经过初始化、布局和绘制的过程,如果布局嵌套层次越深,这些过程就越耗时。因此在构建布局时应该尽量将布局进行扁平化处理,即尽量少进行布局嵌套。
  查找布局层次问题的工具有:Hierarchy Viewer、Lint、LayoutOpt(已被Lint替代)。
  Lint提供的一些优化建议:
  - 使用Compound Drawable — 用一个Compound Drawable 替代一个包含 ImageView 和 TextView 的LinearLayout 会更有效率;
  - 使用merge合并根节点 — 如果 FrameLayout 是 Layout 的根节点,并且没有使用padding 或者背景等,那么用 merge 标签替代他们会稍微高效些;
  - 去除无用的子节点 — 一个没有子节点或者背景的 Layout 应该被去掉,来获得更扁平的层次;
  - 去除无用的父节点 — 一个节点如果没有兄弟节点,并且它不是 ScrollView 或根节点,没有背景,这样的节点应该直接被子节点取代,来获得更扁平的层次
  - 使用RelativeLayout或GridLayout — LinearLayout这类布局可能会导致嵌套层次太深,可以尝试使用更扁平的布局,例如RelativeLayout 或 GridLayout 。

以LinearLayout替换为RelativeLaytout为例,通过Hierarchy Viewer查看,如图一使用LinearLayout层次为数3,替换后图二RelativeLaytout层次数为2。

图一 LinearLayout层次
替换后:

图二 RelativeLaytout层次

3. 使用merge标签实现布局复用

  在Android提供了来复用布局,它可以在多个布局界面中使用相同的布局,并可以设置独立的属性。
  例如为实现多个界面相同的标题栏背景,首先定义titlebar.xml如下:

1
2
3
<FrameLayout>
<ImageView />
</FrameLayout>

在需要设置标题的Activity布局中:

1
2
3
4
<LinearLayout>
<include layout="@layout/titlebar"/>
<TextView/>
</LinearLayout>

标签虽然可以多处复用,但同时因为中Layout带来了布局的冗余,上面例子会直接展开:

1
2
3
4
5
6
<LinearLayout>
<FrameLayout>
<ImageView />
</FrameLayout>
<TextView/>
</LinearLayout>

可以看出这里将多出布局,这是我们不需要的,因此标签设计用来替代可重用的根节点。修改titlebar.xml:

1
2
3
<merge>
<ImageView/>
</merge>

布局最终结构为:

1
2
3
4
<LinearLayout>
<ImageView />
<TextView/>
</LinearLayout>

这里标签将被去除,子节点将直接应用在上层布局中。

4. 使用ViewStub动态加载视图

  有时我们可能不希望某部分视图在初始化时就被加载,而是希望需要呈现时才进行加载以便提高加载速度。例如进度条、列表项的细节,这时可以使用ViewStub来实现。
  ViewStub是一个不包含尺寸的轻量级视图。它不会在Layout中加入任何信息,只有当ViewStub设置为可见或调用inflate()时才会在上级Layout中加入ViewStub 属性android:layout指向的布局。
如下定义一个ViewStub:

1
2
3
4
5
6
7
<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/panel_import"
android:layout="@layout/progress_overlay"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom" />

ViewStub载入方式一设为可见:

1
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);

ViewStub载入方式二调用inflate():

1
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

  一旦设为可见或执行了inflate(),ViewStub元素不再是视图层次中的一部分。它将被它指向的布局替代,并且ID为属性android:inflatedId的值(android:id属性值将不再有效)。

5. 使用ViewHolder缓存视图对象

  频繁调用findViewById()可能会影响到性能,例如在ListView中可能会影响到滑动的效果,通过缓存视图对象减少查询操作来提高性能。
  例如可以定义一个ViewHolder类:

1
2
3
4
5
6
7
static class ViewHolder {
TextView text;
TextView timestamp;
ImageView icon;
ProgressBar progress;
int position;
}

在ListView每一项创建时执行:

1
2
3
4
5
6
ViewHolder holder = new ViewHolder();
holder.icon = (ImageView) convertView.findViewById(R.id.listitem_image);
holder.text = (TextView) convertView.findViewById(R.id.listitem_text);
holder.timestamp = (TextView) convertView.findViewById(R.id.listitem_timestamp);
holder.progress = (ProgressBar) convertView.findViewById(R.id.progress_spinner);
convertView.setTag(holder);

这样可以快速获取每一项的视图,而不需要通过findViewById(),从而节省了CPU计算时间提高了效率。

6. 相关文章

http://developer.android.com/training/improving-layouts/index.html