Performance Tips for Android’s ListView

本文介绍如何通过视图回收、ViewHolder模式、异步加载和交互感知等技巧提高Android中ListView组件的性能表现。

I’ve been messing around with Android-based code for a few months now while hacking on Native Firefox for Android and Pattrn. I noticed that the performance tips for ListViews are a bit scattered in different sources. This post is an attempt to summarize the ones I found most useful.

I’m assuming you’re already familiar with ListViews and understand the framework around AdapterViews. I’ve added some Android source code pointers for the curious readers willing to understand things a bit deeper.

How it works

ListView is designed for scalability and performance. In practice, this essentially means:

  1. It tries to do as few view inflations as possible.
  2. It only paints and lays out children that are (or are about to become) visible on screencode.

The reason for 1 is simple: layout inflations are expensive operationscode. Although layout files are compiled into binary form for more efficient parsingcode, inflations still involve going through a tree of special XML blockscode and instantiating all respective views. ListView solves this problem by recyclingcode non-visible views—called “ScrapViews” in Android’s source code—as you pan around. This means that developers can simply update the contents of recycled viewscode instead of inflating the layout of every single row—more on that later.

In order to implement 2, ListView uses the view recycler to keep adding recycled views below or above the current viewport and moving active views to a recyclable pool as they move off-screencode while scrolling. This wayListView only needs to keep enough views in memory to fill its allocated space in the layout and some additional recyclable views—even when your adapter has hundreds of items. It will fill the space with rows in different ways—from top, from bottom, etc—depending on how the viewport changedcode. The image below visually summarizes what happens when you pan a ListViewdown.

With this framework in mind, let’s move on to the tips. As you’ve seen above,ListView dynamically inflates and recycles tons of views when scrolling so it’s key to make your adapter’s getView() as lightweight as possible. All tips resolve around making getView() faster in one way or another.

View recycling

Every time ListView needs to show a new row on screen, it will call thegetView() method from its adapter. As you know, getView() takes three arguments arguments: the row position, a convertView, and the parentViewGroup.

The convertView argument is essentially a “ScrapView” as described earlier. It will have a non-null value when ListView is asking you recycle the row layout. So, when convertView is not null, you should simply update its contents instead of inflating a new row layout. The getView() code in your adapter would look a bit like:

public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.your_layout, null);
    }

    TextView text = (TextView) convertView.findViewById(R.id.text);
    text.setText("Position " + position);

    return convertView;
}

View Holder pattern

Finding an inner view inside an inflated layout is among the most common operations in Android. This is usually done through a View method calledfindViewById(). This method will recursively go through the view tree looking for a child with a given IDcode. Using findViewById() on static UI layouts is totally fine but, as you’ve seen, ListView calls the adapter’s getView() very frequently when scrolling. findViewById() might perceivably hit scrolling performance in ListViews—especially if your row layout is non-trivial.

The View Holder pattern is about reducing the number of findViewById() calls in the adapter’s getView(). In practice, the View Holder is a lightweight inner class that holds direct references to all inner views from a row. You store it as a tag in the row’s view after inflating it. This way you’ll only have to usefindViewById() when you first create the layout. Here’s the previous code sample with View Holder pattern applied:

public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;

    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.your_layout, null);

        holder = new ViewHolder();
        holder.text = (TextView) convertView.findViewById(R.id.text);

        convertView.setTag(holder);
    } else {
        holder = convertView.getTag();
    }

    holder.text.setText("Position " + position);

    return convertView;
}

private static class ViewHolder {
    public TextView text;
}

Async loading

Very often Android apps show richer content in each ListView row such as images. Using drawable resources in your adapter’s getView() is usually fine as Android caches those internallycode. But you might want to show more dynamic content—coming from local disk or internet—such as thumbnails, profile pictures, etc. In that case, you probably don’t want to load them directly in your adapter’s getView() because, well, you should never ever block UI thread with IO. Doing so means that scrolling your ListView would look anything but smooth.

What you want to do is running all per-row IO or any heavy CPU-bound routine asynchronously in a separate thread. The trick here is to do that and still comply with ListView‘s recycling behaviour. For instance, if you run anAsyncTask to load a profile picture in the adapter’s getView(), the view you’re loading the image for might be recycled for another position before theAsyncTask finishes. So, you need a mechanism to know if the view hasn’t been recycled once you’re done with the async operation.

One simple way to achieve this is to attach some piece of information to the view that identifies which row is associated with it. Then you can check if the target row for the view is still the same when the async operation finishes. There are many ways of achieving this. Here is just a simplistic sketch of one way you could do it:

public View getView(int position, View convertView,
        ViewGroup parent) {
    ViewHolder holder;

    ...

    holder.position = position;

    new ThumbnailTask(position, holder)
            .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);

    return convertView;
}

private static class ThumbnailTask extends AsyncTask {
    private int mPosition;
    private ViewHolder mHolder;

    public ThumbnailTask(int position, ViewHolder holder) {
        mPosition = position;
        mHolder = holder;
    }

    @Override
    protected Cursor doInBackground(Void... arg0) {
        // Download bitmap here
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (mHolder.position == mPosition) {
            mHolder.thumbnail.setImageBitmap(bitmap);
        }
    }
}

private static class ViewHolder {
    public ImageView thumbnail;
    public int position;
}

Interaction awareness

Asynchronously loading heavier assets for each row is an important step to towards a performant ListView. But if you blindly start an asynchronous operation on every getView() call while scrolling, you’d be wasting a lot of resources as most of the results would be discarded due to rows being recycled very often.

We need to add interaction awareness to your ListView adapter so that it doesn’t trigger any asynchronous operation per row after, say, a fling gesture on the ListView—which means that the scrolling is so fast that it doesn’t make sense to even start any asynchronous operation. Once scrolling stops, or is about to stop, is when you want to start actually showing the heavy content for each row.

I won’t post a code sample for this—as it involves too much code to post here—but the classic Shelves app by Romain Guy has a pretty good example. It basically triggers the async book cover loading once the GridView stops scrolling among other things. You can also balance interaction awareness with an in-memory cache so that you show cached content even while scrolling. You got the idea.

That’s all!

I strongly recommend watching Romain Guy and Adam Powell’s talk aboutListView as it covers a lot of the stuff I wrote about here. Have a look at Pattrnif you want to see these tips in action. There’s nothing new about the tips in this post but I thought it would be useful to document them all in one place. Hopefully, it will be a useful reference for hackers getting started on Android development.

Quick update. I’ve just announced Smoothie, a tiny library that that offers an easy way to do async loading in ListViews/GridViews. It incorporates most of the tips described in this blog post behind a very simple API. Have a look!

Got something to add? Find me on Twitter and Google+

一、 内容概要 本资源提供了一个完整的“金属板材压弯成型”非线性仿真案例,基于ABAQUS/Explicit或Standard求解器完成。案例精确模拟了模具(凸模、凹模)与金属板材之间的接触、压合过程,直至板材发生塑性弯曲成型。 模型特点:包含完整的模具-工件装配体,定义了刚体约束、通用接触(或面面接触)及摩擦系数。 材料定义:金属板材采用弹塑性材料模型,定义了完整的屈服强度、塑性应变等真实应力-应变数据。 关键结果:提供了成型过程中的板材应力(Mises应力)、塑性应变(PE)、厚度变化​ 云图,以及模具受力(接触力)曲线,完整再现了压弯工艺的力学状态。 二、 适用人群 CAE工程师/工艺工程师:从事钣金冲压、模具设计、金属成型工艺分析与优化的专业人员。 高校师生:学习ABAQUS非线性分析、金属塑性成形理论,或从事相关课题研究的硕士/博士生。 结构设计工程师:需要评估钣金件可制造性(DFM)或预测成型回弹的设计人员。 三、 使用场景及目标 学习目标: 掌握在ABAQUS中设置金属塑性成形仿真的全流程,包括材料定义、复杂接触设置、边界条件与载荷步。 学习如何调试和分析大变形、非线性接触问题的收敛性技巧。 理解如何通过仿真预测成型缺陷(如减薄、破裂、回弹),并与理论或实验进行对比验证。 应用价值:本案例的建模方法与分析思路可直接应用于汽车覆盖件、电器外壳、结构件等钣金产品的冲压工艺开发与模具设计优化,减少试模成本。 四、 其他说明 资源包内包含参数化的INP文件、CAE模型文件、材料数据参考及一份简要的操作要点说明文档。INP文件便于用户直接修改关键参数(如压边力、摩擦系数、行程)进行自主研究。 建议使用ABAQUS 2022或更高版本打开。显式动力学分析(如用Explicit)对计算资源有一定要求。 本案例为教学与工程参考目的提供,用户可基于此框架进行拓展,应用于V型弯曲
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值