我有一个自定义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使用量增加?
答案 0 :(得分:2)
因为将所有位图加载到Android的内存中通常是不切实际的,所以你应该假设你不时会得到GC。
但是,您可以考虑下一个提示:
将位图缩小到您需要显示它们的大小。您可以使用google's way或my way。
检查您放置图像文件的文件夹。很多人把它们放在res / drawable文件夹中,并且不明白它们为什么会比它们的原始尺寸大得多(这是因为密度 - 它是mdpi,而设备可能是xhdpi或xxhdpi)。
例如,如果图像在drawable文件夹中并且您在xhdpi设备(如galaxy S3)上运行它,则需要(WIDTH * 2)*(HEIGHT * 2)* 4个字节。如果图像是200x200,则其位图对象将至少需要400 * 400 * 4 = 640,000个字节。在xxhdpi设备上会更糟糕,比如galaxy s4和htc设备。
考虑使用内存缓存,例如LruCache
如果位图没有透明度,并且您没有看到质量上的任何差异,请考虑使用RGB_565 config而不是默认值。这将占用每像素2个字节而不是每个像素4个字节。
如果您有足够的责任感,可以使用JNI进行缓存。我为这项任务制作了一个小代码,here。请阅读我在那里写的所有笔记。
答案 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;
}
}
这样,如果用户快速滚动并且您将节省大量内存,则无法从内部存储器中读取大部分图像。