3

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

在我发布我的Adapter实现之前,先解释一下 Image 是如何设置的。我的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 始终指向 my 中正确的可绘制引用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;

}}

现在奇怪的是:使用注释掉的行为所有项目静态设置相同的图像会消除所有滞后和 GC,甚至没有调用一次!我不明白..wp.getImage()返回完全相同的东西,只是R.drawable.name每种武器不同。但是 GC 会在滚动时删除大量对象和ListView滞后。任何想法我做错了什么?

更新

我已将设置图像移至 anAsyncTask并且滞后现在消失了:

    public class AsyncImageSetter extends AsyncTask<Void, Void, Void> {

private ImageView img;
private int image_resId;
private Bitmap bmp;
private Context c;

public AsyncImageSetter(Context c, ImageView img, int image_ResId, Bitmap bmp) {

    this.img = img;
    this.image_resId = image_ResId;
    this.bmp = bmp;
    this.c = c;

}

@Override
protected Void doInBackground(Void... params) {

    bmp = BitmapFactory.decodeResource(c.getResources(), image_resId);

    return null;
}

@Override
protected void onPostExecute(Void result) {

    img.setImageBitmap(bmp);
    bmp = null;

    super.onPostExecute(result);
}

   }

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

4

2 回答 2

3

由于将所有位图加载到 Android 内存中通常是不切实际的,因此您应该假设您会不时获得 GC。

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

  1. 将位图缩小到您需要显示它们的大小。你可以使用谷歌的方式我的方式

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

    例如,如果图像位于可绘制文件夹中,并且您在 xhdpi 设备(如 Galaxy S3)上运行它,则需要 (WIDTH*2)*(HEIGHT*2)*4 bytes 。如果图像是 200x200 ,它的位图对象至少需要 400*400*4=640,000 bytes 。在 xxhdpi 设备上情况会更糟,比如 Galaxy s4 和 htc 设备。

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

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

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

顺便说一句,我注意到您为图像使用了一组标识符。如果图像的名称中有一些逻辑(例如:img1,img2,...),您可以使用 getResources().getIdentifier(...) 代替。

于 2013-08-25T18:51:14.997 回答
1

如果您修复了低 fps 问题并且现在快速滚动执行流畅,那么您实际上很高兴。

当频繁的 GC 操作对您来说只是一个表面问题并且您没有面临OutOfMemoryException或任何其他不利因素时,那么您应该就这样离开它。如果这不是您的选择,您可以做另一件事:除了下采样和缓存之外,您还可以在启动之后AsyncTask和实际检索资源文​​件之前添加一个小的人工等待时间(50-150 毫秒)。然后你在你的任务中添加一个取消标志,在人为延迟后必须检查它。如果它设置为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;
    }
}

这样,如果用户快速滚动,则不会从您的内存中读取大多数图像,并且您将节省大量内存。

于 2013-08-25T19:59:21.443 回答