ListView性能很慢

时间:2013-08-25 01:41:32

标签: android android-listview garbage-collection

我有一个自定义ListView,其中显示了一些从本地数据库中检索到的武器。我总共有88行,每次调用getView()时,每行都会设置一个文本和一个图像。 ListView在快速滚动时滞后,垃圾收集器变得疯狂,每秒删除一些1M对象。我不明白为什么。

在我发布Adapter实现之前,有关如何设置图像的一些说明。我的 Weapon 类只是一个包含setter和getter的数据持有者。这就是在创建数据库时设置名称和图像的方式(是的,它看起来很奇怪,但所有其他解决方案的工作速度更慢):

    private Weapon buildWeapon(Cursor cursor) {
    Weapon w = new Weapon();
    w.setId(cursor.getLong(0));
    w.setName(cursor.getString(1));
    w.setImage(Constants.ALL_WEAPON_IMAGES[(int) cursor.getLong(0)-1]);


    return w;
}

所以我有Array包含R.drawable.somegun形式的所有武器图像。数据结构的实现方式使得ID-1始终指向我Array中的正确可绘制引用。 Weapon类中的图像字段是Integer。现在您已了解我的getImage()方法的工作原理以及下面的Adapter

 public class Weapon_Adapter extends BaseAdapter {
private List<Weapon> items;
private LayoutInflater inflater = null;
private WeaponHolder weaponHolder;
private Weapon wp;


static class WeaponHolder {
    public TextView text;
    public ImageView image;
}

// Context and all weapons of specified class are passed here

public Weapon_Adapter(List<Weapon> items, Context c) {
    this.items = (List<Weapon>) items;
    inflater = LayoutInflater.from(c);
    Log.d("Adapter:", "Adapter created");
}

@Override
public int getCount() {
    return items.size();
}

@Override
public Weapon getItem(int position) {
    return items.get(position);
}


@Override
public long getItemId(int position) {
    return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    wp = (Weapon) getItem(position);

    if (convertView == null) {
        convertView = inflater.inflate(R.layout.category_row, null);
        weaponHolder = new WeaponHolder();
        weaponHolder.text = (TextView) convertView
                .findViewById(R.id.tvCatText);
        weaponHolder.image = (ImageView) convertView
                .findViewById(R.id.imgCatImage);
        convertView.setTag(weaponHolder);
    }

      weaponHolder = (WeaponHolder) convertView.getTag();   


    weaponHolder.text.setText(wp.getName());
    weaponHolder.image.setImageResource(wp.getImage());
           // weaponHolder.image.setImageResource(R.drawable.ak74m);




    return convertView;

}}

现在奇怪的是:使用outcommented行为所有项目静态设置相同的图像删除所有滞后和GC甚至不调用一次!我没有得到它.. {{1返回完全相同的东西,每个武器只有wp.getImage()不同。但GC在滚动时删除了大量对象和R.drawable.name滞后。我有什么想法吗?

更新

我已将图像设置为ListView,现在滞后现已消失:

AsyncTask

然而,在向上和向下滚动整个列表时,仍然会像疯了一样调用GC并增加RAM消耗。现在的问题是:如何优化图像回收以避免RAM使用量增加?

2 个答案:

答案 0 :(得分:2)

因为将所有位图加载到Android的内存中通常是不切实际的,所以你应该假设你不时会得到GC。

但是,您可以考虑下一个提示:

  1. 将位图缩小到您需要显示它们的大小。您可以使用google's waymy way

  2. 检查您放置图像文件的文件夹。很多人把它们放在res / drawable文件夹中,并且不明白它们为什么会比它们的原始尺寸大得多(这是因为密度 - 它是mdpi,而设备可能是xhdpi或xxhdpi)。

    例如,如果图像在drawable文件夹中并且您在xhdpi设备(如galaxy S3)上运行它,则需要(WIDTH * 2)*(HEIGHT * 2)* 4个字节。如果图像是200x200,则其位图对象将至少需要400 * 400 * 4 = 640,000个字节。在xxhdpi设备上会更糟糕,比如galaxy s4和htc设备。

  3. 考虑使用内存缓存,例如LruCache

  4. 如果位图没有透明度,并且您没有看到质量上的任何差异,请考虑使用RGB_565 config而不是默认值。这将占用每像素2个字节而不是每个像素4个字节。

  5. 如果您有足够的责任感,可以使用JNI进行缓存。我为这项任务制作了一个小代码,here。请阅读我在那里写的所有笔记。

  6. 不过,我注意到你为图像使用了一系列标识符。如果图像的名称中有一些逻辑(例如:img1,img2,...),则可以使用getResources()。getIdentifier(...)。

答案 1 :(得分:1)

如果你修复了低fps问题并且快速滚动表现得很顺利,那么你真的很高兴。

如果频繁的GC操作对您来说只是一个美化问题,并且您没有遇到OutOfMemoryException或任何其他缺点,那么您应该这样做。如果你不能选择它,你可以做另外一件事:除了下采样和缓存之外你还可以在启动AsyncTask之后和实际检索资源文​​件之前添加一个小的人工等待时间(50-150ms) 。然后在您的任务中添加一个取消标志,必须在人工延迟后进行检查。如果设置为true,则不请求资源文件。

一些(不可执行的)代码示例:

class MyImageLoader extends AsyncTask {
    private boolean cancel = false

    private Bitmap bitmap;

    public void cancel() { cancel = true }

    public void doInBackground() {
        sleep(100);
        if(!cancel) {
            bitmap = BitmapFactory.decodeResource(...);
        }
    }
}

class Adapter {

    static class WeaponHolder {
        public TextView text;
        public ImageView image;
        public MyImageLoader loader;
    }

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

        if (convertView == null) {
            ...
            holder = new WeaponHolder();
        } else {
            holder = convertView.getTag();
            holder.loader.cancel(); // Cancel currently active loading process
        }

        holder.loader = new MyImageLoader();
        holder.loader.execute();

        return convertView;
    }
}

这样,如果用户快速滚动并且您将节省大量内存,则无法从内部存储器中读取大部分图像。