Android应用性能优化系列视图篇——ListView自适应导致的严重性能问题

ListView是Android中最常用的视图之一,使用的频率仅仅次于三大基础布局,虽然由于使用性和扩展性等原因备受争议,且尽管后来出现了RecyclerView的替代方案,但是ListView仍然广泛地使用在我们的项目中。

自从ListView出道至今,已经不知道衍生出了多少问题,然而很多人只关心功能功能的实现,却极少关注ListView过度调用导致的性能问题。在实际项目中,即使你正确使用了ViewHolder机制来优化ListView性能,但是在某些场景下依然会感觉卡顿严重,到底是什么原因导致的呢,我们来分析下。


1、问题演示

很多时候,我们在使用ListView的时候,都是随手写上一个layout_height=”wrap_content”或者layout_height=”match_parent”,非常常规的写法,乍一看,并没有什么问题,尤其是功能实现上也是无可挑剔。

然而,就是layout_height=”wrap_content”这个属性是导致严重的性能问题的根源,下面以一个简单的例子说明一下:

这里写图片描述

布局如上,接下来,假设ListView一共有5项,那么显示逻辑代码如下:

这里写图片描述

下面,我们来看看log打印的情况:

这里写图片描述

数一数,一个是15次getView调用,其中6次convertView为null,剩余9次convertView为复用,而ListView的数据源真正只有5项!

当然,为了场景的简单化,我们先不考虑ListView内容超过一屏幕的情况(也就是不考虑其复用机制),所以我们期待的情况应该是getView调用5次且convertView全部为null,而事实上getView多调用了10次且有一次convertView为null。

同样的,我们测试一下当layout_height=”match_parent”的情况:

这里写图片描述

另外,ListView内容超过一屏幕的情况下(考虑复用机制),测试结果一样,这里就不再演示了。

在实际项目中,Adapter的getView方法承载着大量的业务逻辑,在性能方面,除去创建视图的损耗,不正确的ListView使用方式导致的性能损耗大约是正常的3倍左右!那么到底是什么原因导致的呢?我们下面来简单分析下ListView源码。


2、原因分析

在演示了layout_height=”wrap_content”导致性能问题的现象之后,我们来从源码的角度分析下,出现这种过度调用问题的根本原因。(源码以API 23为例)

首先,layout_height=”wrap_content”属性意味着ListView的高度需要由子View决定,即在onMeasure的时候,需要一一测量子View的高度,所以我们先从其onMeasure方法入手。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    ...
    if (heightMode == MeasureSpec.AT_MOST) {
        // TODO: after first layout we should maybe start at the first visible position, not 0
        heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
    }
    ...
}

了解View绘制原理的都知道wrap_content对应的mode为MeasureSpec.AT_MOST,所以很容易就能找到测量子视图高度的代码measureHeightOfChildren,当然方法名也体现出来了,所以具体来看这个方法。

final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, 
    int endPosition, int maxHeight, int disallowPartialChildPosition) {

    ...

    endPosition = (endPositio
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值