Android最佳实践之性能 - 提升Layout性能

优化布局结构

参考地址:http://developer.android.com/training/improving-layouts/optimizing-layout.html

布局是Android应用程序的关键部分,直接影响到用户体验。如果实现的不好,布局会消耗大量内存,应用程序UI会变得缓慢。Android SDK包含工具来帮助你识别布局性能问题,结合最佳实践,你将能够实现流畅的滚动体验和一个最低内存的占用。

我们有一个误解,就是使用基本的布局结构,可以实现最有效率的布局。加到App中的布局都会经历初始化、布局和绘制的阶段。比如LinearLayout可能会导致一个过深的视图层级。另外,嵌套在LinearLayout中的每一个View使用layout_weight属性时,性能更低,因为它需要被计算(onMeasure)两次。这在ListView 或 GridView中尤甚,因为这两个widget中的布局重复的出现。

检查你的布局

Android SDK提供了一个叫View Hierarchy的工具。,可以帮助你在App运行时检测布局的结构和瓶颈。hierarchyviewer工具位于/tools/目录中。打开之后可以选择设备以及运行的程序,点击* Load View Hierarchy*,例如下图是LinearLayout嵌套的View Hierarchy:
hierarchy-linearlayout
这是一个三层的视图结构,我们看到在第二层的LinearLayout上有一些问题。点击它会出现下图:
hierarchy-layouttimes
渲染一个列表item所花时间如下:
-Measure: 0.977ms
-Layout: 0.167ms
-Draw: 2.717ms
我们看到在draw上花了较多的时间,这样可以针对绘制进行优化。

修复你的布局

上述描述的布局性能问题是来自嵌套的LinearLayout,我们可以使用扁平的布局方式来提升,即减少它的视图层级。RelativeLayout是一个较好的选择,当使用RelativeLayout布局时,你会发现视图层级变成了2级。看上去如下:
hierarchy-relativelayout
选择渲染一个列表item所花时间如下:
Measure: 0.598ms
Layout: 0.110ms
Draw: 2.146ms
看到时间上有一些提升,但因为是列表项,当列表数据很多时,性能就提升了很多了。
大多数时候LinearLayout性能较低,是因为layout_weight属性的使用。

使用Lint

Lint已经取代Layoutopt工具,它具有更强大的功能。一些Lint的规则如下:

  • 可优化的布局:如包含一个Imageview和一个TextView的LinearLayout,可被采用CompoundDrawable的TextView代替
  • 如果FrameLayout作为根布局,而且没有设置backgroud以及padding属性,那么使用标签代替,会有稍稍的性能提升。
  • 无效的泄露。一个布局如果没有子view没有backgroud,一般就该删掉它。减少视图层次。
  • 过深的层级。视图层级过深,将影响性能。我们使用RelativeLayout 或 GridLayout来减少视图层级。默认视图的最大层级是10

Lint集成到Android Studio中,无论何时编译代码它都会自动执行。使用Lint可以对特定的构建变量或全部的构建变量进行监视。
在Android Studio中,File>Settings>Project Settings,可以设置监视配置。
studio-inspections-config
Lint可以自动修复一些问题,或者提供修复建议,还可以直接跳转到对应的代码进行审查。

使用和重用布局

参考地址:http://developer.android.com/training/improving-layouts/reusing-layouts.html
在Android中可以使用 和 来嵌套一个布局到当前布局中。
重用布局特别强大,它允许你创建可重用的复杂的布局。比如,一个Toggle按钮,或自定义的带描述文本的进度条。这也意味着在多个应用程序中通用的布局元素可以提取,单独管理,然后被包含在其它布局中。因此,尽管你可以通过编写一个自定义View创建个人的UI组件,你也可以更轻易的重用一个布局文件。

创建一个重用布局

例如,下面是一个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="@drawable/gafricalogo" />
</FrameLayout>

使用标签

将上面的titlebar加到自己的当前布局中:

<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>

你也可以覆盖被include进来的布局的root View所有的布局参数(任意android:layout_*属性),例如:

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

然而,在include布局中药覆盖布局参数,务必要覆盖android:layout_height 和 android:layout_width参数,这样布局才能正确的生肖。

使用标签

标签有助于消除视图层次中不用的ViewGroup。例如,当你的主布局中有一个垂直的LinearLayout,其中有两个连续的View可以在多个布局中重用,然后将这个重用的布局组织起来需要一个根View,如果使用LinearLayout作为根View,那么会导致增加View的层次结构,这个LinearLayout没有任何实际意义,也会拖慢UI性能。
这时我们使用标签,可以避免这种现象发生:

<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>

现在,你将这个布局使用嵌套到另一个布局中,系统会直接忽略标签,直接将两个按钮加到布局中,取代include标签。

按需加载的ViewStub

http://developer.android.com/training/improving-layouts/loading-ondemand.html#ViewStub
有时需要加载一个暂时不需要的复杂视图,是比较消耗内存等资源且会影响界面的加载速度的。我们使用ViewStub来满足这个需求。

定义一个ViewStub

ViewStub是一个轻量的view,没有尺寸,不需要在布局中draw任何东西。每一个ViewStub都只需要指定android:layout属性即可,这个属性标识ViewStub要解析的布局。

<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布局

需要加载ViewStub的布局,调用setVisibility(View.VISIBLE)inflate()设置它可见即可:

((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

一旦ViewStub设置可见或inflate过之后,ViewStub 就不再是视图层级中的元素了,它会被inflate出来的布局替换,这个布局的rootView的id为ViewStub中的android:inflatedId属性指定的。

注意:ViewStub的一个缺点是现在不支持标签的解析。

让ListView平滑滚动

参考地址:http://developer.android.com/training/improving-layouts/smooth-scrolling.html
为了让ListView滚动平滑,需要让主线程摆脱繁重的任务处理,确保任何的网络操作、磁盘IO操作、SQL操作都在一个单独的线程中运行。

使用后台线程

使用后台线程(“worker thread”),给主线程(UI线程)减轻负担,让其专注于draw UI。AsyncTask 提供一个简单的方式实现在主线程异步加载数据:

// 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的新特性,使用多核心处理器。我们使用executeOnExecutor()而不是execute()来执行任务,它允许同时处理多个后台任务基于可用的处理器的数量。

使用View Holder对象

在滚动ListView时会频繁调用findViewById()方法,这样是非常消耗性能的,我们使用ViewHolder来解决这类问题:

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

然后填充和存储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);

现在你可以很容易地访问每个View,不需要findViewById(),节省宝贵的处理器时间片。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值