首页 > 代码库 > Android中,如何提升Layout的性能?

Android中,如何提升Layout的性能?

Layout 是 Android 应用中直接影响用户体验的关键部分。如果实现的不好,你的 Layout 会导致程序非常占用内存并且 UI 运行缓慢。Android SDK 带有帮助你找到 Layout 性能问题的工具。

技术分享

主题一:优化Layout层级

一个常见的误区是,用最基础的Layout结构可以提高Layout的性能。然而,因为程序的每个组件和Layout都需要经过初始化、布局和绘制的过程,如果布局嵌套导致层级过深,上面的初始化、布局和绘制操作就更加耗时。例如,使用嵌套的LinearLayout可能会使得View的层级结构过深,此外嵌套使用了 layout_weight参数的LinearLayout的计算量会尤其大,因为每个子元素都需要被测量两次。这对需要多次重复inflate的Layout尤其需要注意,比如嵌套在 ListView或GridView时。

In this lesson you‘ll learn to use Hierarchy Viewer and Layoutopt to examine and optimize your layout. 使用两个工具:Hierarchy Viewer和Layoutopt。

如何审视自己设计的Layout?

Android SDK 工具箱中有一个叫做 Hierarchy Viewer 的工具,能够在程序运行时分析 Layout。你可以用这个工具找到 Layout 的性能瓶颈。

Hierarchy Viewer 会让你选择设备或者模拟器上正在运行的进程,然后显示其 Layout 的树型结构。每个块上的交通灯分别代表了它在测量、布局和绘画时的性能,帮你找出瓶颈部分。比如,下图是 ListView 中一个列表项的Layout。列表项里,左边放一个小位图,右边是两个层叠的文字。像这种需要被多次 inflate 的 Layout ,优化它们会有事半功倍的效果。

技术分享

技术分享

The hierarchyviewer tool is available in <sdk>/tools/. Click Load View Hierarchy to view the layout hierarchy of the selected component.

找到UI性能瓶颈了,如何修正Layout?

上面的 Layout 由于有这个嵌套的 LinearLayout 导致性能太慢,可能的解决办法是将 Layout 层级扁平化 - 变浅变宽,而不是又窄又深。RelativeaLayout 作为根节点时就可以达到目的。所以,当换成基于 RelativeLayout 的设计时,你的 Layout 变成了两层。新的Layout变成如下形式:

技术分享

可能看起来是很小的进步,但是由于它对列表中每个项都有效,这个时间要翻倍。这个时间的主要差异是由于在 LinearLayout 中使用 layout_weight 所致,因为会减慢“测量”的速度。这只是一个正确使用各种 Layout 的例子,当你使用 layout_weight 时有必要慎重。

如何使用Lint工具辅助检测?

运行 Lint 工具来检查 Layout 可能的优化方法,是个很好的实践。Lint 已经取代了Layoutopt工具,它拥有更强大的功能。

从Eclipse中如何启动Lint?如下方式均可以:

技术分享

技术分享

Lint中包含如下检测规则:

使用compound drawable — 用一个compound drawable 替代一个包含 ImageView 和 TextView 的 LinearLayout 会更有效率。

合并根 frame — 如果 FrameLayout 是 Layout 的根节点,并且没有使用 padding 或者背景等,那么用 merge 标签替代他们会稍微高效些。

没用的子节点 — 一个没有子节点或者背景的 Layout 应该被去掉,来获得更扁平的层级。

没用的父节点 — 一个节点如果没有兄弟节点,并且它不是 ScrollView 或根节点,没有背景,这样的节点应该直接被子节点取代,来获得更扁平的层级。

太深的 Layout — Layout 的嵌套层数太深对性能有很大影响。尝试使用更扁平的 Layout ,比如 RelativeLayout 或 GridLayout 来提高性能。一般最多不超过10层。

主题二:使用<include>标签重复利用布局

虽然 Android 提供很多小的可重用的交互组件,你仍然可能需要重用复杂一点的组件,这也许会用到 Layout。为了高效重用整个的 Layout,你可以使用 <include/> 和 <merge/> 标签把其他 Layout 嵌入当前 Layout。

重用 Layout 非常强大,它让你可以创建复杂的可重用 Layout。比如,一个 yes/no 按钮面板,或者带有文字的自定义进度条。这也意味着,任何在多个 Layout 中重复出现的元素可以被提取出来,被单独管理,再添加到 Layout 中。所以,虽然可以添加一个自定义 View 来实现单独的 UI 组件,你可以更简单的直接重用某个 Layout 文件。

如何创建可重用的Layout?

如果你已经知道你需要重用的 Layout,就先创建一个新的 XML 文件并定义 Layout 。比如,以下是一个来自 G-Kenya codelab 的 Layout,定义了一个需要添加到每个 Activity 中的标题栏(titlebar.xml):

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width=”match_parent”
    android:layout_height="wrap_content"
    android:background="@color/titlebar_bg">
    <ImageView android:layout_width="wrap_content"
               android:layout_height="wrap_content" 
               android:src="http://www.mamicode.com/@drawable/gafricalogo" />
</FrameLayout>

其中:根节点的View类型就是你想要的添加进入的Layout。

如何添加到指定Layout中,使用<include>标签

使用 <include> 标签,可以在 Layout 中添加可重用的组件。比如,这里有一个来自 G-Kenya codelab 的 Layout 需要包含上面的那个标题栏:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width=”match_parent”
    android:layout_height=”match_parent”
    android:background="@color/app_bg"
    android:gravity="center_horizontal">
    <include layout="@layout/titlebar"/>
    <TextView android:layout_width=”match_parent”
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp" />
    ...
</LinearLayout>

你也可以覆写被添加的Layout的所有Layout参数(任何android:layout_* 属性),通过在<include/>中声明他们来完成。比如:

<include android:id=”@+id/news_title”
         android:layout_width=”match_parent”
         android:layout_height=”match_parent”
         layout=”@layout/title”/>

但必须指出的是:如果想要覆写被加入Layout的属性,必须先覆写其layout_width和layout_height属性。

另一个方式:使用<merge>标签

<merge /> 标签在你嵌套 Layout 时取消了 UI 层级中冗余的 ViewGroup 。比如,如果你有一个 Layout 是一个竖直方向的 LinearLayout,其中包含两个连续的 View 可以在别的 Layout 中重用,那么你会做一个 LinearLayout 来包含这两个 View ,以便重用。不过,当使用一个 LinearLayout 作为另一个 LinearLayout 的根节点时,这种嵌套 LinearLayout 的方式除了减慢你的 UI 性能外没有任何意义。

为了避免这种情况,可以使用<merge>元素来替代可重用Layout的根节点。

<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <Button
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="@string/add"/>
    <Button
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="@string/delete"/>
</merge>

现在,当你要将这个 Layout 包含到另一个 Layout 中时(并且使用了 <include/> 标签),系统会忽略 <merge> 标签,直接把两个 Button 放到 Layout 中 <include> 的所在位置。

主题三:按需加载布局

主题四:优化ListView滑动性能

保持程序流畅的关键,是让主线程(UI 线程)不要进行大量运算。你要确保在其他线程执行磁盘读写、网络读写或是 SQL 操作等。为了测试你的应用的状态,你可以启用 StrictMode。

使用后台线程加载数据

Using a background thread ("worker thread") removes strain from the main thread so it can focus on drawing the UI.In many cases, using AsyncTask provides a simple way to perform your work outside the main thread. UI线程仅仅做Layout的绘制,“worker thread”运行后台任务。

// Using an AsyncTask to load the slow images in a background thread
new AsyncTask<ViewHolder, Void, Bitmap>() {
    private ViewHolder v;
    @Override
    protected Bitmap doInBackground(ViewHolder... params) {
        v = params[0];
        return mFakeImageLoader.getImage();
    }
    @Override
    protected void onPostExecute(Bitmap result) {
        super.onPostExecute(result);
        if (v.position == position) {
            // If this item hasn‘t been recycled already, hide the
            // progress and set and show the image
            v.progress.setVisibility(View.GONE);
            v.icon.setVisibility(View.VISIBLE);
            v.icon.setImageBitmap(result);
        }
    }
}.execute(holder);

这个行为是全局的,这意味着你不需要考虑自己定义线程池的事情。从 Android 3.0 (API level 11) 开始, AsyncTask 有个新特性,那就是它可以在多个 CPU 核上运行。你可以调用 executeOnExecutor()而不是execute(),前者可以根据CPU的核心数来触发多个任务同时进行。

使用ViewHolder填入视图对象

你的代码可能在 ListView 滑动时经常使用 findViewById(),这样会降低性能。即使是 Adapter 返回一个用于回收的 inflate 后的视图,你仍然需要查看这个元素并更新它。避免频繁调用 findViewById() 的方法之一,就是使用 ViewHolder(视图占位符)的设计模式。

一个 ViewHolder 对象存储了他的标签下的每个视图。这样你不用频繁查找这个元素。第一,你需要创建一个类来存储你会用到的视图。比如:

static class ViewHolder {
  TextView text;
  TextView timestamp;
  ImageView icon;
  ProgressBar progress;
  int position;
}

在Layout类中生成一个ViewHolder实例:

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() 来不断查找子视图,节省了宝贵的运算时间。

Android中,如何提升Layout的性能?